1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Head to the 33rd Modeling Contest Poll and drink to your heart's desire.
    Dismiss Notice
  3. Choose your means of doom in the 17th Mini Mapping Contest Poll.
    Dismiss Notice
  4. A slave to two rhythms, the 22nd Terraining Contest is here.
    Dismiss Notice
  5. The heavens smile on the old faithful. The 16th Techtree Contest has begun.
    Dismiss Notice
  6. The die is cast - the 6th Melee Mapping Contest results have been announced. Onward to the Hive Cup!
    Dismiss Notice
  7. The glory of the 20th Icon Contest is yours for the taking!
    Dismiss Notice
  8. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Trigger Viewer

ZTSThreatv2_7.w3x
Variables
System
ZTS
Debug
Debug Board
Other
DamageDetect
Init
AI Event Demo 1
AI Event Demo 2
Stress Test
Spells
Heal
Water Elemental
Storm Bolt
Fade
Beast Summon

		
Name Type Is Array Initial Value
//TESH.scrollpos=741
//TESH.alwaysfold=0
//------------------------------------------------------------------------------------------------------//
//  ZTS - ZWIEBELCHEN'S THREAT SYSTEM        v. 2.6                                                     //
//------------------------------------------------------------------------------------------------------//        
//------------------------------------------------------------------------------------------------------//
//                                                                                                      //
//  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:
//      (http://www.wc3c.net/showthread.php?t=100618)
//      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, boolean includeCombatCamps) 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.
//          If includeCombatCamps is true, the unit will be added to already fighting camps. If it is false, the unit will
//          create its own camp group, if it can't find any non-fighting units nearby.
//          This should be false in most cases, but it can be useful when you have bosses that summon units infight, so that
//          the summons will be added to the bossfight correctly instead of getting their own seperate group.
//
//      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 U) returns boolean:
//
//          Returns the combat state of a player or npc 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_GetCombatTime(unit NPC) returns boolean:
//
//          Returns the incombat time of the npc.
//          Does not work for player units.
//          Returns "0" if the unit is not in combat or currently returning to camp position.
//
//      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 integer TState = 0
    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 GetCombatTime takes unit u returns real
    if GetUnitTypeId(u) == 0 or IsUnitType(u, UNIT_TYPE_DEAD) then //unit dead or null
        return 0.
    elseif HaveSavedInteger(NPClist, GetHandleId(u), 0) then //unit is npc
        if LoadInteger(NPClist, GetHandleId(u), 0) == 1 then //only return a time when the unit is in combat
            return LoadReal(NPClist, GetHandleId(u), 3)
        endif
    endif
    return 0.
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)))+1)
    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 boolean
    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
    return false
endfunction

private function FilterUnitsWithCampGroup takes nothing returns boolean
    return HaveSavedHandle(NPClist, GetHandleId(GetFilterUnit()), 4) and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false and LoadInteger(NPClist, GetHandleId(GetFilterUnit()), 0) <= TState
endfunction

