• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Zwiebelchen's Threat System 2.7

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.

If you ever played World of Warcraft (as a mapper, you should have), chances are you know what a threat system does.
And this is an almost perfect replica of it.


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.7
- Fixed a bug that sometimes did not properly render units in-combat if pulled again right after returning to camp position

2.6
- Added a new function: ZTS_GetCombatTime, which returns the time an npc unit is currently in combat; returns 0 if the unit is currently out of combat or returning to camp position; does not work on player units

2.5
- Added a new boolean to the ZTS_AddThreatUnit command that determines wether the added unit shall be added to already fighting Creep Camps or only to non-fighting Creep Camps - this is interesting when you create encounters that summon units as you usually don't want those summoned units to create their own Creep Camp but want them to be added to the Boss' Creep Camp
- Fixed a small bug that sometimes caused linked Creep Camps

2.4
- Fixed a bug sometimes rendering units permanently invulnerable, when registering a unit in close range to currently retreating units

2.3
- Changed TriggerActions of dynamic triggers to TriggerConditions, to avoid nasty action leak
- Fixed GetThreatUnitAmount (did not return the correct value)

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

Keywords:
threat, aggro, wow, world of warcraft
Contents

Threat System v2.7 (Map)

Reviews
17:56, 17th Jan 2010 TriggerHappy: Very good, and you know what my suggestions are. This can be approved in it's current state, though.

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Actually nevermind T_T still doesn't work, does your system not work with Neutral hostile? I have a trigger set to call ZTS_ApplyHealThreat(GetEventDamageSource(),GetEventDamageSource(), 200, false, true) on attack and it no work :/
All neutral hostile units need to be registered to the system. ApplyHealThreat only works on player units registered to the system.
The system works fine with neutral hostile.

Also another strange thing, is my trigger doesn't work on units that were in game at init, until they have died and are respawned by my system...mind = blown or are spawned by a trigger, which is weird because how are they added to threat table units after respawn or are they xD?
This is because dead units get cleared from the system. You need to register those units again upon revival or when created.
 
Level 3
Joined
Jan 10, 2012
Messages
31
Ok, so I'm just going to post my triggers, since nothing else is working...
Trigger 1: 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 (Trigger - Add to DamageDetect <gen> the event (Unit - (Picked unit) Takes damage))
Unit Group - Pick every unit in (Units owned by Player 8 (Pink)) and do (Trigger - Add to DamageDetect <gen> the event (Unit - (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 9 (Gray)) and do (Custom script: call ZTS_AddPlayerUnit( GetEnumUnit() ))
Unit Group - Pick every unit in (Units owned by Player 11 (Dark Green)) 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 ))
Unit Group - Pick every unit in (Units owned by Player 8 (Pink)) and do (Custom script: call ZTS_AddThreatUnit( GetEnumUnit(), false ))



Trigger 2: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)


Trigger 3:Will of the Archpaladin
Events
Game - DamageEvent becomes Equal to 1.00
Conditions
(Level of Will of the Archpaladin (Paladin) Evasion for DamageEventSource) Equal to 1
Actions
Custom script: call ZTS_ApplyHealThreat(GetEventDamageSource(),GetEventDamageSource(), 400, false, true)
Game - Display to (All players) the text: Will of the Archpal...


Ok, so why does your system not work for me? units that are already made before the game is launched do NOT work, until they are re-spawned then they do if they are player pink, not Neutral Hostile, which is opposite of what you are saying.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
From just your triggers, I can not find a mistake. Post more, as there is definitely no bug with the system (I have used it for years now without any issues)!

Remember that the units owned by pink and neutral hostile will only react to gray and dark green units this way. The threat system will simply ignore all other players.
Also remember that any units of gray and dark green MUST be registered again after revival/creation/recreation.
Same goes for pink and neutral hostile.
 
Level 5
Joined
Oct 8, 2010
Messages
134
Heya Zwiebelchen,
I was wondering the following things:

1. If I make a skill that applies a buff to the caster, what type of script call do I need to generate threat for the caster (static amount) to all nearby enemies?

2. Is it possible that edit the threat generation rate for specific hero's
Like a tanking class generates 125% threat while others have 100%?
This is just for their basic attacks.

3. Is it possible to make an effect (exclamation mark) if the target changes target.
so players have a visual warning if they pull a enemy.

4. this is the script call you used for the basic heal testing skill
  • call ZTS_ApplyHealThreat( GetTriggerUnit(), GetSpellTargetUnit(), 100*GetUnitAbilityLevel(GetTriggerUnit(), GetSpellAbilityId()), true, true)
