Zwiebelchen
Hosted Project GR
- Joined
- Sep 17, 2009
- Messages
- 7,234
What is a threat system?
Basicly, it's a system that takes over Mob AI. The unit that dealt the most damage to the creep will be the one that gets attacked.
Although that is only half the truth, it should be enough to get the idea.
ZTS is the easiest to use and most stable threat system currently available on the web.
Requirements:
- vJass preprocessor
What is different from this to other systems:
- First of all, this system is almost (if not completely) 100% fail-safe. It does checks on killed AND removed units and clears them automaticly.
- Second, it features a unique camp squad functionality, which ensures that units that are preplaced in camps will always attack and return together. If units return, they will be rendered invulnerable until they reach their original camp position again. If they can not reach their camping position within a certain duration, they will get instantly teleported back.
Like this, there is almost no more kiting or bug-abusing possible, as it was with other systems.
- Third: It does not block other Spellcasting systems. The system will only give orders to units, if the unit either has no order (i.e. auto-engage) or an "attack" order. If you want the unit to use a spell instead of attacking, you can do so whenever you want without having to fear interference of the system.
In fact, there is a special function provided so that people can create their own Spell-AI directly in a custom trigger
- Fourth: It provides a lot of useful functions and Getters (Like ApplyHealThreat or GetCombatState), to make triggering spells even more easy.
- Fifth: It is, by far, the fastest and most flexible threat system out there. I completely remastered the system from the last version and the entire script (except for certain enumerations that can not be avoided) is now O(1) complexity.
Version history:
2.2b
- fixed a small logic bug with GetThreatUnitAmount and GetThreatUnitPosition
2.2
- Now uses "smart" order instead of "attack" to issue attack orders ... it turned out that the "smart" order returns false if the unit can not reach the target (for example when rooted) - weird, as it doesn't work for "attack" for some reason
- because of that, the AddRootAbility function and the Range Setter and Getters were removed, as they have become obsolete
2.1b
- fixed a small logical bug with GetCombatState sometimes returning a false positive
2.1
- rebuilt Update function to avoid some useless enumerations - depending on the number of PlayerUnits registered, the system should now be MULTIPLE TIMES faster - As a side effect, I could also remove some useless variables
2.0
- Initial release
Comments & Discussion:
Why hashtables instead of global arrays and structs?
- hashtables are not limited in terms of max size, unlike and array of structs with a unit array, which reaches the 8000 limit very fast ... this was basicly the most important point on that decision
- hashtables are more flexible and easier to use (Flush functions, etc.)
- hashtables were benchmarked to be only 60-80% slower than getting UnitUserData alone
Where is the sort function?
- version 2.0 and higher does not use sorting anymore; instead, when threat is applied to a unit, it uses an insertion method to keep the order of the list
What is new compared to pre 2.0 versions?
- Aside from the fact that the system now is a dozen times faster than before, I also improved the AI by using the "smart" instead of "attack" order. It turned out that - in a weird way - "smart" is indeed smarter than other orders, as it returns false if the unit can't reach the target (i.e. because of root)
- There is now a way to directly get an Order event by the threat system, to make creating spell-AI easier.
Does it matter how many units are registered to the system at the same time?
- In terms of speed, no; it only affects memory usage, but that should not have an impact on game performance at all, even with tousands of units registered - only the number of currently fighting units affects runtime
I still do not really understand how to use the system...
- just check the demo map and you'll get the idea
JASS:
//------------------------------------------------------------------------------------------------------//
// ZTS - ZWIEBELCHEN'S THREAT SYSTEM v. 2.2b //
//------------------------------------------------------------------------------------------------------//
//------------------------------------------------------------------------------------------------------//
// //
// Requires: vJass Pre-Processor //
// //
// Special thanks to TEC-Ghost, who inspired me on creating this system. //
//------------------------------------------------------------------------------------------------------//
// MANUAL //
//------------------------------------------------------------------------------------------------------//
// 1. How to Install:
//
// - Create a trigger called "ZTS"
// - Convert it to custom Text
// - Replace everything inside with this code
//
//
// 2. How to set it up:
//
// 2.1 Constants
//
// There are a bunch of global constants below this manual, you can edit to your liking.
// I commented everything you need to know about those constants right beside them. If you need additional information,
// please tell me, so I can improve this manual. However, I think most of it should be pretty clear.
//
// 2.2. Gameplay Constants
//
// It is recommended to edit certain Gameplay Constants entries, to use the full potential of
// the system.
// The most important entries are: (with selected "Show Raw-Data")
// CallForHelp --> Set this to 0, if possible; the system will manage this - it isn't a problem if you don't do this, though
// You don't have to do it if your threat-system controlled units are neutral hostile
// CreepCallForHelp --> Set this to 0, if possible; the system will manage this - it isn't a problem if you don't do this, though
// GuardDistance --> Set this to something higher than ReturnRange (see below)
// MaxGuardDistance --> Set this to something higher than ReturnRange (see below)
// GuardReturnTime --> Set this to something very high, so that the standard AI doesn't interfere (i.e. 60 seconds)
//
// 2.3. Damage Detection
//
// Of course, a threat system is pretty useless without a trigger, that adds threat depending on
// the damage dealt by a player unit.
// I recommend using a damage detection script like IDDS:
// ([url]http://www.wc3c.net/showthread.php?t=100618[/url])
// Check up the demo map for a very simple (but leaking) damage detection trigger
// The only function you actually need then is:
// call ModifyThreat(GetEventDamageSource(), GetTriggerUnit(), GetEventDamage(), true)
// The function does all required checks on its own. There is no need to run something else.
//
// 3. How to use it:
//
// 3.1. Core functions
//
// call ZTS_AddThreatUnit(unit npc) returns nothing:
//
// This function registers the unit as an AI-controlled unit.
// ThreatUnits will automaticly attack the highest-in-threat attacker.
// When adding a ThreatUnit, its current position gets saved and be considered camp-position.
// It will always return to this position if pulled to far or on victory.
// Nearby units will be considered in the same camp group. Camp members will always retreat and attack together.
//
// call ZTS_AddPlayerUnit(unit pu) returns nothing:
//
// Units add by this way will generate threat on ThreatUnits.
// If the unit is not registered as a PlayerUnit, it will not be attacked by ThreatUnits.
//
// call ZTS_RemoveThreatUnit(unit npc) returns nothing:
//
// Removes a ThreatUnit from the system. The unit will no longer be controlled by the threat system.
// Also, the threat list for that unit will be cleared.
// Dead or removed units will automaticly be cleared. You need to add them again after revival/recreation.
//
// call ZTS_RemovePlayerUnit(unit pu) returns nothing:
//
// Removes a player unit from the system. The unit will no longer generate threat on ThreatUnits.
// The unit will also be instantly removed from all threat lists.
// If the unit was the last unit in combat with the same hostile camp, all units
// of that camp group will immediately return to their camp positions.
// You can use this, followed by AddPlayerUnit to that unit out of combat and reset all threat.
// Dead or removed units will automaticly be cleared. You need to add them again after revival/recreation.
//
// call ZTS_ModifyThreat(unit pu, unit npc, real amount, boolean add) returns nothing:
//
// Adds, sets or substracts threat from npc's threat list caused by pu.
// Set 'add' to true to add or substract amount from the current value.
// Set 'add' to false to set the new threat value to amount.
// To reduce threat, use negative amount values with add == true.
// Remember: If a unit has 0 threat, it is still considered in-combat -
// this also means, that adding "0" to the units threat causes them to attack!
//
// call ZTS_ApplyHealThreat(unit pu, unit ally, real amount, boolean add, boolean divide) returns nothing:
//
// Adds Healing Threat to all units, that have ally on threat-list
// This can be abused to apply global threat to a unit by passing the same unit to p and ally.
// Parameter divide = true means that the amount is split by the number of units attacking the target;
// for example if 3 units are currently attacking the targeted ally, it adds amount/3 threat from pu to all of them.
// Parameter divide = false means that every attacking unit gets 'amount' of threat applied.
// use add = false to set the amount of threat to 'amount', instead of increasing/decreasing it
// negative values are allowed in combination with 'add' to reduce threat.
// You can also use this with add = false and amount = 0 with pu = ally to set total threat generated back to zero for this unit.
//
//
// 3.2. Getter functions
//
// call ZTS_GetCombatState(unit PU) returns boolean:
//
// Returns the combat state of a player unit.
// Returns true, if the unit is registered and in combat.
// Returns false, if the unit is not registered or out of combat.
//
//
// call ZTS_GetThreatUnitPosition(unit NPC, unit PU) returns integer:
//
// Returns the position of unit PU in unit NPC's threat list
// Returns "0" if the unit was not found, NPC does not feature a threat list or in case of invalid input data
//
// call ZTS_GetThreatUnitAmount(unit NPC, unit PU) returns real:
//
// Returns the amount of threat unit PU has in unit NPC's threat list
// Returns "0" if the unit was not found, NPC does not feature a threat list or in case of invalid input data
//
// call ZTS_GetThreatSlotUnit(unit NPC, integer position) returns unit:
//
// Returns the unit in threat-slot position
// Returns null if the NPC does not feature a threat list, the number is too large
// or in case of invalid input data
//
// call ZTS_GetThreatSlotAmount(unit NPC, integer position) returns real:
//
// Returns the threat amount of the threat-slot position
// Returns "0" if the NPC does not feature a threat list, the number is too large
// or in case of invalid input data
//
// call ZTS_GetAttackers(unit U) returns group:
//
// If used on a ThreatUnit, this returns a group of all units in threat list;
// if used on a PlayerUnit, this returns a group of all units aggroed.
// Returns an empty group, in case of invalid input data or empty lists.
//
//
// 3.3. Advanced User features
//
// call ZTS_IsEvent() returns boolean
//
// When using "A unit is issued an order without target" or "A unit is issued a target order" events,
// this function returns true when the order was issued by the threat system.
// You can use this to setup your own spell-AI for units.
// Let's say you want the unit to cast Summon Water Elemental whenever the cooldown is ready:
// Just use the mentioned events and add:
// Custom script: if not ZTS_IsEvent() then
// Custom script: return
// Custom script: endif
// at the beginning of you trigger's actions and you're done.
// You can now issue the order to the triggering unit:
// Unit - Order (Triggering unit) to Human Archmage - Summon Water Elemental
// In combination with some of the Getter functions, you can trigger nice spell AI like this.
// NOTE: ZTS_IsEvent will only return true once(!) for every fired event, so if you need it again inside that trigger,
// make sure to save it to a variable.
//
//------------------------------------------------------------------------------------------------------//
library ZTS initializer InitThreatSystem
globals
private constant real UpdateIntervall = 0.5 //The intervall for issueing orders and performing AttackRange check. recommended value: 0.5
private constant real HelpRange = 400 //The range between units considered being in the same camp. If a unit of the same camp gets attacked, all others will help.
//Set CallForHelp to something lower in Gameplay Constants.
private constant real OrderReturnRange = 4000 //The range the unit's target can be away from the original camping position, before being ordered to return.
private constant real ReturnRange = 1500 //The range the unit can move away from the original camping position, before being ordered to return.
private constant real TimeToPort = 10 //This timer expires once a unit tries to return to its camping position.
//If it reaches 0 before reaching the camp position, the unit will be teleported immediately.
private constant boolean HealUnitsOnReturn = true //If this is true, returning units will be healed to 100% health.
// Do not edit below here!
//------------------------------------------------------------------------------------------------------//
private boolexpr BOOLEXPR = null
private constant timer Updater = CreateTimer()
private constant group NPCgroup = CreateGroup()
private constant hashtable NPClist = InitHashtable()
private constant hashtable PUlist = InitHashtable()
//temporary variables for enumerations and forgroups
private unit TSub = null
private unit TMod = null
private constant group TGroupSub = CreateGroup()
private unit THealer = null
private real THealthreat = 0
private boolean TBool = false
private constant group TGroupUpd = CreateGroup()
private group TGroupGet = null
private boolean EventBool = false
endglobals
public function IsEvent takes nothing returns boolean
if EventBool then
set EventBool = false
return true
endif
return false
endfunction
private function Pos2Key takes integer position returns integer //converts threat list position into hashtable childkey
return 8+(position*2)
endfunction
private function Key2Pos takes integer key returns integer //converts hashtable childkey into threat list position
return (key-8)/2
endfunction
public function GetCombatState takes unit u returns boolean
if GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD) then //unit dead or null
return false
elseif HaveSavedInteger(NPClist, GetHandleId(u), 0) then //unit is npc
return LoadInteger(NPClist, GetHandleId(u), 0) > 0
elseif HaveSavedHandle(PUlist, GetHandleId(u), 0) then //unit is player unit
return LoadInteger(PUlist, GetHandleId(u), 1) > 0
endif
return false
endfunction
public function GetThreatUnitPosition takes unit npc, unit pu returns integer
if GetUnitTypeId(npc) == 0 or IsUnitType(npc, UNIT_TYPE_DEAD) or GetUnitTypeId(pu) == 0 or IsUnitType(pu, UNIT_TYPE_DEAD) then //units dead or null
return 0
elseif not (HaveSavedInteger(NPClist, GetHandleId(npc), 0) and HaveSavedHandle(PUlist, GetHandleId(pu), 0)) then //units not added
return 0
elseif HaveSavedInteger(PUlist, GetHandleId(pu), GetHandleId(npc)) then
return LoadInteger(PUlist, GetHandleId(pu), GetHandleId(npc))
endif
return 0
endfunction
public function GetThreatUnitAmount takes unit npc, unit pu returns real
if GetUnitTypeId(npc) == 0 or IsUnitType(npc, UNIT_TYPE_DEAD) or GetUnitTypeId(pu) == 0 or IsUnitType(pu, UNIT_TYPE_DEAD) then //units dead or null
return 0.
elseif not (HaveSavedInteger(NPClist, GetHandleId(npc), 0) and HaveSavedHandle(PUlist, GetHandleId(pu), 0)) then //units not added
return 0.
elseif HaveSavedInteger(PUlist, GetHandleId(pu), GetHandleId(npc)) then
return LoadReal(NPClist, GetHandleId(npc), Pos2Key(LoadInteger(PUlist, GetHandleId(pu), GetHandleId(npc))))
endif
return 0.
endfunction
public function GetThreatSlotUnit takes unit npc, integer position returns unit
if GetUnitTypeId(npc) == 0 or IsUnitType(npc, UNIT_TYPE_DEAD) or position <= 0 then //unit dead or null or invalid slot
return null
elseif not HaveSavedInteger(NPClist, GetHandleId(npc), 0) then //unit not added
return null
elseif HaveSavedHandle(NPClist, GetHandleId(npc), Pos2Key(position)) then
return LoadUnitHandle(NPClist, GetHandleId(npc), Pos2Key(position))
endif
return null
endfunction
public function GetThreatSlotAmount takes unit npc, integer position returns real
if GetUnitTypeId(npc) == 0 or IsUnitType(npc, UNIT_TYPE_DEAD) or position <= 0 then //unit dead or null or invalid slot
return 0.
elseif not HaveSavedInteger(NPClist, GetHandleId(npc), 0) then //unit not added
return 0.
elseif HaveSavedReal(NPClist, GetHandleId(npc), Pos2Key(position)+1) then
return LoadReal(NPClist, GetHandleId(npc), Pos2Key(position)+1)
endif
return 0.
endfunction
private function GetAttackersSub takes nothing returns nothing
call GroupAddUnit(TGroupGet, GetEnumUnit())
endfunction
public function GetAttackers takes unit u returns group
local group g = CreateGroup()
local integer key = 10
local integer max
if GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD) then //unit dead or null
return g
endif
if HaveSavedInteger(NPClist, GetHandleId(u), 0) then //unit is npc
set max = Pos2Key(LoadInteger(NPClist, GetHandleId(u), 5))
loop
exitwhen key > max
call GroupAddUnit(g, LoadUnitHandle(NPClist, GetHandleId(u), key))
set key = key+2
endloop
elseif HaveSavedHandle(PUlist, GetHandleId(u), 0) then //unit is player unit
set TGroupGet = g
call ForGroup(LoadGroupHandle(PUlist, GetHandleId(u), 0), function GetAttackersSub)
set g = TGroupGet
set TGroupGet = null
endif
return g
endfunction
private function Swap takes integer npcID, integer key1, integer key2 returns nothing
local unit u = LoadUnitHandle(NPClist, npcID, key1)
local real r = LoadReal(NPClist, npcID, key1+1)
call SaveUnitHandle(NPClist, npcID, key1, LoadUnitHandle(NPClist, npcID, key2))
call SaveReal(NPClist, npcID, key1+1, LoadReal(NPClist, npcID, key2+1))
call SaveInteger(PUlist, GetHandleId(LoadUnitHandle(NPClist, npcID, key1)), npcID, Key2Pos(key1)) //update position list
call SaveUnitHandle(NPClist, npcID, key2, u)
call SaveReal(NPClist, npcID, key2+1, r)
call SaveInteger(PUlist, GetHandleId(u), npcID, Key2Pos(key2)) //update position list
set u = null
endfunction
private function CampThreat takes nothing returns nothing
local integer npcID = GetHandleId(GetEnumUnit())
local integer puID = GetHandleId(TMod)
local integer key
local integer listlength
if GetEnumUnit() == TSub then
return
elseif HaveSavedInteger(PUlist, puID, npcID) then //original pu unit already listed in EnumUnit's threat list
return
elseif LoadInteger(NPClist, npcID, 0) > 1 or IsUnitType(GetEnumUnit(), UNIT_TYPE_DEAD) then //do not add threat to dead or units that are status: returning
return
endif
set listlength = LoadInteger(NPClist, npcID, 5)+1
call SaveInteger(NPClist, npcID, 5, listlength) //add to list length of EnumUnit
set key = Pos2Key(listlength)
call SaveUnitHandle(NPClist, npcID, key, TMod) //add original pu unit to end of EnumUnit's threat list
call SaveReal(NPClist, npcID, key+1, 0)
call SaveInteger(PUlist, puID, npcID, listlength) //add EnumUnit to slot list
call GroupAddUnit(LoadGroupHandle(PUlist, puID, 0), GetEnumUnit()) //add EnumUnit to slot list group
call SaveInteger(PUlist, puID, 1, LoadInteger(PUlist, puID, 1)+1) //increase group size count
if LoadInteger(NPClist, npcID, 0) == 0 then
call SaveInteger(NPClist, npcID, 0, 1) //set unit status: combat
call GroupAddUnit(NPCgroup, GetEnumUnit()) //add the unit to incombat group
endif
endfunction
public function ModifyThreat takes unit pu, unit npc, real amount, boolean add returns nothing
local integer npcID = GetHandleId(npc)
local integer puID = GetHandleId(pu)
local integer key
local integer listlength
local integer i = 0
local real newamount
local real oldamount = 0
local boolean b = false
if not (HaveSavedInteger(NPClist, npcID, 0) and HaveSavedHandle(PUlist, puID, 0)) then //units not added
return
elseif IsUnitType(pu, UNIT_TYPE_DEAD) or IsUnitType(npc, UNIT_TYPE_DEAD) then //units dead
return
elseif GetUnitTypeId(pu) == 0 or GetUnitTypeId(npc) == 0 then //null units
return
elseif LoadInteger(NPClist, npcID, 0) > 1 then //do not add threat to units that are status: returning
return
endif
if not HaveSavedInteger(PUlist, puID, npcID) then //pu not listed in npc's threat list
set listlength = LoadInteger(NPClist, npcID, 5)+1
call SaveInteger(NPClist, npcID, 5, listlength) //add to list length of npc
set key = Pos2Key(listlength)
call SaveUnitHandle(NPClist, npcID, key, pu) //add pu to end of npc's threat list
call SaveInteger(PUlist, puID, npcID, listlength) //add npc to slot list
call GroupAddUnit(LoadGroupHandle(PUlist, puID, 0), npc) //add npc to slot list group
call SaveInteger(PUlist, puID, 1, LoadInteger(PUlist, puID, 1)+1) //increase group size count
if LoadInteger(NPClist, npcID, 0) == 0 then
call SaveInteger(NPClist, npcID, 0, 1) //set unit status: combat
call GroupAddUnit(NPCgroup, npc) //add the unit to incombat group
endif
set b = true
else
set key = Pos2Key(LoadInteger(PUlist, puID, npcID))
set oldamount = LoadReal(NPClist, npcID, key+1)
endif
if add then
set newamount = oldamount+amount
else
set newamount = amount
endif
if newamount < 0 then
set newamount = 0
endif
call SaveReal(NPClist, npcID, key+1, newamount)
if newamount > oldamount then //check lower keys
loop
if HaveSavedReal(NPClist, npcID, key-1-i) then
if LoadReal(NPClist, npcID, key-1-i) < newamount then //lower key amount is smaller
call Swap(npcID, key-2-i, key-i)
else
exitwhen true
endif
set i = i + 2
else
exitwhen true
endif
endloop
elseif newamount < oldamount then //check higher keys
loop
if HaveSavedReal(NPClist, npcID, key+3+i) then
if LoadReal(NPClist, npcID, key+3+i) > newamount then //upper key amount is larger
call Swap(npcID, key+2+i, key+i)
else
exitwhen true
endif
set i = i + 2
else
exitwhen true
endif
endloop
endif
if b then //set all units of the same camp to status: combat and apply 0 threat from pu to them
set TSub = npc
set TMod = pu
call ForGroup(LoadGroupHandle(NPClist, npcID, 4), function CampThreat)
endif
endfunction
public function AddPlayerUnit takes unit u returns nothing
local integer ID = GetHandleId(u)
if HaveSavedInteger(NPClist, ID, 0) or HaveSavedHandle(PUlist, ID, 0) then //unit already added
return
elseif GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD) then //unit dead or null
return
endif
call SaveGroupHandle(PUlist, ID, 0, CreateGroup()) //slot list group
call SaveInteger(PUlist, ID, 1, 0) //list group count
endfunction
private function AcquireTarget takes nothing returns nothing
local unit npc = GetTriggerUnit()
local unit pu
if GetEventTargetUnit() != null then
set pu = GetEventTargetUnit()
else
set pu = GetOrderTargetUnit()
endif
if IsUnitEnemy(pu, GetOwningPlayer(npc)) then
if LoadInteger(NPClist, GetHandleId(npc), 0) == 0 then //pull out of combat units only
call ModifyThreat(pu, npc, 0, true)
endif
endif
set pu = null
set npc = null
endfunction
private function FilterUnitsWithCampGroup takes nothing returns boolean
return HaveSavedHandle(NPClist, GetHandleId(GetFilterUnit()), 4) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD)
endfunction
public function AddThreatUnit takes unit u returns nothing
local integer ID = GetHandleId(u)
local group g = null
local trigger t = null
if HaveSavedInteger(NPClist, ID, 0) or HaveSavedHandle(PUlist, ID, 0) then //unit already added
return
elseif GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD) then //unit dead or null
return
endif
call SaveInteger(NPClist, ID, 0, 0) //status
call SaveReal(NPClist, ID, 1, GetUnitX(u)) //return X
call SaveReal(NPClist, ID, 2, GetUnitY(u)) //return Y
call SaveReal(NPClist, ID, 3, TimeToPort) //return countdown
call SaveInteger(NPClist, ID, 5, 0) //list length
set t = CreateTrigger()
call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_ACQUIRED_TARGET)
call TriggerAddAction(t, function AcquireTarget)
call SaveTriggerHandle(NPClist, ID, 6, t) //acquire target event trigger
call GroupEnumUnitsInRange(TGroupSub, GetUnitX(u), GetUnitY(u), HelpRange, Condition(function FilterUnitsWithCampGroup))
if FirstOfGroup(TGroupSub) != null then
set g = LoadGroupHandle(NPClist, GetHandleId(FirstOfGroup(TGroupSub)), 4)
else //no unit in range has a camp group assigned, so create a new one
set g = CreateGroup()
endif
call GroupAddUnit(g, u)
call SaveGroupHandle(NPClist, ID, 4, g) //camp group
set t = null
set g = null
endfunction
public function RemoveThreatUnit takes unit u returns nothing
local integer ID = GetHandleId(u)
local integer OtherID
local group g = null
local integer key = 10
if not HaveSavedInteger(NPClist, ID, 0) then //unit not added
return
elseif GetUnitTypeId(u) == 0 then
return
endif
if LoadInteger(NPClist, ID, 0) > 1 then //unit status is: returning
call IssueImmediateOrder(u, "stop")
call SetUnitInvulnerable(u, false)
if IsUnitPaused(u) then
call PauseUnit(u, false)
endif
endif
loop //remove the entry in the player unit's position list and list group and decrease list group count
if HaveSavedHandle(NPClist, ID, key) then
set OtherID = GetHandleId(LoadUnitHandle(NPClist, ID, key))
call RemoveSavedInteger(PUlist, OtherID, ID)
call GroupRemoveUnit(LoadGroupHandle(PUlist, OtherID, 0), u)
call SaveInteger(PUlist, OtherID, 1, LoadInteger(PUlist, OtherID, 1)-1)
set key = key+2
else //last entry reached
exitwhen true
endif
endloop
set g = LoadGroupHandle(NPClist, ID, 4)
call GroupRemoveUnit(g, u)
if FirstOfGroup(g) == null then //camp group is empty
call DestroyGroup(g)
endif
call DestroyTrigger(LoadTriggerHandle(NPClist, ID, 6))
call FlushChildHashtable(NPClist, ID)
if IsUnitInGroup(u, NPCgroup) then
call GroupRemoveUnit(NPCgroup, u) //remove unit from incombat group
endif
set g = null
endfunction
private function RemovePlayerUnitEntries takes nothing returns nothing
local integer ID = GetHandleId(TSub)
local integer OtherID = GetHandleId(GetEnumUnit())
local integer key = Pos2Key(LoadInteger(PUlist, ID, OtherID))
loop //remove the entry in u's threat list and fill the gap
if HaveSavedHandle(NPClist, OtherID, key+2) then //move up next entry
call SaveUnitHandle(NPClist, OtherID, key, LoadUnitHandle(NPClist, OtherID, key+2))
call SaveReal(NPClist, OtherID, key+1, LoadReal(NPClist, OtherID, key+3))
call SaveInteger(PUlist, GetHandleId(LoadUnitHandle(NPClist, OtherID, key)), OtherID, Key2Pos(key)) //update position in player unit list
set key = key+2
else //last entry reached
call RemoveSavedHandle(NPClist, OtherID, key)
call RemoveSavedReal(NPClist, OtherID, key+1)
call SaveInteger(NPClist, OtherID, 5, Key2Pos(key-2)) //decrease list length
exitwhen true
endif
endloop
endfunction
public function RemovePlayerUnit takes unit u returns nothing
local integer ID = GetHandleId(u)
if not HaveSavedHandle(PUlist, ID, 0) then //unit not added
return
elseif GetUnitTypeId(u) == 0 then
return
endif
set TSub = u
call ForGroup(LoadGroupHandle(PUlist, ID, 0), function RemovePlayerUnitEntries)
call DestroyGroup(LoadGroupHandle(PUlist, ID, 0))
call FlushChildHashtable(PUlist, ID)
endfunction
private function HealThreatSub takes nothing returns nothing
call ModifyThreat(THealer, GetEnumUnit(), THealthreat, TBool)
endfunction
public function ApplyHealThreat takes unit pu, unit ally, real amount, boolean add, boolean divide returns nothing
local integer puID = GetHandleId(pu)
local integer allyID = GetHandleId(ally)
if not (HaveSavedHandle(PUlist, puID, 0) and HaveSavedHandle(PUlist, allyID, 0)) then //units not added
return
elseif IsUnitType(pu, UNIT_TYPE_DEAD) or IsUnitType(ally, UNIT_TYPE_DEAD) then //units dead
return
elseif GetUnitTypeId(pu) == 0 or GetUnitTypeId(ally) == 0 then //null units
return
endif
if divide and LoadInteger(PUlist, allyID, 1) > 1 then
set THealthreat = amount/LoadInteger(PUlist, allyID, 1)
else
set THealthreat = amount
endif
set TBool = add
set THealer = pu
call ForGroup(LoadGroupHandle(PUlist, allyID, 0), function HealThreatSub)
endfunction
private function CampCommand takes nothing returns nothing
local unit u = GetEnumUnit()
local integer ID = GetHandleId(u)
local integer OtherID
local integer status = LoadInteger(NPClist, ID, 0)
local integer key = 10
if status == 1 then
call SaveInteger(NPClist, GetHandleId(u), 0, 2) //set status: returning
loop //remove the entry in the player unit's position list and list group and decrease list group count
if HaveSavedHandle(NPClist, ID, key) then
set OtherID = GetHandleId(LoadUnitHandle(NPClist, ID, key))
call RemoveSavedInteger(PUlist, OtherID, ID)
call GroupRemoveUnit(LoadGroupHandle(PUlist, OtherID, 0), u)
call SaveInteger(PUlist, OtherID, 1, LoadInteger(PUlist, OtherID, 1)-1)
call RemoveSavedHandle(NPClist, ID, key)
call RemoveSavedReal(NPClist, ID, key+1)
set key = key+2
else //last entry reached
exitwhen true
endif
endloop
call SaveInteger(NPClist, ID, 5, 0) //also set list length to zero
call IssueImmediateOrder(u, "stop") //cancels even spellcast with casting time
call IssuePointOrder(u, "move", LoadReal(NPClist, ID, 1), LoadReal(NPClist, ID, 2))
call SaveReal(NPClist, ID, 3, TimeToPort)
call SetUnitInvulnerable(u, true)
if HealUnitsOnReturn then
call SetUnitState(u, UNIT_STATE_LIFE, GetUnitState(u, UNIT_STATE_MAX_LIFE))
call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA))
endif
elseif status == 3 then
call SaveInteger(NPClist, GetHandleId(u), 0, 0) //set status: out of combat
call SetUnitInvulnerable(u, false)
call PauseUnit(u, false)
if HealUnitsOnReturn then
call SetUnitState(u, UNIT_STATE_LIFE, GetUnitState(u, UNIT_STATE_MAX_LIFE))
call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA))
endif
call GroupRemoveUnit(NPCgroup, u) //remove from combat group
endif
set u = null
endfunction
private function CampStatus takes nothing returns nothing
if LoadInteger(NPClist, GetHandleId(GetEnumUnit()), 0) != 3 then
set TBool = false
endif
endfunction
private function IssueOrder takes nothing returns nothing
local unit npc = GetEnumUnit()
local integer npcID = GetHandleId(npc)
local integer status = LoadInteger(NPClist, npcID, 0)
local integer i = 0
local boolean b = true
local unit target = null
if status == 1 then //unit in combat
if IsUnitInRangeXY(npc, LoadReal(NPClist, npcID, 1), LoadReal(NPClist, npcID, 2), ReturnRange) and HaveSavedHandle(NPClist, npcID, 10) then
set target = LoadUnitHandle(NPClist, npcID, 10)
if IsUnitInRangeXY(target, LoadReal(NPClist, npcID, 1), LoadReal(NPClist, npcID, 2), OrderReturnRange) then
if GetUnitCurrentOrder(npc) == 851983 or GetUnitCurrentOrder(npc) == 0 or GetUnitCurrentOrder(npc) == 851971 then //attack order or no order or smart order
set EventBool = true
call IssueTargetOrder(npc, "smart", target)
set EventBool = false
endif
else //target of unit to far away from camp position
call ForGroup(LoadGroupHandle(NPClist, npcID, 4), function CampCommand) //set camp returning
endif
else //unit left return range or killed all player units
call ForGroup(LoadGroupHandle(NPClist, npcID, 4), function CampCommand) //set camp returning
endif
elseif status == 2 then //unit is returning
if LoadReal(NPClist, npcID, 3) > 0 then
if not IsUnitInRangeXY(npc, LoadReal(NPClist, npcID, 1), LoadReal(NPClist, npcID, 2), 35) then
call IssuePointOrder(npc, "move", LoadReal(NPClist, npcID, 1), LoadReal(NPClist, npcID, 2))
call SaveReal(NPClist, npcID, 3, LoadReal(NPClist, npcID, 3) - UpdateIntervall)
call SetUnitInvulnerable(npc, true)
else //unit within close range to camp position
if GetUnitCurrentOrder(npc) == 851986 then //move order
call SaveReal(NPClist, npcID, 3, LoadReal(NPClist, npcID, 3) - UpdateIntervall)
call SetUnitInvulnerable(npc, true)
else //Something blocks the exact spot or the unit has arrived
call SaveInteger(NPClist, npcID, 0, 3) //set status: returned
set TBool = true
call ForGroup(LoadGroupHandle(NPClist, npcID, 4), function CampStatus)
if TBool then //all units in camp have status: returned to camp position
call ForGroup(LoadGroupHandle(NPClist, npcID, 4), function CampCommand) //set camp ooc
else
call PauseUnit(npc, true) //make sure it doesn't move or attack when invulnerable
endif
endif
endif
else //counter expired - perform instant teleport
call SetUnitPosition(npc, LoadReal(NPClist, npcID, 1), LoadReal(NPClist, npcID, 2))
call SaveInteger(NPClist, npcID, 0, 3) //set status: returned
set TBool = true
call ForGroup(LoadGroupHandle(NPClist, npcID, 4), function CampStatus)
if TBool then //all units in camp have status: returned to camp position
call ForGroup(LoadGroupHandle(NPClist, npcID, 4), function CampCommand) //set camp ooc
else
call PauseUnit(npc, true) //make sure it doesn't move or attack when invulnerable
endif
endif
endif
set npc = null
set target = null
endfunction
private function Update takes nothing returns nothing
call ForGroup(NPCgroup, function IssueOrder) //issues orders to all units in combat
endfunction
private function rettrue takes nothing returns boolean
return true
endfunction
private function RemovedUnitFound takes unit u returns nothing
if HaveSavedInteger(NPClist, GetHandleId(u), 0) then
call RemoveThreatUnit(u)
endif
if HaveSavedHandle(PUlist, GetHandleId(u), 0) then
call RemovePlayerUnit(u)
endif
endfunction
hook RemoveUnit RemovedUnitFound
private function OnDeath takes nothing returns nothing
if HaveSavedInteger(NPClist, GetHandleId(GetTriggerUnit()), 0) then
call RemoveThreatUnit(GetTriggerUnit())
endif
if HaveSavedHandle(PUlist, GetHandleId(GetTriggerUnit()), 0) then
call RemovePlayerUnit(GetTriggerUnit())
endif
endfunction
private function InitThreatSystem takes nothing returns nothing
local trigger t = CreateTrigger()
local integer index = 0
set BOOLEXPR=Condition(function rettrue) //prevent booleanexpressions from leaking
call TimerStart(Updater, UpdateIntervall, true, function Update)
loop
call TriggerRegisterPlayerUnitEvent(t, Player(index), EVENT_PLAYER_UNIT_DEATH, BOOLEXPR)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddAction(t, function OnDeath)
set t = null
endfunction
endlibrary