public function AddThreatUnit takes unit u, boolean includeCombatCamps returns nothing
    local integer ID = GetHandleId(u)
    local group g = null
    local trigger t = null
    local unit other = null
    local integer otherID = 0
    local unit temp = null
    local integer i = 0
    local integer listlength = 0
    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, 0) //return countdown and incombat timer
    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 TriggerAddCondition(t, Condition(function AcquireTarget))
    call SaveTriggerHandle(NPClist, ID, 6, t) //acquire target event trigger
    if includeCombatCamps then
        set TState = 1
    else
        set TState = 0
    endif
    call GroupEnumUnitsInRange(TGroupSub, GetUnitX(u), GetUnitY(u), HelpRange, Condition(function FilterUnitsWithCampGroup))
    set other = FirstOfGroup(TGroupSub)
    if other != null then
        set otherID = GetHandleId(other)
        set g = LoadGroupHandle(NPClist, otherID, 4)
        if includeCombatCamps then
            //don't forget to inherit the camp unit's threat list...
            if LoadInteger(NPClist, otherID, 0) == 1 then //...but only if filtered unit is actually infight
                set listlength = LoadInteger(NPClist, otherID, 5)
                call SaveInteger(NPClist, ID, 5, listlength) //copy list length
                loop //copy all list entries as the newly added unit has an empty list and will cause the camp to reset almost instantly
                    set i = i + 1
                    exitwhen i > listlength
                    set temp = LoadUnitHandle(NPClist, otherID, Pos2Key(i))
                    call SaveUnitHandle(NPClist, ID, Pos2Key(i), temp)
                    call SaveReal(NPClist, ID, Pos2Key(i)+1, 0)
                    call SaveInteger(PUlist, GetHandleId(temp), ID, i) //assign the threat position to the player unit's reference list
                    call GroupAddUnit(LoadGroupHandle(PUlist, GetHandleId(temp), 0), u) //add the unit to the player unit's threat group
                    call SaveInteger(PUlist, GetHandleId(temp), 1, LoadInteger(PUlist, GetHandleId(temp), 1)+1) //increase group size count
                endloop
                call SaveInteger(NPClist, ID, 0, 1) //set unit status: combat
                call GroupAddUnit(NPCgroup, u) //add the unit to incombat group
                set temp = null
            endif
        endif
    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
    set other = 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 SaveReal(NPClist, GetHandleId(u), 3, 0.) //reset incombat and return timer
        call SetUnitInvulnerable(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
       
        call PauseUnit(u, false)
        call IssueImmediateOrder(u, "stop")
    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
        call SaveReal(NPClist, npcID, 3, LoadReal(NPClist, npcID, 3) + UpdateIntervall) //increase the combat timer
        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
This trigger is only for debugging.

It displays handles used and the threat list of the selected foe.
//TESH.scrollpos=12
//TESH.alwaysfold=0
globals
    leaderboard udg_HandleBoard
    group DebugGroup = CreateGroup()
endglobals

function HandleCounter_L2I takes location P returns integer
    return GetHandleId(P)
endfunction

function HandleCounter_Update takes nothing returns nothing
        local unit U
        local integer i = 0
        local integer j = 1
        local integer id
        local location array P
        local real result=0
        loop
                exitwhen i >= 50
                set i = i + 1
                set P[i] = Location(0,0)
                set id = HandleCounter_L2I(P[i])
                set result = result + (id-0x100000)
        endloop
        set result = result/i-i/2
        loop
                call RemoveLocation(P[i])
                set P[i] = null
                exitwhen i <= 1
                set i = i - 1
        endloop
       
        call GroupEnumUnitsSelected(DebugGroup, Player(0), null)
        set U = FirstOfGroup(DebugGroup)
        call LeaderboardSetItemValue(udg_HandleBoard, 0, R2I(result))
        call LeaderboardSetItemLabel(udg_HandleBoard, 1, "Combat State")
        if ZTS_GetCombatState(U) then
            call LeaderboardSetItemValue(udg_HandleBoard, 1, 1)
        else
            call LeaderboardSetItemValue(udg_HandleBoard, 1, 0)
        endif
        if GetOwningPlayer(U) == Player(PLAYER_NEUTRAL_AGGRESSIVE) then
            loop
                exitwhen j > 7
                if ZTS_GetThreatSlotUnit(U, j) != null then
                    call LeaderboardSetItemLabel(udg_HandleBoard, 1+j, "Slot "+I2S(j)+" - "+I2S(GetHandleId(ZTS_GetThreatSlotUnit(U, j)))+":")
                    call LeaderboardSetItemValue(udg_HandleBoard, 1+j, R2I(ZTS_GetThreatSlotAmount(U, j)))
                else
                    call LeaderboardSetItemLabel(udg_HandleBoard, 1+j, "Slot "+I2S(j)+" - "+"<empty>"+":")
                    call LeaderboardSetItemValue(udg_HandleBoard, 1+j, 0)
                endif
                set j = j + 1
            endloop
        endif
        set U = null
endfunction

function HandleCounter_Actions takes nothing returns nothing
        set udg_HandleBoard = CreateLeaderboard()
        call LeaderboardSetLabel(udg_HandleBoard, "threat-table for selected:")
        call PlayerSetLeaderboard(GetLocalPlayer(),udg_HandleBoard)
        call LeaderboardDisplay(udg_HandleBoard,true)
        call LeaderboardAddItem(udg_HandleBoard,"(Debug) Handles",0,Player(0))
        call LeaderboardAddItem(udg_HandleBoard, "Combat Status", 0, Player(1))
        call LeaderboardAddItem(udg_HandleBoard, "Slot 1", 0, Player(2))
        call LeaderboardAddItem(udg_HandleBoard, "Slot 2", 0, Player(3))
        call LeaderboardAddItem(udg_HandleBoard, "Slot 3", 0, Player(4))
        call LeaderboardAddItem(udg_HandleBoard, "Slot 4", 0, Player(5))
        call LeaderboardAddItem(udg_HandleBoard, "Slot 5", 0, Player(6))
        call LeaderboardAddItem(udg_HandleBoard, "Slot 6", 0, Player(7))
        call LeaderboardAddItem(udg_HandleBoard, "Slot 7", 0, Player(8))
        call LeaderboardSetSizeByItemCount(udg_HandleBoard,8)
        call HandleCounter_Update()
        call TimerStart(GetExpiredTimer(),0.05,true,function HandleCounter_Update)
endfunction

//===========================================================================
function InitTrig_Debug_Board takes nothing returns nothing
        call TimerStart(CreateTimer(),0,false,function HandleCounter_Actions)
endfunction
DamageDetect
  Events
  Conditions
  Actions
    -------- A simple damage detection function used to add threat in amount of damage dealt --------
    Custom script: call ZTS_ModifyThreat(GetEventDamageSource(), GetTriggerUnit(), GetEventDamage(), true)
Init
  Events
    Map initialization
  Conditions
  Actions
    Player - Disable sleeping for all creeps
    -------- Add units to damage detection trigger --------
    Unit Group - Pick every unit in (Units owned by Neutral Hostile.) and do (Add to DamageDetect <gen> the event ((Picked unit) Takes damage))
    -------- Setting up of all the participating units in the threat system --------
    -------- Units where AI shall be taken over by the script have to be registered with AddThreatUnit --------
    -------- Units that shall appear on ThreatUnit's threat lists have to be registered with AddPlayerUnit --------
    Unit Group - Pick every unit in (Units owned by Player 1 (Red).) and do (Custom script: call ZTS_AddPlayerUnit( GetEnumUnit() ))
    Unit Group - Pick every unit in (Units owned by Neutral Hostile.) and do (Custom script: call ZTS_AddThreatUnit( GetEnumUnit(), false ))
AI Event Demo 1
  Events
    Unit - A unit owned by Neutral Hostile.Is issued an order targeting an object
    Unit - A unit owned by Neutral Hostile.Is issued an order with no target
  Conditions
    (Unit-type of (Triggering unit)) Equal to Kobold Geomancer
  Actions
    -------- Use ZTS_IsEvent in combination with Order Events to issue special orders (for example spells) --------
    -------- Ordinary Wc3 spells will be used randomly be creeps even with the threat system active --------
    -------- so you only need this when you want to manually order certain things --------
    Custom script: if not ZTS_IsEvent() then
    Custom script: return
    Custom script: endif
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        (Random integer number between 1 and 10) Equal to 5
      Then - Actions
        Floating Text - Create floating text that reads Ooops! above (Triggering unit) with Z offset 100.00, using font size 10, color (100%, 100%, 100%), and 0% transparency
        Floating Text - Change (Last created floating text): Disable permanence
        Floating Text - Change the lifespan of (Last created floating text) to 3.00 seconds
        Unit - Explode (Triggering unit).
      Else - Actions
AI Event Demo 2
  Events
    Unit - A unit owned by Neutral Hostile.Is issued an order targeting an object
    Unit - A unit owned by Neutral Hostile.Is issued an order with no target
  Conditions
    (Unit-type of (Triggering unit)) Equal to Fire Revenant
  Actions
    -------- Use ZTS_IsEvent in combination with Order Events to issue special orders (for example spells) --------
    -------- Ordinary Wc3 spells will be used randomly be creeps even with the threat system active --------
    -------- so you only need this when you want to manually order certain things --------
    Custom script: if not ZTS_IsEvent() then
    Custom script: return
    Custom script: endif
    Unit - Order (Triggering unit) to Orc Far Seer - Feral Spirit.
Stress Test
  Events
    Player - Player 1 (Red) types a chat message containing -stress (stringnoformat) as An exact match
  Conditions
  Actions
    Camera - Pan camera for Player 1 (Red) to (Center of Region_001 <gen>) over 1.00 seconds
    Unit - Create 100 Skeleton Archer for Player 1 (Red) at (Center of Region_002 <gen>) facing (Center of (Playable map area))
    Unit - Create 100 Skeleton Archer for Neutral Hostile at (Center of Region_001 <gen>) facing (Center of (Playable map area))
    Unit Group - Pick every unit in (Units owned by Neutral Hostile.) and do (Add to DamageDetect <gen> the event ((Picked unit) Takes damage))
    Unit Group - Pick every unit in (Units owned by Player 1 (Red).) and do (Custom script: call ZTS_AddPlayerUnit( GetEnumUnit() ))
    Unit Group - Pick every unit in (Units owned by Neutral Hostile.) and do (Custom script: call ZTS_AddThreatUnit( GetEnumUnit(), false ))
    Trigger - Turn off (This trigger)
Heal
  Events
    Unit - A unit owned by Player 1 (Red).Starts the effect of an ability
  Conditions
    (Ability being cast) Equal to Holy Light
  Actions
    -------- Apply 100 threat*SpellLevel to all units, that have the target on threat-list and divide through their number --------
    -------- i.e. 3 units are currently attacking the healed target --> You apply 33 threat to each attacker on level 1 --------
    Custom script: call ZTS_ApplyHealThreat( GetTriggerUnit(), GetSpellTargetUnit(), 100*GetUnitAbilityLevel(GetTriggerUnit(), GetSpellAbilityId()), true, true)
Water Elemental
  Events
    Unit - A unit owned by Player 1 (Red).Spawns a summoned unit
  Conditions
  Actions
    -------- Apply 100 threat to all units, that have the Archmage on threat-list via abusing the HealThreat function --------
    Custom script: call ZTS_ApplyHealThreat( GetSummoningUnit(), GetSummoningUnit(), 200, true, false)
    -------- Don't forget to also add the spawned unit, or it won't appear on threat lists --------
    Custom script: call ZTS_AddPlayerUnit( GetSummonedUnit() )
Storm Bolt
  Events
    Unit - A unit owned by Player 1 (Red).Starts the effect of an ability
  Conditions
    (Ability being cast) Equal to Storm Bolt
  Actions
    -------- Remove 100*SpellLevel threat from the unit for the caster --------
    -------- As the damage detection applies 1 threat for each damage point, this equals to no threat produced --------
    Custom script: call ZTS_ModifyThreat( GetTriggerUnit(), GetSpellTargetUnit(), -100*GetUnitAbilityLevel(GetTriggerUnit(), GetSpellAbilityId()), true)
Fade
  Events
    Unit - A unit owned by Player 1 (Red).Starts the effect of an ability
  Conditions
    (Ability being cast) Equal to Fade
  Actions
    -------- Remove the unit from the threat system and add it again for instant threat wipe --------
    Custom script: call ZTS_RemovePlayerUnit( GetTriggerUnit() )
    Custom script: call ZTS_AddPlayerUnit( GetTriggerUnit() )
Beast Summon
  Events
    Unit - A unit Spawns a summoned unit
  Conditions
    (Owner of (Summoning unit)) Equal to Neutral Hostile
  Actions
    Trigger - Add to DamageDetect <gen> the event ((Summoned unit) Takes damage)
    Custom script: call ZTS_AddThreatUnit( GetSummonedUnit(), true )