I use stat based skills that do not lvl, so would it then be
GetHeroAtributeStr ?
if not what is the proper syntax?

I really like the way your system functions, and I am sadly new to Jass and ruby, having only done the basic python isnt helping much.

So could you assist me.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Heya Zwiebelchen,
I was wondering the following things:

1. If I make a skill that applies a buff to the caster, what type of script call do I need to generate threat for the caster (static amount) to all nearby enemies?
You can simply enumerate all enemies within X range and then use ZTS_ModifyThreat on each one.

You can also use the ZTS_ApplyHealThreat, however, this mechanic will split the threat over all units that are currently aggroed and is not based on range - it will use all currently aggroed units. Which means that if a spell creates 500 threat and there's 5 units around, all units will get a total of 100 threat applied.

You probably want the first solution.

2. Is it possible that edit the threat generation rate for specific hero's
Like a tanking class generates 125% threat while others have 100%?
This is just for their basic attacks.
Yes, just use a damage detection system and if the hero unit type is XYZ, apply 1.25 points of threat per damage instead of 1 point of threat per damage.

3. Is it possible to make an effect (exclamation mark) if the target changes target.
so players have a visual warning if they pull a enemy.
You can basicly compare if the threat slot unit at slot 1 before and after applying threat is not the same. If it is, then do nothing, if it isn't, display the exclamation mark.

I'd say write a small snippet for this, so you can use a one-liner when you need to apply threat.

4. this is the script call you used for the basic heal testing skill
  • call ZTS_ApplyHealThreat( GetTriggerUnit(), GetSpellTargetUnit(), 100*GetUnitAbilityLevel(GetTriggerUnit(), GetSpellAbilityId()), true, true)
I use stat based skills that do not lvl, so would it then be
GetHeroAtributeStr ?
if not what is the proper syntax?
You can use GetHeroStr(GetTriggerUnit()).
 
Level 30
Joined
Jul 23, 2009
Messages
1,029
Edit: I fixed the problem I had.

However I noticed something else. If I run away from a unit so that they start running back to their camp position and I then beat them to the destination so that they start attacking me right when they have returned they never count as out of combat and the threat system does no longer work for that unit. This means the unit will chase me indefinately but if I attack him he will then return to his camp position and once again count in the threat system. I can't find a logic reason to why this happens, does anyone know what might be the cause here?
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Edit: I fixed the problem I had.

However I noticed something else. If I run away from a unit so that they start running back to their camp position and I then beat them to the destination so that they start attacking me right when they have returned they never count as out of combat and the threat system does no longer work for that unit. This means the unit will chase me indefinately but if I attack him he will then return to his camp position and once again count in the threat system. I can't find a logic reason to why this happens, does anyone know what might be the cause here?
All I can say is that it can't be a bug with the actual system, as I have been using it for years and never had this problem. For reference, see the demo map. It works properly there.

I think there is some other script interfering here. Is there a script running in your map that might unregister the units that leave combat? Remember: once added to the system, you don't need to remove those units again; there is no periodic trigger running for out-of-combat units, so no performance hit at all!
 
Level 30
Joined
Jul 23, 2009
Messages
1,029
Thanks for the reply. It does work like it should in the testmap like you said but I tried deleting all triggers in my map except the ZTS and one trigger to add the units to the threat list as showed in the picture.

Even in this state this problem occurs in my map and I'm really not sure what I have set wrong :/
I tried making a debug trigger to get the combatstate of a unit and show it over their head every 0.5 seconds. When the unit was standing in its camp position it was registered as out of combat as it should be. When I aggro the unit it says it's in combat as it should be. But when I get too far away he returns to his camping position and then if I aggro it right when it comes back it says the unit is still out of combat even though it is hitting my unit. But if I modify the threat of my unit towards the enemy unit it will be registered in combat.

EDIT: I noticed this bug exists in your test map too. But it only occurs if I do not deal damage to the enemy unit and if the enemy unit is the only one in the camp.

EDIT Again: I noticed this bug even exists in your map Gaias Retaliation. I tried aggroing a wolf without damaging it. I then lured it away so that it starts returning. I then beat it back to its camp location instantly aggroing it once it returned. I made sure to not deal any damage to it and then I could lure it away out of its return range. This is not a common occurence but I know one instance that may cause this: One player aggros a creep and runs away so it starts returning. Another player is following the first player but is so far behind that it aggros the creep just as it had returned from chasing player one. Now the second player is followed by the creep until it attacks the creep.

attachment.php
 

Attachments

  • ShowPic.png
    ShowPic.png
    36.6 KB · Views: 242
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I'll investigate that. I have an idea what could be the cause for that... I think the event that usually makes the unit enter combat fires too early.

--> unit returns and turns invulnerable; is still considered retreating and not truly out of combat
--> unit gets unpaused
--> player enters range again; WC3 AI issues an order to it
--> event that renders unit in-combat fires, but doesn't do anything as it still hasn't properly left combat from the previous pull
--> unit is officially out of combat again now after the initial pull, even though it's already attacking

If that is indeed the problem, it's an easy fix; I'll upload a fixed version in several hours.
 
Level 30
Joined
Jul 23, 2009
Messages
1,029
I'll investigate that. I have an idea what could be the cause for that... I think the event that usually makes the unit enter combat fires too early.

--> unit returns and turns invulnerable; is still considered retreating and not truly out of combat
--> unit gets unpaused
--> player enters range again; WC3 AI issues an order to it
--> event that renders unit in-combat fires, but doesn't do anything as it still hasn't properly left combat from the previous pull
--> unit is officially out of combat again now after the initial pull, even though it's already attacking

If that is indeed the problem, it's an easy fix; I'll upload a fixed version in several hours.

Sounds just about right. Chill chill.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Thanks for the above contribution. I pretty much already use it in combination with UnitIndexer for my purposes, but I never made that build public.

At some point in the future, I might completely rewrite this to use a cleaner struct syntax approach. While the current implementation is very effective from a performance point-of-view, it couldn't hurt updating it for better readability.
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
  • Events
    • Unit - A unit owned by Neutral feindlich Erhält einen auf ein Objekt zielenden Befehl
    • Unit - A unit owned by Neutral feindlich Erhält einen Befehl ohne Ziel
  • Conditions
    • (Unit-type of (Triggering unit)) equal to Snowpaw
  • 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-Troll-Berserker - 'Berserk'
it works just like in the test map, the event fires once the unit enters combat, but then it waits for a new order (which only happens if someone else takes over aggro)
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Maybe its something with the spell you're using. Can you post what it does?

What you can do to troubleshoot is trying to periodically get the current order of the unit after issueing the berserk order once. Does it go back to none, "smart" or "attack"? If not, then the system will not reissue any orders. It will only apply new orders if the unit has no order, a smart or an attack order. Even move orders will interrupt any automatic orders by ZTS.
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
It is just a basic spell that i used for testing. I think the first one was 'spawn ghost wolves' and the second 'berserk'. The unit immediately goes back to smart order after casting an ability. The thing is, it has the same result in your test map. There are 2 units that spawn ghostboars.(this is the example trigger for AI), but they also cast it only once when entering combat (or, as i said, when changing aggro target). Maybe it has sth to do with the new sharpcraft editor?
 
Level 2
Joined
Jun 20, 2009
Messages
15
I love this system. But I seem to have run into a problem.
My Neutral Hostile units won't cast their standard spells when registered in the Threat-System. If I try to remove them in-game, they will cast their spells regularly again.

From what I understand, they are still supposed to be casting regular spells when, even when they are controlled by your system. Do you have any idea what could be causing this?
 
Level 2
Joined
Jun 20, 2009
Messages
15
Usually I don't have any problem making Neutral Hostile cast spells based on abilities like Blizzard (Neutral Hostile), Frostbolt, Roar, etc.
And as I mentioned - whenever I remove a Creep from the system with your "call ZTS_RemoveThreatUnit(unit npc)" function. They will cast their spells at random again.

I just hoped the system wouldn't interfere, since you also wrote in your demo-map:

"Ordinary Wc3 spells will be used randomly by creeps even with the threat system active"
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Just to go sure: do you issue any orders manually in your game? I'm asking that because even with neutral hostile AI being different from computer player AI, neutral hostile should still occasionally use certain spells.

What spells are you using here? Remember that neutral hostile will not spam spells like Blizzard or Frost Bolt. The AI will only use them when the requirements are met (like having a cluster of attacking units for Blizzard, etc).
 
Last edited:
Level 2
Joined
Jun 20, 2009
Messages
15
I have some spells being handled manually. None that involve the units I'm trying to create here though.

So if I want threat-registered neutral hostile mobs to cast spells - I need to script them individually?
I suppose the solution would be to assign them to a computer player then, I just really would like the level of the mob to be easily visible in this map, the way they are on Neutral Hostile.
 
Level 2
Joined
Jun 20, 2009
Messages
15
I don't think I have any triggers that interfere with neutral hostile behaviour actually.

Could I send you the map? In case you have access to the World Editor right now. I've set it up so it's immediately visible where the problem lies.
 
Level 2
Joined
Jun 20, 2009
Messages
15
The Spells I handle manually are only dummy spells - part of custom abilities for the player, but they are not handled by Neutral Hostile. As such, the only time I manually order Neutral Hostile so far, is when I tell them to "Attack Move".
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
You register your units with the wrong function here.

ZTS_AddPlayerUnit is for player controlled units only. Player units will never receive any orders by the system. Instead, player units are the units that can build up aggro on other the units added by the other registering function.
For the threat AI to actually control units and have threat lists, you must use ZTS_AddThreatUnit on these units instead.

Example:
Player 1 has and controls a hero that can build up threat on units owned by neutral hostile -> ZTS_AddPlayerUnit on that hero
Player Neutral Hostile controls creeps that have threat tables that Player 1's units can be listed in -> ZTS_AddThreatUnit on that creep

Also you are not applying any threat anywhere in these triggers. If you just register the units without ever applying threat, nothing is going to happen and the units behave as if they were never added to the system until they receive at least 1 point of threat from any source.
If you want threat applied by dealing damage, you need a damage detection system and apply threat via ZTS_ModifyThreat for the amount of damage dealt.


Besides, it's not smart to attack-move units away from their initial registry position. They will eventually return to their original spot if they move too far. From what I see here you have some kind of defense event going on? In that case, make sure the return range is high enough so that the units can move from P1 to P2 without getting out of range of the original spawn location.
 
Last edited:
Level 2
Joined
Jun 20, 2009
Messages
15
Thank you for answering.
You were right of course, about me using the wrong function on the spawned creeps. This was a minor trigger, so I hadn't really noticed the effect - (or lack thereof).
I am using Bribe's damage detection system, and it works just fine with this as well. Your debug window is really helpful for testing. Also I've kept the issues with move-orders in mind, but in these cases we're talking something like a 400 unit distance, so it's well within the return range.
Anyway, I guess it's something else I have that interferes with Neutral Hostile AI then, so I'll keep digging at that.
Thank you for helping me out - and for giving us this system of course.
 
Level 2
Joined
Jun 20, 2009
Messages
15
So I finally found a way for Neutral Hostile mobs to start using their normal abilities again. I had to change the attack order to "attack" instead of "smart" in your trigger. After doing this, the Neutral Hostile creeps would use their native spells again.
 
Level 2
Joined
Aug 18, 2019
Messages
8
Seems like AoE healing abilities (tested with Replenish and Essence of Blight) aren't registered initially. Attacking / using a different kind of healing spell (e.g. Heal) adds the unit to the registered ones and then continually increases threat when using those AoE healing abilities.

Any chance to lend a helping hand in how to add such healing abilities to the ApplyHealThreat function?
 
Level 2
Joined
Aug 18, 2019
Messages
8
Just register the cast event of these abilities and use the HealThreat function once on that unit. It does exactly what you want.
Easier said than done. Due to the functionality of mentioned abilities (Essence of Blight / Replenish) they do not have a target, but instead are point-blank AoE abilities. And due to not having a specific target, no allies on the threat-list are picked leading to the caster not being added to the threat list.
So, I'm searching a way to get the caster registered on the threat list even though he's not having an ability-target (ally).

For now I've done a workaround by dealing 0 damage to all units in radius x around the caster which adds the caster to the threat list. However, I was hoping to find a cleaner way ...
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Easier said than done. Due to the functionality of mentioned abilities (Essence of Blight / Replenish) they do not have a target, but instead are point-blank AoE abilities. And due to not having a specific target, no allies on the threat-list are picked leading to the caster not being added to the threat list.
So, I'm searching a way to get the caster registered on the threat list even though he's not having an ability-target (ally).

For now I've done a workaround by dealing 0 damage to all units in radius x around the caster which adds the caster to the threat list. However, I was hoping to find a cleaner way ...
Use ApplyHealThreat and set the caster for both source and target (as if you used a healing spell on yourself)
It will apply threat equally to all units currently aggroed.
 
Level 1
Joined
Jun 19, 2015
Messages
2
After the new patch, I've noticed that if you make any changes to a map that has ZTS in it and try to run it, the map editor will disable all triggers related to the system. I noticed it even does it to the ZTS map itself.

I'm not experienced enough with JASS to know how to fix this. Anyone else experiencing this?
 
Level 1
Joined
Jun 19, 2015
Messages
2
Oh, that makes sense. Thank you so much!
In the Trigger Editor window, navigate on the top bar to JassHelper > Enable vJASS and make sure that it's turned on. The WE has this setting defaulting to off for all maps now, but it updates on a per-map basis when you save.
 
Top