• 🏆 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!
  • ✅ The POLL for Hive's Texturing Contest #33 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!

Damage Engine 5.A.0.0

This bundle is marked as director's cut. It exceeds all expectations and excels in every regard.
Damage Engine

Three GUI Damage systems for the community of The Hive,
Seven vJass Damage systems for the JASS-heads on their pedestals high,
Nine competing Damage systems, doomed to die,
One for Bribe on his dark throne
In the Land of the Hive where the Workshop lies.
One Damage Engine to rule them all, One Damage Engine to find them,
One Damage Engine to bring them all and in cross-compatibility unite them.


Whether you're looking for a simple DDS (Damage Detection System), need to modify damage or even if you want a complex network of damage pre-and post-processing, this resource is for you. Damage Engine is the most adapted DDS in existence to take full advantage of the new damage detection natives, and is constantly evaluated and scrutinized by both the community and myself for performance improvements, bug fixes and new features alike.

What started with humble beginnings to bring a Unit Indexer-based version of DDS to GUI users to improve on the previous standard (at the time it was Weep's GDDS) would eventually evolve to incorporate other aspects of damage systems out there. @looking_for_help had identified an extremely useful technique with negative spell resistance being used to detect spell damage, and for a time his Physical Damage Detection system became the new standard. It wouldn't be until much later when Damage Engine would resurface and use the same technique in order to be more useful for the community.

Fast forward to 2020, and you'll find not only that cross-compatibility with Weep's and LFH's systems are incorporated, but the most popular DDS systems in existence - even vJass ones - are fully supported via stripped-down API wrappers. All of the functionality of prior DDS systems has been infused into Damage Engine 5.7, and as such the transition to a "one size fits all" DDS is complete. I hope you find this to be useful, and I also hope that it will help you in developing your map to be more dynamic what than you had previously thought possible.

Features

Legacy Code & Requirements

How it works

How to install/upgrade

Video Guides

FAQs

Thanks


  • Damage Type, Attack Type, Weapon Type, Armor Type and Defense Type detection and modification to alter/understand the fundamentals of WarCraft 3's damage processing;
  • Access to the view and change the damage amount before and/or after reduction by armor, armor type and shields.
  • Fully cross-compatible with every major DDS - vJass and GUI alike.
  • Correctly distribute/negate Spirit Link damage.
  • Differentiate between Ranged, Melee, Spell or other types of attack.
  • Does not require any Object Editor data nor Unit Indexer.
  • As of 5.4, it is now completely recursion-proof.
  • Other features:
    • Damage Blocking, reducing, amplifying and/or conversion to healing
    • Does not require you to know nor use Jass NewGen Pack nor any custom script
    • Custom DamageType association available to be set before and/or during damage event
    • Detect when multiple targets were hit simultaneously via DamageEventAOE > 1.
    • Detect when the same unit was hit simultaneously by the same source via DamageEventLevel > 1.
  • Detect damage: use the event "OnDamageEventEqual to <any value>". You have access to the following variables for reference:
    • DamageEventSource - the unit dealing the damage
    • DamageEventTarget - the unit receiving the damage
    • DamageEventAmount - the amount of damage the unit will receive
    • DamageEventPrevAmt - the amount of damage prior to being modified by the game engine.
    • DamageEventAttackT - the attack type (Chaos, Spells, Pierce, etc.)
    • DamageEventDamageT - the damage type - for comprehensive info, click here.
    • DamageEventWeaponT - the weapon type determines if an attack plays some kind of sound on attack (ie. Metal Heavy Bash). It is always NONE for spells and almost always NONE for ranged attacks.
    • DamageEventArmorT - the armor type (Flesh, Stone, Ethereal, etc.)
    • DamageEventDefenseT - the defense type (Hero, Fortified, Unarmored, etc.)
    • DamageEventArmorPierced - if DAMAGE_TYPE_NORMAL, how much armor was set to be ignored by the user.
    • IsDamageSpell, IsDamageRanged, IsDamageMelee - determine the source category of damage dealt.
    • IsDamageCode - Determine if the damage was dealt natively or by the user. This can give incorrect readings if the user has not identified to Damage Engine that it is code damage. Therefore I recommend setting NextDamageType prior to dealing damage.
    • DamageEventType - An integer identifier that can be referenced by you for whatever extra differentiation you need. You can also create your own special damage types and add them to the definitions in the Damage Event Config trigger. If you the unit should explode on death by that damage, use a damage type integer less than 0. If you want the damage to ignore all modifications, use DamageTypePure.
  • To change damage before it's processed by the WarCraft 3 engine: use the event "PreDamageEvent Becomes Equal to <any value>". Whether you just want to use one monolithic trigger for all damage modification like I do in the demo map, or use the different modifier events here, is up to you.
  • To interact with damage after it's been factored for armor and resistances, use "ArmorDamageEvent Becomes Equal to <any value>". This is typically useful for custom shields. If you fully block or absorb DamageEventAmount (setting it to 0 or less), this event doesn't run.
  • To set the DamageEventType before dealing triggered Damage, use:
    - Set NextDamageType = DamageTypeWhatever
    - Unit - Cause...
  • You can modify the following variables from a "PreDamageEvent" trigger:
    • DamageEventAmount
    • DamageEventAttackT/DamageT/WeaponT/ArmorT/DefenseT
    • DamageEventArmorPierced
    • DamageEventType
    • DamageEventOverride - You can set this if you want to remind yourself not to modify the damage further. If you use the UNKOWN damage type from a Unit - Damage Target native or set "NextDamageType = DamageTypePure" before that function, damage modification is skipped.
  • To catch a unit the moment before it would die from damage, use LethalDamageEvent Becomes Equal to 1.00. Instead of modifying the DamageEventAmount here, modify LethalDamageHP to let the system know how much life to keep the unit alive with. Or you can just reference that LethalDamageHP value in order to know how much "overkill" damage was dealt.
  • To catch a unit as soon as the damage is applied against its Hit Points, use AfterDamageEvent Equal to 1.00.
  • Usage of the "<Event> becomes EQUAL to <value>" can be extrapolated as per the below:
    • EQUAL works as it always has - will run for any damage.
    • NOT EQUAL only runs for code damage.
    • LESS THAN only runs for damage from attacks but not coded attacks.
    • LESS THAN OR EQUAL only runs for melee attacks but not coded attacks.
    • GREATER THAN OR EQUAL only runs for ranged attacks but not coded attacks.
    • GREATER THAN only runs for Spell damage but not coded spell damage.

  • Damage Engine 5 and higher requires the latest Warcraft 3 patch (currently 1.32).
  • For people who did not update their game to Reforged / have not updated to Reforged yet. Alternatively, adding the below JASS script to the top of the DamageEngine library SHOULD enable it to work too:

    JASS:
    function BlzGetEventIsAttack takes nothing returns boolean
         return BlzGetEventDamageType() == DAMAGE_TYPE_NORMAL
    endfunction
  • I have created a Pastebin for all information pertaining to Damage Engine 3.8, including the link to download it, via: Damage Engine 3.8.0.0 | HIVE.
  • As of 20 June 2020, JNGP users who are still on WarCraft 3 1.26 can benefit from Damage Engine 3A, which integrates many of the design choices added in various stages of Damage Engine 5. This is a special update which only requires the JASS script be replaced (no new GUI variables added). Can be found here: Damage Engine 3A.0.0.0 and 3.8.0.0 | HIVE

1
Unit attacks or casts a spell. The pure damage is assessed at this point - damage dice, evasion, critical strike. There is currently no event to affect or modify these at source.
→ →
2
The projectile or weapon hits the target unit
→ →
3
EVENT_UNIT_DAMAGING is fired before any modifications to the damage are made.
→ ↓
↓ ←
6
User changes to DamageEventAmount and/or to DamageEventDamageT/AttackT/WeaponT are saved into the WC3 engine.
← ←
5
If any recursive damage is detected from any of those events, it is postponed and the current damage will continue first.
← ←
4
Damage Engine deploys the PreDamageEvent
7
WarCraft 3 processes the user damage.
→ →
8
WarCraft 3 then distributes the user damage into Spirit Link (before armor)
→ →
9
WarCraft 3 runs any interrupting events, such as spirit link or defensive damage like Thorns Aura
→ ↓
↓ ←
12
The EVENT_UNIT_DAMAGED event runs. This is the original event we have always had access to. Damage Engine will either keep using the original variables from the DAMAGING event, or if there was Spirit Link/defensive damage it will freshly retrieve the event values from WC3 and only retain DamageEventPrevAmt.
← ←
11
Once any potential recursive WarCraft 3 damage is processed, the armor/mana shield modifications to the damage are performed.
← ←
10
If such events such as Spirit Link were detected, they fire their own EVENT_UNIT_DAMAGING & EVENT_UNIT_DAMAGED, and DamageEngine processes it along the way for the user.
13
If the damage is above zero, ArmorDamageEvent will run. If any recursive damage is detected, it is postponed.
→ →
14
The user can make modification to the damage here with the after-damage amount.
→ →
15
The user can access DamageEventPrevAmount if they want to know the damage amount before user changes/WarCraft 3 changes.
→ ↓
↓ ←
18
The user can specify whether or not to change LethalDamageHP in order to stop the unit from dying.
← ←
17
If the damage is still above zero, check if the damage is lethal. If so, run LethalDamageEvent 1.00. If any recursive damage is detected, it is postponed.
← ←
16
If the user wants the value that DamageEngine used to have with DamageEventPrevAmt (after WarCraft 3 processing but before user changes) they multiply DamageEventPrevAmt x DamageScalingWC3.
19
Once all modification is done, run OnDamageEvent. If any recursive damage is detected, it is postponed.
→ →
20
After a new damage instance is detected, or the 0.00 timer expires, run AfterDamageEvent. If any recursive damage is detected, it is postponed.
→ →
21
Run all potential recursive Unit - Damage Target function calls in chronological order (first in, first out).

How to install Damage Engine:
  1. Use WarCraft 3 Version 1.32
  2. If you're upgrading from 3.8 or prior, please delete the entire "Damage Engine" category from your map.
  3. Copy & Paste the Damage Engine category from the attached map to your own map.
How do I upgrade to the latest Damage Engine?
- Depending on the complexity, you'll either need to re-copy the Damage Engine category or just the Damage Engine script. Generally, you can use this as a guide:

  • Damage Engine _._._.x - only requires copying of the JASS script
  • Damage Engine _._.x._ - generally only needs copying of the JASS script, but read the patch notes in case there are changes you may want to make to your own code in order to utilize the changes.
  • Damage Engine _.x._._ - delete your current Damage Engine category or manually add the missing variables to your Variable Editor, and update your JASS script.
  • Damage Engine x._._._ - this occurs very infrequently. Typically requires changes to the way Damage Engine needs to be installed and used.

Notes about upgrading from Damage Engine 4.0 or prior:
  • Revert any custom Object Editor changes made (such as Life Drain reversal, Spell Damage Reduction inversion, etc).
  • You can safely delete the custom spells "Spell Damage Detection" and "Cheat Death Ability"
  • You can delete the Unit Indexer trigger if you do not use it or would prefer to use a different indexer.
  • You should delete any "Mana Shield fix" or "Finger of Death fix" you may have imported, as well as revert any Object Editor data that was required to make it work, as these are no longer needed.
  • !!!DEPRECATED FEATURE!!! As of 5.4, do not bother to take any recursive damage mitigation - Damage Engine will now handle all of that for you!
  • !!!DEPRECATED FEATURE!!! Do not use "ClearDamageEvent" - it does nothing. Just delete it.
  • !!!DEPRECATED FEATURE!!! Do not use "NextDamageOverride" - set NextDamageType = DamageTypePure instead.
  • !!!CHANGED FEATURE!!! If the system detects code damage and the user did not specify it as any particular DamageEvenType, the system will assign it DamageTypeCode automatically.
  • !!!CHANGED FEATURE!!! DamageModifierEvent 1.00-3.00 now run prior to armor reduction. This enables the user to modify the damage before armor and resistance changes are applied - also before Mana Shield and Anti-Magic shell kick in, so no more need for special triggers.


  • Q: Why am I getting a bunch of 'trigger was disabled' errors when I save my map?
  • A: This issue is not unique to Damage Engine, but to all vJass resources. Blizzard has taken the very confusing decision to make JassHelper 'disabled' by default, meaning that every new map that uses a vJass resource has to manually enable JassHelper. Please see this thread if you want to know where to find the Enable JassHelper option.
    .
  • Q: How can I detect when a unit gets damaged?
  • A: Create a trigger with the event: "Game - Value of Real Variable <DamageEvent> becomes Equal to 1.00".
    • Use the following custom variables to reference the event responses:
      • DamageEventSource - the unit dealing the damage
      • DamageEventTarget - the unit getting damaged
      • DamageEventAmount - how much damage is being dealt
      • DamageEventAttackT - which attack type was used by DamageEventSource to damage DamageEventTarget
      • DamageEventDamageT - which damage type was used to damage the target (ie. UNIVERSAL for ultimate damage, or NORMAL for damage that gets reduced by armor).
      • DamageEventDefenseT - which defense type does the target unit have (ie. Hero, Fortified, Unarmored).
      • DamageEventArmorT - which armor type does the target unit have (ie. Flesh, Ethereal, Stone).
      • DamageEventPrevAmt - what the value of the damage was before being modified by armor, ethereal/item bonuses or user changes.
        .
  • Q: How do I modify the damage that is dealt to a unit?
  • A: Create a trigger with the event: "Game - Value of Real Variable <PreDamageEvent> becomes Equal to <any value>".
    • You can change the following variables to affect the damage that will be dealt:
      • DamageEventAmount - how much damage will be dealt (before armor reductions)
      • DamageEventAttackT - which attack type will be used by DamageEventSource to damage DamageEventTarget
      • DamageEventDamageT - which damage type will be used to damage the target.
      • DamageEventDefenseT - which defense type should the target unit have during this attack.
      • DamageEventArmorT - which armor type should the target unit have during this attack.
      • DamageEventArmorPierced - how much armor value to ignore when dealing this damage (applies to DAMAGE_TYPE_NORMAL only, otherwise all armor is ignored).
        .

  • Q: How do I deal Pure damage to a unit (bypassing armor/skipping user modification)?
  • A: Use the following actions:
    • Set NextDamageType = DamageTypePure
    • Unit - Cause Source to damage Target for Amount using attack type Spells and damage type Universal
      .

  • Q: How do I protect against recursive damage?
  • A: Damage Engine 5.4 and above is completely recursion-proof, using vJass hooks to detect registered Damage Event triggers.
    .

  • Q: I've been using <insert Damage system here>. Can I use those with Damage Engine?
  • A: Better - Damage Engine has integrated cross-compatibility with all other approved Damage systems and even the major ones from outside of Hiveworkhop.
    .

  • Q: Can I cause an attack to 'Miss' its target?
  • A: Kind of. Ranged attacks will still explode on the target, and on-hit effects will still apply, but you can do the following:
    • Use the event "PreDamageEvent becomes Equal to 1.00"
    • Use the following actions:
      • Set DamageEventAmount = 0.00
      • Set DamageEventArmorT = ARMOR_TYPE_NONE
      • Set DamageEventWeaponT = WEAPON_TYPE_NONE
    • Setting the weapon type and armor type to none like the above will stop any on-hit sounds from playing. When the Peasant attacks in the demo map, this is the trick I'm using. Instead of saying "MISSED!" I have the Floating Text saying "FAIL!" because - again - it's not exactly a "miss".

Thank you to Blizzard for continuing to work on this amazing game to give us awesome new natives that have the best possible control over incoming damage. Damage Engine brings that power to GUI. Also, a very special thank you to @KILLCIDE for getting me motivated to start up the 5.0 project in the first place.

Thank you to the users of this system who have helped me mold this project into the stable, powerful form it is in today!

For versions 3.8 and prior:

Thank you @looking_for_help for finding the spell damage detection method used in Damage Engine 3 - it was the greatest find since the undefend bug.

Thanks to Jesus4Lyf and @Nestharus for building the inspiration that originally led me to create DamageEngine.

Thank you Wietlol and looking_for_help for challenging me on this project to integrate solutions to problems I had not been made aware of, such as the importance of an After-Damage Event.

Thanks to @Spellbound for several crucial bug reports.


Damage Engine Config

Damage Engine vJass

Damage Engine Lua

Changelog


  • Damage Engine Config
    • Events
      • Map initialization
      • Game - DamageModifierEvent becomes Greater than 0.00
      • Game - LethalDamageEvent becomes Less than or equal to 0.00
      • Game - DamageEvent becomes Not equal to 0.00
      • Game - AfterDamageEvent becomes Less than 0.00
      • Game - AOEDamageEvent becomes Greater than or equal to 0.00
      • Game - SourceDamageEvent becomes Equal to 0.00
      • Game - PreDamageEvent becomes Equal to 0.00
      • Game - ArmorDamageEvent becomes Equal to 0.00
      • Game - ZeroDamageEvent becomes Equal to 0.00
    • Conditions
    • Actions
      • -------- You can add extra classifications here if you want to differentiate between your triggered damage --------
      • -------- Use DamageTypeExplosive (or any negative value damage type) if you want a unit killed by that damage to explode --------
      • -------- - --------
      • -------- The pre-defined type Code might be set by Damage Engine if Unit - Damage Target is detected and the user didn't define a type of their own. --------
      • -------- "Pure" is especially important because it overrides both the Damage Engine as well as WarCraft 3 damage modification. --------
      • -------- I therefore gave the user "Explosive Pure" in case one wants to combine the functionality of the two. --------
      • -------- - --------
      • Set VariableSet DamageTypePureExplosive = -2
      • Set VariableSet DamageTypeExplosive = -1
      • Set VariableSet DamageTypeCode = 1
      • Set VariableSet DamageTypePure = 2
      • -------- - --------
      • Set VariableSet DamageTypeHeal = 3
      • Set VariableSet DamageTypeBlocked = 4
      • Set VariableSet DamageTypeReduced = 5
      • -------- - --------
      • Set VariableSet DamageTypeCriticalStrike = 6
      • -------- - --------
      • Custom script: call DamageEngine_DebugStr()

BribeFromTheHive/DamageEngine

Lua 1.0.2.3 - Fixed to match adjustment made in vJass version 5.4.2.3.
Lua 1.0.2.2 - Fixed to match adjustment made in vJass version 5.4.2.2.
Lua 1.0.2.1 - Fixed to match adjustment made in vJass version 5.4.2.1.
Lua 1.0.2.0 - Added support for Lua Fast Triggers ([Lua] Ridiculously Fast Triggers). Fixed an issue where the AfterDamageEvent wasn't always timed the correct way like it was in the JASS verion.
Lua 1.0.1.0 - Fixed encapsulation issue and recursion issue with DamageEngine_inception. Now hooks UnitDamageTarget.
Lua 1.0.0.0 - Release based on Damage Engine 5.4.2.0

5.9.0.0 - Added the following clearer event names to make things less confusing for new users:
  • PreDamageEvent - can be used in place of DamageModifierEvent pre-armor modification
  • ArmorDamageEvent - can be used in place of DamageModifierEvent post-armor modification
  • OnDamageEvent - can be used instead of a non-zero DamageEvent
  • ZeroDamageEvent - can be used instead of a zero damage DamageEvent
  • SourceDamageEvent - runs at the same time as AOEDamageEvent, but doesn't need to hit multiple units.
Added "DamageFilterRunChance" - odds for the trigger to BE run (works inversely to DamageFilterFailChance).
Shortened the Configuration trigger so that it focuses primarily on what the user can modify.
Organized all variables into categories to help users better understand what does what.
Installation or updating from a previous version will require re-copying the entire Damage Engine folder.
Updated the demo map's text tag production to include a new custom update for ArcingTextTag, thanks to @Ugabunda and @Kusanagi Kuro.
Side note - the Demo Map's triggers have been heavily cleaned up and will now no longer cause crashes when being imported into a new map.
5.8.0.0 -
  • Added a new functionality to the AOEDamageEvent, which allows it to fire even when only one unit is hit, if the AOEDamageEvent is registered as "Not Equal" instead of "Equal to". This is useful in a very specific scenario where an AfterDamageEvent may not be able to be relied upon to properly deallocate data from a running instance that needs to be able to function when multiple units are hit, but also needs to not fail when only one unit is hit.
  • Added additional filters for GUI users to avoid using trigger conditions in even more scenarios:
    • DamageFilterSource/TargetI (has item)
    • DamageFilterSource/TargetA (has ability)
    • DamageFilterSource/TargetC (has a certain classification, like hero or Tauren)
    • DamageFilterFailChance - odds for the trigger to not be run (ideal for critical strike or evasion)
  • Provided a way for GUI to un-register a Damage Event by setting "RemoveDamageEvent" to true from within their trigger's actions.
  • Moved the CreateTimer/CreateTrigger/CreateGroup calls that had been included in the globals block down to the onInit block as per request of @Ricola3D
  • Minor performance improvements thanks to @BLOKKADE
5.7.1.2 - Fixed an issue with armor penetration sometimes bugging out.
5.7.1.1 - Fixed an issue with the eventFilter not retaining its value during an AOE event. Fixed an issue with eventFilter when USE_MELEE_RANGE was set to false. Both issues reported by @lolreported
5.7.1.0 -
I have fixed several potential points of failure reported by multiple users that stopped Damage Engine from working for the rest of the game. In any case there should no longer be ANY SITUATION where Damage Engine just "stops working", even in ways I can't predict. The most likely issue was with DamageScalingWC3/User having possible 0-division errors.​
Fixed the "configured" issue reported by @lolreported where manual configuration didn't work unless the damage and attack type checks were initialized tto -1.​
In addition to these fixed bugs, I have added several more static ifs so that advanced users have more control over getting rid of features they might not care about.​

5.7.0.3 - Fixed the issue reported by @KitsuneTailsPrower wherein the DamageInterface extension wasn't working. This needed to be fixed both in the Damage Engine source as well as the DamageInterface plugin itself.
5.7.0.2 - Fixed the issue reported by @Wazzz where the armor reduction wasn't taken into consideration. Actually there was a much bigger flaw that I had overlooked that was prompting this.
5.7.0.1 - Improved the logic of the 0 damage event.
5.7.0.0:

  • Usage of the "DamageEvent becomes EQUAL to 1.00" now can be extrapolated further than ever before:
    • EQUAL works as it always has.
    • NOT EQUAL only runs for code damage.
    • LESS THAN only runs for damage from attacks.
    • LESS THAN OR EQUAL only runs for melee attacks.
    • GREATER THAN OR EQUAL only runs for ranged attacks.
    • GREATER THAN only runs for Spell damage.
  • Fully adapted Damage Engine to work with every other major DDS
  • Rewrote the internal script to use vJass structs: Damage and DamageTrigger.
  • Changed some of the vJass API. You can find the API listed in the Damage Engine trigger. Notably:
    • I removed UnitDamageTargetEx. You can replace this with Damage.apply.
      • The reason for this change is because of cross-compatibility. Rising Dusk's IDDS uses a different set of parameters for this function, so I removed it from my library to allow this to work seamlessly.
    • TriggerRegisterDamageEvent -> TriggerRegisterDamageEngine
    • RegisterDamageEvent -> RegisterDamageEngine
      • The reason for the above two changes is, like for UnitDamageTargetEx, because Rising Dusk's IDDS uses them. I don't want to make the same mistake I did with Table's API and preferred to walk back my API choices before things got out of hand again.
  • Various performance tweaks, bug fixes and re-commenting of variables.
  • Recursive damage is now processed more intelligently and possibly be more performance-friendly.
  • Adapted the cross-compatibility libraries for Weep and LFH's systems to use textmacros to insert themselves into Damage Engine (this might have been in the 5.6 update, but these two updates were both tailored to achieve similar goals).
5.6.2.0 - Fixed a bug with Damage modification and laid groundwork for a bunch of optional vJass compatibility addons.
5.6.1.0 - Patchwork on Melee/Ranged detection, recursion tracking. Also added the ability to modify damage from a DamageEvent to make it easier on beginners.
5.6.0.1 - Fixed an issue where the DamageEventType for recursive damage that used NextDamageType would always default to DamageTypeCode.
5.6.0.0
Rewrote a significant amount of the internal code so that struct and GUI syntax both work - and are synchronized with each other. vJass-exclusive users can disable the GUI synchronization if they don't use any GUI damage events in their maps.​
Four new variables must be added to your variable editor:​
  • boolean NextDamageIsAttack
  • boolean NextDamageIsMelee
  • boolean NextDamageIsRanged
  • integer NextDamageWeaponT
Struct syntax should not need too much introduction - Damage.index is the triggering event ID, and the syntax is Damage.index.amount/source/target/etc. To initialize a JASS damage event, use:​
JASS:
function RegisterDamageEvent takes code c, string eventName, real value returns nothing
The string is simplified: "Modifier", "" (for simple DamageEvent), "After", "Lethal", "AOE"​
Finally, a neat QOL improvement is that I have assigned weight to each of the events. When registering an event, you can include numbers different than 1, 2, 3 or 4 (for modification) or just the plain "1" for the others. Now you can set your own sequencing and use 1.5, 3.14 or even -1 to give an even more extreme priority to something. Lower numbers run first, higher ones last.​
5.5.0.0 - Added support for the new native BlzGetEventIsAttack via "IsDamageAttack". Also updated the Config trigger to make importing a bit easier
5.4.2.3 - Fixed a mis-flag of the IsDamageSpell value when not being actual spell damage.
5.4.2.2 - Actually fixed the Cold Arrows issue (division by 0.00001 or something ...somehow... either way, it is fixed).
5.4.2.1 - A fix which should hopefully quell the recent reports of damage events failing in complex situations involving Cold Arrows.
5.4.2.0 - A ton of fixes to repair the issues plaguing Sunken City and anyone else who might be pushing this engine well beyond what I imagined it would be used for. Also added a couple of variables intended to be used to ricochet damage: CONVERTED_ATTACK_TYPE and CONVERTED_DAMAGE_TYPE. Check the demo map for how they can be used in a Unit - Damage Target action.
5.4.1.0 - The "Inception" update. Advanced users can interact with Damage Engine via custom script to set DamageEvent_inception = true to allow their damage to potentially go recursive (to a fixed extent).
5.4.0.1 - Hotfixed that modifiers 2 and 3 weren't running.
5.4.0.0 - By using an innovative approach of hooking TriggerRegisterVariableEvent, I've permanently eliminated all risks of recursion in the engine.
5.3.0.1 - Fixed unexpected behavior with DamageTypePure when it is affected by Anti-Magic Shell or Mana Shield. DamageTypePure now no longer ignores DamageScalingWC3.
5.3.0.0 - Fixed an issue with AfterDamageEvent sometimes being delayed. Added DamageScalingUser to track the ratio of user modified damage, as well as DamageEventArmorPierced which allows the user to define how much armor to ignore when working with DAMAGE_TYPE_NORMAL.
5.2.0.1 - Fixed an issue where with the final unit in a Spirit Link chain or the retaliating damage of Thorns/Carapace would not deploy an AfterDamageEvent. Also fixed an issue where AfterDamageEvent would still fire from DAMAGE_TYPE_UNKNOWN if it was greater than 0. Simply copy over the JASS from this post in order to update your own implementation.
5.2.0.0

  • Now features DamageEventArmorT and DamageEventDefenseT, which pull from the target unit's Object Editor data in order to allow you more complete access to detect and even MODIFY those defenses. Changes must be made in a DamageModifierEvent, and they are reverted as soon as armor is calculated.
  • Re-introduced AfterDamageEvent, which is the moment after the unit's life is changed, but before any recursive damage has run.

5.1.3.1 - Bug fixes and performance improvements. No, really. Fixed an issue with the DAMAGED event running even though it was turned off (who would've guessed that?)
5.1.3.0 - Engine re-tooling to improve accuracy and get Spirit Link/Defensive damage out of hardcoded mode - it will now work even in circumstances I haven't tested for (in case those weren't the only issues). Also fixed the Is Unit Moving resource.
5.1.2.1 - Fixed an issue with Spiked Carapace and Thorns Aura where the melee attacker would not get recorded correctly. This required the same fix as I needed to apply for the initial target in a spirit link chain.
5.1.2.0 - Tweaked recursion and fixed a few bugs. Re-introduced the zero damage event now that patch 1.31.1 brought it back.
5.1.1.1 - Minor tweak to Spirit Link in rare situation.
5.1.1.0 - Fixed issues related to Spirit Link. Now works intuitively as expected.
5.1.0.0 - Crazy improvements - check out the details in "How to upgrade from Damage Engine 4.0 or earlier" and "How to use Damage Engine 5.1"
5.0.0.0 - Oh man. Where do I even start?
  • You can now set/compare DamageEventDamageT, DamageEventAttackT or DamageEventWeaponT
  • No longer needs Unit Indexer nor any custom Object Editor data nor modifications.
  • Requires WarCraft 3 1.31 or higher.
  • Changed vanilla JASS code to vJass to clean up a lot of things such as making certain variables private.
  • Look for more details in "How to upgrade from Damage Engine 4.0 or earlier"

4.0.0.0 - Never officially released, but was the first iteration which used SetEventDamage.
For 3.8.0.0 and prior, see: Damage Engine 3.8.0.0 | HIVE


Cross-Compatibility:

BribeFromTheHive/DamageEngine

Keywords:unit indexer, damage detection, damage event, weep, nestharus, looking_for_help, EVENT_PLAYER_UNIT_DAMAGED, damage engine, spell, physical, EVENT_PLAYER_UNIT_DAMAGED, attacktype, damagetype, weapontype, armortype, defensetype, BlzSetEventDamage
Previews
Contents

Damage Engine Demo Map (Map)

Lua Damage Engine 2.0.0.0 (Map)

Reviews
23:20, 11th Jan 2015, BPower Criticism: On the one hand Damage Engine offers extremely high utility to every spell, system and map maker independent of the coding style ( GUI, JASS, vJass, .... ) on the other hand it's very easy to import and...
View attachment 464639
For some reason i cant get the system to detect damage types am i doing something wrong? what is the condition that checks damage type if it is not integer?
The "if" conditions are supposed to be "DamageEventDamageT" = "DAMAGE_TYPE_FIRE"/"DAMAGE_TYPE_LIGHTNING", since DAMAGE_TYPE_FIRE & DAMAGE_TYPE_LIGHTNING are constants.
 
Level 8
Joined
Jul 9, 2006
Messages
229
Spirit Link seems to be working for me:
workingspiritlink.png


Using the triggers copied from the Demo Engine Demo Map (including the damage text ones) into my own map. Using just a default Spirit Walker Spirit Link on a bunch of weak but otherwise blank heroes fighting Militia, it seems to be working.

Are you perhaps using the LUA triggers? I haven't tried those yet, only GUIish/JASS.

Maybe it doesn't work on friendly unit damage, I've had that happen occasionally with other abilities I was expecting to work, like Resistant Skin.




By the way, would there be some way to determine whether an attack is from a ranged/projectile source, and not the ground?

I.e. determine the difference between blizzard vs flamestrike, or storm bolt vs entangle.

Besides manually classifying each spell.

There is IsDamageRanged and IsDamageSpell, but I presume there is no IsDamageRangedSpell, eh?
 
Last edited:
Level 4
Joined
Nov 20, 2006
Messages
71
I'm sorry if this question has been asked. I went through the demo map and didn't see an obvious way to handle this. What is the order of changes to the damage event? I may have multiple sources of damage reduction, some of them might be multiplicative and some might be flat changes. Can I make sure that the multiplication is done before/after the addition, even if they both come from multiple sources (e.g. an aura reduces damage by 10% but an item reduces damage by 5 points)? Also, can I make this processing happen after spirit link distributes damage, rather than before?
 
Level 14
Joined
Oct 18, 2013
Messages
703
Spirit Link seems to be working for me:
View attachment 467138

Using the triggers copied from the Demo Engine Demo Map (including the damage text ones) into my own map. Using just a default Spirit Walker Spirit Link on a bunch of weak but otherwise blank heroes fighting Militia, it seems to be working.

Are you perhaps using the LUA triggers? I haven't tried those yet, only GUIish/JASS.

Maybe it doesn't work on friendly unit damage, I've had that happen occasionally with other abilities I was expecting to work, like Resistant Skin.




By the way, would there be some way to determine whether an attack is from a ranged/projectile source, and not the ground?

I.e. determine the difference between blizzard vs flamestrike, or storm bolt vs entangle.

Besides manually classifying each spell.

There is IsDamageRanged and IsDamageSpell, but I presume there is no IsDamageRangedSpell, eh?
Thanks for the testing, I have no idea what could be breaking it in my map but it happened after importing Damage Engine. It's possible that it only breaks when Spirit Link is handling a ton of units, as the heroes ult is spirit linking every unit on the map. I'll do some more testing.
 
Level 11
Joined
Mar 9, 2008
Messages
235
Hey guys, so is there an optimal way to use this system in regards to how I set up my GUI triggers? At the moment, I am using this pretty religiously; if I have a considerable amount of Triggers that use these events (lets say ballpark 30 or so), will that noticeably impact performance?

I am able to run my map very well, but people with poor system specs are dropping down to 5 FPS during combat (mostly in large battles). I want to be sure that the way I'm using the engine isn't causing a massive amount of overhead CPU wise.

TLDR : Should I be creating like 50 triggers that look like this : (Also, does setting position Z of an effect cause desync? I have intermittent desync I've been chasing and I can't for the life of me resolve)

1714096754932.png



Or is this going to tank my performance on lower end systems. I will say, I am now managing these triggers so that they are only active when an ability or item is actually in play; that was my solution if this was the case.

The processor specs of the person who is having trouble running :

AMD Athlon X4 840 Quad Core 1633.70 MHz
 
Level 14
Joined
Oct 18, 2013
Messages
703
I think you'll want to merge all of your triggers into one. I know there were maps that did this for other things, like item pickups or ability casts, but the performance on those should be negligible I think. For something that runs so much though, I think you have to bite the bullet. You could also save some time by just using JASS, and creating the effects at X/Y


local effect sfx
local real x
local real y

//whatever text macro stuff
set x = ProjectionX(GetUnitX(udg_DamageEventTarget) , GetRandomReal(-25. , 25))
set y = ProjectionY(GetUnitY(udg_DamageEventTarget) , GetRandomReal(-25. , 25))
set sfx = AddSpecialEffect("model\\file\\path.mdl",x,y)
call BlzSetSpecialEffectScale(sfx, GetRandomReal(.3 , 1.8)
call BlzSetSpecialEffectZ(sfx, BlzGetSpecialEffectZ(sfx) + GetRandomReal(-10. , 20.) //not sure on this function name but you get the idea hopefully
call DestroyEffect(sfx)
set sfx = null

For effects where you dont need to change anything about it, you might even be able to wrap the effect creation around DestroyEffect(), can't remember if that works or not.


You'll need these helper functions to use the above code, they are quite quick though so it's worth doing if you're having performance troubles : )

function ProjectionX takes real x, real dist, real angle returns real
return (x + dist * Cos(angle * bj_DEGTORAD))
endfunction

function ProjectionY takes real y, real dist, real angle returns real
return (y + dist * Sin(angle * bj_DEGTORAD))
endfunction
 
Level 11
Joined
Mar 9, 2008
Messages
235
I think you'll want to merge all of your triggers into one. I know there were maps that did this for other things, like item pickups or ability casts, but the performance on those should be negligible I think. For something that runs so much though, I think you have to bite the bullet. You could also save some time by just using JASS, and creating the effects at X/Y


local effect sfx
local real x
local real y

//whatever text macro stuff
set x = ProjectionX(GetUnitX(udg_DamageEventTarget) , GetRandomReal(-25. , 25))
set y = ProjectionY(GetUnitY(udg_DamageEventTarget) , GetRandomReal(-25. , 25))
set sfx = AddSpecialEffect("model\\file\\path.mdl",x,y)
call BlzSetSpecialEffectScale(sfx, GetRandomReal(.3 , 1.8)
call BlzSetSpecialEffectZ(sfx, BlzGetSpecialEffectZ(sfx) + GetRandomReal(-10. , 20.) //not sure on this function name but you get the idea hopefully
call DestroyEffect(sfx)
set sfx = null

For effects where you dont need to change anything about it, you might even be able to wrap the effect creation around DestroyEffect(), can't remember if that works or not.


You'll need these helper functions to use the above code, they are quite quick though so it's worth doing if you're having performance troubles : )

function ProjectionX takes real x, real dist, real angle returns real
return (x + dist * Cos(angle * bj_DEGTORAD))
endfunction

function ProjectionY takes real y, real dist, real angle returns real
return (y + dist * Sin(angle * bj_DEGTORAD))
endfunction

Ok thank you! I will absolutely integrate this in the future for my SFX needs :)

I suppose my question is more about handling the DamageEvents themselves.

I should rephrase the question : Should I then only have one "PreDamageEvent becomes Greater than 1.00" trigger and then handle EVERY instance of ranged damage using traditional GUI If/then/else? I am wondering how many "PreDamageEvents" or "DamageEvents" I can have active with filters before Damage Engine starts to throttle lower end CPUs.

My biggest concern is this line in particular :

1714794748889.png


I don't really see a clear alternative for managing conditional events using DamageEngine, perhaps I just haven't looked hard enough. I do see, in the example map, that for the "Ranged" trigger there is an indexer that checks to see if the target is moving based off what I assume is a JASS (or VJASS) script. But the rest of the examples all use filters liberally, at least in the demo map. The "no longer recommended" really confuses me and leaves me somewhat paranoid considering the extent to which I use them in my own map.

Right now, I am trying to manage these triggers by making sure they are only active if a unit, ability, or item is actually in the game; they usually start "Off" and then get turned on when the associated unit enters the playable map area for the first time (I suppose I could manage them on exit as well). This works particularly well with "activated" abilities, as those are already managed using my triggers so turning on/off the predamage triggers is easy.

I am wondering if this is a valuable usage of optimization time? If not, what is the best way to integrate many complicated different scenarios using pre, post, lethal, etc damage events on separate triggers.
 
Level 14
Joined
Oct 18, 2013
Messages
703
depends on the map! If you expect most of the units to be in play most of the time, then yeah I'd just go with having everything in one function. It's possible the FilterTarget API stuff just runs really slow though, why dont you just write your own conditional in the function?
 
Level 11
Joined
Mar 9, 2008
Messages
235
depends on the map! If you expect most of the units to be in play most of the time, then yeah I'd just go with having everything in one function. It's possible the FilterTarget API stuff just runs really slow though, why dont you just write your own conditional in the function?

To be honest, I wasn't 100% how Damage Engine worked when I first started using it. I have since been using it for around 6 months, and I never really branched out to try using conditions like usual since for some reason I didn't think it would work.

I don't see why one function is a good idea since then I am going to have a GUI trigger that is about 300 pages long for like 5 or 6 factions worth of custom units and abilities, not to mention items haha. The readability and editability of that type of Trigger would be basically untouchable

I appreciate the help, this basically has told me what I need to know. I'll start by removing all the usage of filters I suppose, and see how that effects things. I'm also wondering if this could be a cause of desync, maybe the filters are fighting for priority or something, but I doubt it. Thought it was walkable bridges because two games without them were fine, now I hosted today and one by one throughout the game about 5 people desynced over time
 
Level 14
Joined
Oct 18, 2013
Messages
703
Ooh, thats a good question. I swore I read something about bridges causing desync, so it wouldn't surprise me. I don't think anything with filters is causing desync though, maybe if you have some really funky code but shrug
 
Level 11
Joined
Mar 9, 2008
Messages
235
Ooh, thats a good question. I swore I read something about bridges causing desync, so it wouldn't surprise me. I don't think anything with filters is causing desync though, maybe if you have some really funky code but shrug

Yeah, they definitely cause desync, especially with Reforged and Classic discrepancies. The lesser known desync is destructible animations not being sync'd between clients, causing Z-level desync if you use GetZLocation() or whatever the name is.

My current plan of action is turning off damage engine and MPS (Master Projectile System) in order to see if that's at least the ballpark. After that, the rest of the triggers. If that doesn't work, I take a no parachute jump off my balcony screaming obscenities at Blizzard.

But at the risk of getting off topic, I suppose that's for another thread haha. Thank you again for the help

EDIT: DamageEngine and MPS are absolutely fine, color me shocked.
 
Last edited:
Level 11
Joined
Mar 9, 2008
Messages
235
How does custom damage type works by default? I just want a custom damage type to exclude them from checking but it seems to behave different than default attack.

Alrighty, so my first question is : What exactly do you mean by "exclude them from checking?"

I will work off the assumption that you want this damage type to be excluded from a damage event of some sort.

The way to do this, at the highest level possible I suppose, would be as shown :

1715892911960.png


Now, I will say, I had a similar problem as you, where my custom DamageTypes weren't working as intended at all. It was very confusing, and not just custom damage types, but also the regular ones like Fire, Death, Mind, etc.

The problem was, I wasn't using the right variable when checking for damage type. This is slightly confusing, but if you are using DamageEventType you will be checking for the type of damage (Ranged, Melee, Code, etc), not the "DamageType"

1715893004787.png



If this isn't the problem, then I'm sorry. But this was a problem I had for a while, and I felt dumb after I realized I was conditionally checking for the wrong variable.
 
Alrighty, so my first question is : What exactly do you mean by "exclude them from checking?"

I will work off the assumption that you want this damage type to be excluded from a damage event of some sort.

The way to do this, at the highest level possible I suppose, would be as shown :

View attachment 473498

Now, I will say, I had a similar problem as you, where my custom DamageTypes weren't working as intended at all. It was very confusing, and not just custom damage types, but also the regular ones like Fire, Death, Mind, etc.

The problem was, I wasn't using the right variable when checking for damage type. This is slightly confusing, but if you are using DamageEventType you will be checking for the type of damage (Ranged, Melee, Code, etc), not the "DamageType"

View attachment 473499


If this isn't the problem, then I'm sorry. But this was a problem I had for a while, and I felt dumb after I realized I was conditionally checking for the wrong variable.
I think this is not my issue, the custom damage type I am using is part of the type of damage. After some tinkering, I figured out the issue was with how I use the events (pre vs after and such things).
 
Level 2
Joined
Mar 13, 2015
Messages
5
After running my map with this script it not only failed to run, it changed the name of every single custom unit, upgrade, item ect. to "TRIGSTR###" what an absolute nightmare this is gonna be to fix.
 
Level 14
Joined
Oct 18, 2013
Messages
703
thats really strange, sounds like some map corruption. Haven't had that corruption, but I have had all my custom models get ripped out of the map archive. Backups are super needed with worldedit, it's pretty finnicky software D:
 
After running my map with this script it not only failed to run, it changed the name of every single custom unit, upgrade, item ect. to "TRIGSTR###" what an absolute nightmare this is gonna be to fix.
Jesus that sucks. But that isn't the fault of this script. I have had similar bullshit happen to me in the past. Your JassHelper may have made a backup of your map already, but it might just be the war3map.j file whereas it seems here that your .wts file got broken somehow.
 
Level 11
Joined
Mar 9, 2008
Messages
235
After running my map with this script it not only failed to run, it changed the name of every single custom unit, upgrade, item ect. to "TRIGSTR###" what an absolute nightmare this is gonna be to fix.

I recommend constantly saving backups of your map, this system is incredible for the most part. I usually save a backup after every unit, hero, terrain piece, etc. I work on.

This is good practice not only in WC3, but in greater software development version control is extremely important.
 
Level 2
Joined
Mar 13, 2015
Messages
5
It was very strange, any text that was edited was changed. Thankfully I had a backup that wasn't too old so I was able to copy and paste most of the text. I didn't have JassHelper enabled so when I tried to test the map with the script running it crashed, probably corrupted at the same time. All seems to be working now that I have JassHelper on, the problem didn't replicate thankfully.
 
Hi. Is there a plan to update lua version of damage engine?
There was, but it has too many dependencies that need to be fixed first, and I haven't had the time or energy or motivation to code in Lua in a long time. The build process and annoying annotations and bad limitations on types have dragged me down. I ended up hating EmmyLua/Sumneko's extension too much to keep using it, did some stuff in vJass, then ended up not coding in WarCraft 3 at all in a long time.
 
Level 1
Joined
Oct 22, 2019
Messages
3
Hello, with this system could you make it detect the type of spell? Example: If a unit damages and that damage is chain lightning...I want to specify abilities
 
No specific spell but You can filter attack type and damage tipo for detect.
Chain ligthing clase type for example is attack type spell and damage type ligthing
Yes, attack type and damage type filtering can go a long way if you know that a unit will not have overlapping damage types that you want to differentiate from.

 
Level 1
Joined
Jul 24, 2024
Messages
2
Hi! How would I make this work with GUI Unit Event? There seems to be a conflict in the required libraryies and the Damage Engine doesn't seem to work with newer versions of the Required Lua. Thanks!
 
Lua Damage Engine has very dated dependencies because I never finished the process. Lua Event is the main thing holding everything back, and I got burned out from all the annoying bugs in the Lua and Sumneko LLS source codes preventing it from working the way I wanted it to. the project has been shelved for around 2 years. I do programming as a job now, and there isn't or wasn't enough of a reason for me to motivate myself to spend dozens more hours of my very limited free time to make this stuff work
 
Level 10
Joined
Dec 16, 2017
Messages
398
How can you incorporate "Crowd Control" of chopinski with the events that detect 0 damage and do "Blocked" as text?
When i call a "Crowd Control", it does 0 damage and the text for "blocked" pops up, how can i make it check if the 0 damage come's from the crowd control so it will pop up no text at all or a custom text like "Crowd Control".

  • Damage Tag
    • Events
      • Game - OnDamageEvent becomes Equal to 1.00
    • Conditions
    • Actions
      • Set VariableSet ReportLife = (Life of DamageEventTarget)
      • Game - Display to (All players) the text: (String((Mana of DamageEventTarget)))
      • Set VariableSet DmgStr = |cffffffff
      • Set VariableSet DmgDuration = 1.00
      • Set VariableSet DmgSize = 1.00
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • DamageEventAmount Less than 0.01
          • DamageEventAmount Greater than -0.01
        • Then - Actions
          • -------- A block or near-block --------
          • Set VariableSet DmgSize = 0.60
          • Set VariableSet DmgDuration = 0.60
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Unit-type of DamageEventSource) Equal to Peasant
            • Then - Actions
              • Set VariableSet DmgStr = |c00900081EVASION!|R
            • Else - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • DamageScalingWC3 Equal to 0.00
                • Then - Actions
                  • Set VariableSet DmgStr = (|c00AAAAAABlocked + ((String((Integer((DamageEventPrevAmt x DamageScalingUser))))) + !|r))
                • Else - Actions
                  • Set VariableSet DmgStr = (|c00AAAAAABlocked + ((String((Integer((DamageEventPrevAmt x DamageScalingWC3))))) + !|r))
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • DamageEventAmount Less than -0.99
            • Then - Actions
              • -------- Heal --------
              • Set VariableSet DmgSize = 0.80
              • Set VariableSet DmgDuration = 0.80
              • Set VariableSet DmgStr = (|cff00ff00+ + ((String((Integer((0.00 - DamageEventAmount))))) + !|r))
            • Else - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • DamageScalingUser Greater than or equal to 1.50
                • Then - Actions
                  • -------- User-triggered Critical Strike --------
                  • Set VariableSet DmgSize = 1.50
                  • Set VariableSet DmgDuration = 1.50
                  • Set VariableSet DmgStr = (|cffff0000 + ((String((Integer(DamageEventAmount)))) + !|r))
                • Else - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • DamageEventAmount Greater than 0.99
                    • Then - Actions
                      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                        • If - Conditions
                          • DamageScalingUser Less than 0.60
                        • Then - Actions
                          • -------- User reduced damage --------
                          • Set VariableSet DmgSize = 0.70
                          • Set VariableSet DmgDuration = 0.70
                          • Set VariableSet DmgStr = (|cff808000 + ((String((Integer(DamageEventAmount)))) + |r))
                        • Else - Actions
                          • -------- Normal sequence --------
                          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                            • If - Conditions
                              • IsDamageSpell Equal to True
                            • Then - Actions
                              • Set VariableSet DmgStr = (|c0000FFFF + ((String((Integer(DamageEventAmount)))) + |r))
                            • Else - Actions
                              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                                • If - Conditions
                                  • IsDamageRanged Equal to True
                                • Then - Actions
                                  • Set VariableSet DmgStr = (|cffffff00 + ((String((Integer(DamageEventAmount)))) + |r))
                                • Else - Actions
                                  • Set VariableSet DmgStr = (DmgStr + ((String((Integer(DamageEventAmount)))) + |r))
                    • Else - Actions
      • Custom script: call ArcingTextTag.createEx(udg_DmgStr, udg_DamageEventTarget, udg_DmgDuration, udg_DmgSize, GetLocalPlayer())
  • Arrow Repel Damage
    • Events
    • Conditions
      • (MissileTarget is alive) Equal to True
    • Actions
      • Unit - Cause MissileSource to damage MissileTarget, dealing (MissileDamage + 0.00) damage of attack type Spells and damage type Normal
      • Special Effect - Create a special effect attached to the origin of MissileTarget using Abilities\Spells\Orc\Purge\PurgeBuffTarget.mdl
      • Special Effect - Destroy (Last created special effect)
      • Custom script: call CrowdControl.disarm(udg_MissileTarget, 2, "Disarm Orange.mdl", "overhead", false)
 
Level 10
Joined
Dec 16, 2017
Messages
398
As far as i know, the spell is not casted from a dummy unit, i attach the trigger below.
Yes, it's the only 0 damage spell.
The spell is directly used on the target, like added as an ability and buff on the target, and not an actual cast from a dummy unit.

JASS:
library CrowdControl requires Utilities, WorldBounds, Indexer, TimerUtils, RegisterPlayerUnitEvent, optional Tenacity
    /* ------------------------------------- Crowd Control v1.0 ------------------------------------- */
    // How to Import:
    // 1 - Copy the Utilities library over to your map and follow its install instructions
    // 2 - Copy the WorldBounds library over to your map and follow its install instructions
    // 3 - Copy the Indexer library over to your map and follow its install instructions
    // 4 - Copy the TimerUtils library over to your map and follow its install instructions
    // 5 - Copy the RegisterPlayerUnitEvent library over to your map and follow its install instructions
    // 6 - Copy the Tenacity library over to your map and follow its install instructions
    // 7 - Copy this library into your map
    // 8 - Copy the 14 buffs and 15 abilities with the CC prefix and match their raw code below.
    /* ---------------------------------------- By Chopinski ---------------------------------------- */

    /* ---------------------------------------------------------------------------------------------- */
    /*                                          Configuration                                         */
    /* ---------------------------------------------------------------------------------------------- */
    globals
        // The raw code of the silence ability
        private constant integer SILENCE            = 'A07C'
        // The raw code of the stun ability
        private constant integer STUN               = 'A07K'
        // The raw code of the attack slow ability
        private constant integer ATTACK_SLOW        = 'A062'
        // The raw code of the movement slow ability
        private constant integer MOVEMENT_SLOW      = 'A074'
        // The raw code of the banish ability
        private constant integer BANISH             = 'A063'
        // The raw code of the ensnare ability
        private constant integer ENSNARE            = 'A071'
        // The raw code of the purge ability
        private constant integer PURGE              = 'A075'
        // The raw code of the hex ability
        private constant integer HEX                = 'A073'
        // The raw code of the sleep ability
        private constant integer SLEEP              = 'A07G'
        // The raw code of the cyclone ability
        private constant integer CYCLONE            = 'A06E'
        // The raw code of the entangle ability
        private constant integer ENTANGLE           = 'A06H'
        // The raw code of the disarm ability
        private constant integer DISARM             = 'A06G'
        // The raw code of the fear ability
        private constant integer FEAR               = 'A072'
        // The raw code of the taunt ability
        private constant integer TAUNT              = 'A07L'
        // The raw code of the true sight ability
        private constant integer TRUE_SIGHT         = 'A07P'
        // The raw code of the silence buff
        private constant integer SILENCE_BUFF       = 'B034'
        // The raw code of the stun buff
        private constant integer STUN_BUFF          = 'B036'
        // The raw code of the attack slow buff
        private constant integer ATTACK_SLOW_BUFF   = 'B01L'
        // The raw code of the movement slow buff
        private constant integer MOVEMENT_SLOW_BUFF = 'B032'
        // The raw code of the banish buff
        private constant integer BANISH_BUFF        = 'B028'
        // The raw code of the ensnare buff
        private constant integer ENSNARE_BUFF       = 'B02Y'
        // The raw code of the purge buff
        private constant integer PURGE_BUFF         = 'B033'
        // The raw code of the hex buff
        private constant integer HEX_BUFF           = 'B031'
        // The raw code of the sleep buff
        private constant integer SLEEP_BUFF         = 'B035'
        // The raw code of the cyclone buff
        private constant integer CYCLONE_BUFF       = 'B029'
        // The raw code of the entangle buff
        private constant integer ENTANGLE_BUFF      = 'B02Z'
        // The raw code of the disarm buff
        private constant integer DISARM_BUFF        = 'B02A'
        // The raw code of the fear buff
        private constant integer FEAR_BUFF          = 'B030'
        // The raw code of the taunt buff
        private constant integer TAUNT_BUFF         = 'B037'

        // This is the maximum recursion limit allowed by the system.
        // Its value must be greater than or equal to 0. When equal to 0
        // no recursion is allowed. Values too big can cause screen freezes.
        private constant integer RECURSION_LIMIT    = 8

        // The Crowd Control types
        constant integer CROWD_CONTROL_SILENCE      = 0
        constant integer CROWD_CONTROL_STUN         = 1
        constant integer CROWD_CONTROL_SLOW         = 2
        constant integer CROWD_CONTROL_SLOW_ATTACK  = 3
        constant integer CROWD_CONTROL_BANISH       = 4
        constant integer CROWD_CONTROL_ENSNARE      = 5
        constant integer CROWD_CONTROL_PURGE        = 6
        constant integer CROWD_CONTROL_HEX          = 7
        constant integer CROWD_CONTROL_SLEEP        = 8
        constant integer CROWD_CONTROL_CYCLONE      = 9
        constant integer CROWD_CONTROL_ENTANGLE     = 10
        constant integer CROWD_CONTROL_DISARM       = 11
        constant integer CROWD_CONTROL_FEAR         = 12
        constant integer CROWD_CONTROL_TAUNT        = 13
        constant integer CROWD_CONTROL_KNOCKBACK    = 14
        constant integer CROWD_CONTROL_KNOCKUP      = 15
    endglobals

    /* ---------------------------------------------------------------------------------------------- */
    /*                                            JASS API                                            */
    /* ---------------------------------------------------------------------------------------------- */
    /* ------------------------------------------- Disarm ------------------------------------------- */
    function DisarmUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.disarm(target, duration, model, point, stack)
    endfunction

    function IsUnitDisarmed takes unit target returns boolean
        return CrowdControl.disarmed(target)
    endfunction

    /* -------------------------------------------- Fear -------------------------------------------- */
    function FearUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.fear(target, duration, model, point, stack)
    endfunction

    function IsUnitFeared takes unit target returns boolean
        return CrowdControl.feared(target)
    endfunction

    /* -------------------------------------------- Taunt ------------------------------------------- */
    function TauntUnit takes unit source, unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.taunt(source, target, duration, model, point, stack)
    endfunction

    function IsUnitTaunted takes unit target returns boolean
        return CrowdControl.taunted(target)
    endfunction

    /* ------------------------------------------ Knockback ----------------------------------------- */
    function KnockbackUnit takes unit target, real angle, real distance, real duration, string model, string point, boolean onCliff, boolean onDestructable, boolean onUnit, boolean stack returns nothing
        call CrowdControl.knockback(target, angle, distance, duration, model, point, onCliff, onDestructable, onUnit, stack)
    endfunction
  
    function IsUnitKnockedBack takes unit target returns boolean
        return CrowdControl.knockedback(target)
    endfunction

    /* ------------------------------------------- Knockup ------------------------------------------ */
    function KnockupUnit takes unit target, real maxHeight, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.knockup(target, maxHeight, duration, model, point, stack)
    endfunction

    function IsUnitKnockedUp takes unit target returns boolean
        return CrowdControl.knockedup(target)
    endfunction

    /* ------------------------------------------- Silence ------------------------------------------ */
    function SilenceUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.silence(target, duration, model, point, stack)
    endfunction

    function IsUnitSilenced takes unit target returns boolean
        return CrowdControl.silenced(target)
    endfunction

    /* -------------------------------------------- Stun -------------------------------------------- */
    function StunUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.stun(target, duration, model, point, stack)
    endfunction

    function IsUnitStunned takes unit target returns boolean
        return CrowdControl.stunned(target)
    endfunction

    /* ---------------------------------------- Movement Slow --------------------------------------- */
    function SlowUnit takes unit target, real amount, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.slow(target, amount, duration, model, point, stack)
    endfunction

    function IsUnitSlowed takes unit target returns boolean
        return CrowdControl.slowed(target)
    endfunction

    /* ----------------------------------------- Attack Slow ---------------------------------------- */
    function SlowUnitAttack takes unit target, real amount, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.slowAttack(target, amount, duration, model, point, stack)
    endfunction

    function IsUnitAttackSlowed takes unit target returns boolean
        return CrowdControl.attackSlowed(target)
    endfunction

    /* ------------------------------------------- Banish ------------------------------------------- */
    function BanishUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.banish(target, duration, model, point, stack)
    endfunction

    function IsUnitBanished takes unit target returns boolean
        return CrowdControl.banished(target)
    endfunction

    /* ------------------------------------------- Ensnare ------------------------------------------ */
    function EnsnareUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.ensnare(target, duration, model, point, stack)
    endfunction

    function IsUnitEnsnared takes unit target returns boolean
        return CrowdControl.ensnared(target)
    endfunction

    /* -------------------------------------------- Purge ------------------------------------------- */
    function PurgeUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.purge(target, duration, model, point, stack)
    endfunction

    function IsUnitPurged takes unit target returns boolean
        return CrowdControl.purged(target)
    endfunction

    /* --------------------------------------------- Hex -------------------------------------------- */
    function HexUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.hex(target, duration, model, point, stack)
    endfunction

    function IsUnitHexed takes unit target returns boolean
        return CrowdControl.hexed(target)
    endfunction

    /* -------------------------------------------- Sleep ------------------------------------------- */
    function SleepUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.sleep(target, duration, model, point, stack)
    endfunction

    function IsUnitSleeping takes unit target returns boolean
        return CrowdControl.sleeping(target)
    endfunction

    /* ------------------------------------------- Cyclone ------------------------------------------ */
    function CycloneUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.cyclone(target, duration, model, point, stack)
    endfunction

    function IsUnitCycloned takes unit target returns boolean
        return CrowdControl.cycloned(target)
    endfunction

    /* ------------------------------------------ Entangle ------------------------------------------ */
    function EntangleUnit takes unit target, real duration, string model, string point, boolean stack returns nothing
        call CrowdControl.entangle(target, duration, model, point, stack)
    endfunction

    function IsUnitEntangled takes unit target returns boolean
        return CrowdControl.entangled(target)
    endfunction

    /* ------------------------------------------- Dispel ------------------------------------------- */
    function UnitDispelCrowdControl takes unit target, integer id returns nothing
        call CrowdControl.dispel(target, id)
    endfunction

    function UnitDispelAllCrowdControl takes unit target returns nothing
        call CrowdControl.dispelAll(target)
    endfunction

    /* ------------------------------------------- Events ------------------------------------------- */
    function RegisterCrowdControlEvent takes integer id, code c returns nothing
        call CrowdControl.register(id, c)
    endfunction

    function RegisterAnyCrowdControlEvent takes code c returns nothing
        call CrowdControl.register(-1, c)
    endfunction

    function GetCrowdControlUnit takes nothing returns unit
        return CrowdControl.unit[CrowdControl.key]
    endfunction

    function GetCrowdControlType takes nothing returns integer
        return CrowdControl.type[CrowdControl.key]
    endfunction

    function GetCrowdControlDuration takes nothing returns real
        return CrowdControl.duration[CrowdControl.key]
    endfunction

    function GetCrowdControlAmount takes nothing returns real
        return CrowdControl.amount[CrowdControl.key]
    endfunction

    function GetCrowdControlModel takes nothing returns string
        return CrowdControl.model[CrowdControl.key]
    endfunction

    function GetCrowdControlBone takes nothing returns string
        return CrowdControl.point[CrowdControl.key]
    endfunction

    function GetCrowdControlStack takes nothing returns boolean
        return CrowdControl.stack[CrowdControl.key]
    endfunction

    function GetCrowdControlRemaining takes unit target, integer id returns real
        return CrowdControl.remaining(target, id)
    endfunction

    function GetTauntSource takes nothing returns unit
        return CrowdControl.source[CrowdControl.key]
    endfunction

    function GetKnockbackAngle takes nothing returns real
        return CrowdControl.angle[CrowdControl.key]
    endfunction

    function GetKnockbackDistance takes nothing returns real
        return CrowdControl.distance[CrowdControl.key]
    endfunction

    function GetKnockupHeight takes nothing returns real
        return CrowdControl.height[CrowdControl.key]
    endfunction

    function GetKnockbackOnCliff takes nothing returns boolean
        return CrowdControl.cliff[CrowdControl.key]
    endfunction

    function GetKnockbackOnDestructable takes nothing returns boolean
        return CrowdControl.destructable[CrowdControl.key]
    endfunction

    function GetKnockbackOnUnit takes nothing returns boolean
        return CrowdControl.agent[CrowdControl.key]
    endfunction

    function SetCrowdControlUnit takes unit u returns nothing
        set CrowdControl.unit[CrowdControl.key] = u
    endfunction

    function SetCrowdControlType takes integer id returns nothing
        if id >= CROWD_CONTROL_SILENCE and id <= CROWD_CONTROL_KNOCKUP then
            set CrowdControl.type[CrowdControl.key] = id
        endif
    endfunction

    function SetCrowdControlDuration takes real duration returns nothing
        set CrowdControl.duration[CrowdControl.key] = duration
    endfunction

    function SetCrowdControlAmount takes real amount returns nothing
        set CrowdControl.amount[CrowdControl.key] = amount
    endfunction

    function SetCrowdControlModel takes string model returns nothing
        set CrowdControl.model[CrowdControl.key] = model
    endfunction

    function SetCrowdControlBone takes string point returns nothing
        set CrowdControl.point[CrowdControl.key] = point
    endfunction

    function SetCrowdControlStack takes boolean stack returns nothing
        set CrowdControl.stack[CrowdControl.key] = stack
    endfunction

    function SetTauntSource takes unit u returns nothing
        set CrowdControl.source[CrowdControl.key] = u
    endfunction

    function SetKnockbackAngle takes real angle returns nothing
        set CrowdControl.angle[CrowdControl.key] = angle
    endfunction

    function SetKnockbackDistance takes real distance returns nothing
        set CrowdControl.distance[CrowdControl.key] = distance
    endfunction

    function SetKnockupHeight takes real height returns nothing
        set CrowdControl.height[CrowdControl.key] = height
    endfunction

    function SetKnockbackOnCliff takes boolean onCliff returns nothing
        set CrowdControl.cliff[CrowdControl.key] = onCliff
    endfunction

    function SetKnockbackOnDestructable takes boolean onDestructable returns nothing
        set CrowdControl.destructable[CrowdControl.key] = onDestructable
    endfunction

    function SetKnockbackOnUnit takes boolean onUnit returns nothing
        set CrowdControl.agent[CrowdControl.key] = onUnit
    endfunction

    /* ---------------------------------------------------------------------------------------------- */
    /*                                             Systems                                            */
    /* ---------------------------------------------------------------------------------------------- */
    /* ------------------------------------------ Knockback ----------------------------------------- */
    struct Knockback
        private static timer timer = CreateTimer()
        private static rect rect = Rect(0., 0., 0., 0.)
        private static constant real period = 0.03125
        private static thistype array array
        private static integer array struct
        private static integer key = -1
        private static thistype temp
  
        private unit unit
        private group group
        private real angle
        private real offset
        private real distance
        private real duration
        private real collision
        private integer id
        private effect effect
        private boolean onCliff
        private boolean onDest
        private boolean onUnit
      
        private method remove takes integer i returns integer
            call DestroyGroup(group)
            call DestroyEffect(effect)
            call BlzPauseUnitEx(unit, false)

            set unit = null
            set group = null
            set effect = null
            set struct[id] = 0
            set array[i] = array[key]
            set key = key - 1

            call deallocate()

            if key == -1 then
                call PauseTimer(timer)
            endif

            return i - 1
        endmethod

        private static method onDestructable takes nothing returns nothing
            local thistype this = temp

            if GetDestructableLife(GetEnumDestructable()) > 0 then
                set duration = 0
                return
            endif
        endmethod

        private static method onPeriod takes nothing returns nothing
            local integer i = 0
            local thistype this
            local real x
            local real y
            local unit u

            loop
                exitwhen i > key
                    set this = array[i]

                    if duration > 0 and UnitAlive(unit) then
                        set duration = duration - period
                        set x = GetUnitX(unit) + offset*Cos(angle)
                        set y = GetUnitY(unit) + offset*Sin(angle)
                      
                        if onUnit and collision > 0 then
                            call GroupEnumUnitsInRange(group, x, y, collision, null)
                            call GroupRemoveUnit(group, unit)

                            loop
                                set u = FirstOfGroup(group)
                                exitwhen u == null
                                    if UnitAlive(u) then
                                        set duration = 0
                                        set u = null
                                        exitwhen true
                                    endif
                                call GroupRemoveUnit(group, u)
                            endloop
                        endif

                        if onDest and duration > 0 and collision > 0 then
                            set temp = this
                            call SetRect(rect, x - collision, y - collision, x + collision, y + collision)
                            call EnumDestructablesInRect(rect, null, function thistype.onDestructable)
                        endif

                        if onCliff and duration > 0 then
                            if GetTerrainCliffLevel(GetUnitX(unit), GetUnitY(unit)) < GetTerrainCliffLevel(x, y) and GetUnitZ(unit) < (GetTerrainCliffLevel(x, y) - GetTerrainCliffLevel(WorldBounds.maxX, WorldBounds.maxY))*bj_CLIFFHEIGHT then
                                set duration = 0
                            endif
                        endif

                        if duration > 0 then
                            call SetUnitX(unit, x)
                            call SetUnitY(unit, y)
                        endif
                    else
                        set i = remove(i)
                    endif
                set i = i + 1
            endloop
        endmethod

        static method knocked takes unit u returns boolean
            return struct[GetUnitUserData(u)] != 0
        endmethod
      
        static method apply takes unit target, real angle, real distance, real duration, string model, string point, boolean onCliff, boolean onDestructable, boolean onUnit returns nothing
            local integer id = GetUnitUserData(target)
            local thistype this

            if duration > 0 and UnitAlive(target) then
                if struct[id] != 0 then
                    set this = struct[id]
                else
                    set this = thistype.allocate()
                    set .id = id
                    set .unit = target
                    set .collision = 2*BlzGetUnitCollisionSize(target)
                    set .group = CreateGroup()
                    set key = key + 1
                    set array[key] = this
                    set struct[id] = this

                    call BlzPauseUnitEx(target, true)

                    if model != null and point != null then
                        set effect = AddSpecialEffectTarget(model, target, point)
                    endif

                    if key == 0 then
                        call TimerStart(timer, period, true, function thistype.onPeriod)
                    endif
                endif

                set .angle = angle
                set .distance = distance
                set .duration = duration
                set .onCliff = onCliff
                set .onDest = onDestructable
                set .onUnit = onUnit
                set .offset = RMaxBJ(0.00000001, distance*period/RMaxBJ(0.00000001, duration))
            endif
        endmethod
    endstruct

    /* ------------------------------------------- Knockup ------------------------------------------ */
    struct Knockup
        private static integer array knocked

        timer timer
        unit unit
        effect effect
        integer key
        boolean up
        real rate
        real airTime

        static method isUnitKnocked takes unit u returns boolean
            return knocked[GetUnitUserData(u)] > 0
        endmethod

        private static method onPeriod takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())

            if up then
                set up = false
                call SetUnitFlyHeight(unit, GetUnitDefaultFlyHeight(unit), rate)
                call TimerStart(timer, airTime/2, false, function thistype.onPeriod)
            else
                call DestroyEffect(effect)
                call ReleaseTimer(timer)
                call deallocate()

                set knocked[key] = knocked[key] - 1

                if knocked[key] == 0 then
                    call BlzPauseUnitEx(unit, false)
                endif

                set timer = null
                set unit = null
                set effect = null
            endif
        endmethod

        static method apply takes unit whichUnit, real airTime, real maxHeight, string model, string point returns nothing
            local thistype this

            if airTime > 0 then
                set this = thistype.allocate()
                set timer = NewTimerEx(this)
                set unit = whichUnit
                set rate = maxHeight/airTime
                set .airTime = airTime
                set up = true
                set key = GetUnitUserData(unit)
                set knocked[key] = knocked[key] + 1

                if model != null and point != null then
                    set effect = AddSpecialEffectTarget(model, unit, point)
                endif

                if knocked[key] == 1 then
                    call BlzPauseUnitEx(whichUnit, true)
                endif

                call UnitAddAbility(unit, 'Amrf')
                call UnitRemoveAbility(unit, 'Amrf')
                call SetUnitFlyHeight(unit, (GetUnitDefaultFlyHeight(unit) + maxHeight), rate)
                call TimerStart(timer, airTime/2, false, function thistype.onPeriod)
            endif
        endmethod
    endstruct

    /* -------------------------------------------- Fear -------------------------------------------- */
    struct Fear
        private static timer timer = CreateTimer()
        private static constant integer DIRECTION_CHANGE = 5
        private static constant real MAX_CHANGE = 200.
        private static constant real PERIOD = 1./5.
        private static integer key = -1
        private static thistype array array
        private static integer array struct
        private static boolean array flag
        private static real array x
        private static real array y
        private static ability ability
        private static unit dummy

        unit unit
        effect effect
        integer id
        integer change

        static method feared takes unit target returns boolean
            return GetUnitAbilityLevel(target, FEAR_BUFF) > 0
        endmethod

        method remove takes integer i returns integer
            set flag[id] = true
            call IssueImmediateOrder(unit, "stop")
            call DestroyEffect(effect)

            set struct[id] = 0
            set unit = null
            set effect = null
            set array[i] = array[key]
            set key = key - 1

            call deallocate()

            if key == -1 then
                call PauseTimer(timer)
            endif

            return i - 1
        endmethod

        private static method onPeriod takes nothing returns nothing
            local integer i = 0
            local thistype this

            loop
                exitwhen i > key
                    set this = array[i]

                    if GetUnitAbilityLevel(unit, FEAR_BUFF) > 0 then
                        set change = change + 1

                        if change >= DIRECTION_CHANGE then
                            set change = 0
                            set flag[id] = true
                            set x[id] = GetRandomReal(GetUnitX(unit) - MAX_CHANGE, GetUnitX(unit) + MAX_CHANGE)
                            set y[id] = GetRandomReal(GetUnitY(unit) - MAX_CHANGE, GetUnitY(unit) + MAX_CHANGE)
                            call IssuePointOrder(unit, "move", x[id], y[id])
                        endif
                    else
                        set i = remove(i)
                    endif
                set i = i + 1
            endloop
        endmethod

        static method apply takes unit whichUnit, real duration, string model, string point returns nothing
            local integer id = GetUnitUserData(whichUnit)
            local thistype this

            if duration > 0 then
                call BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_NORMAL, 0, duration)
                call BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, duration)
                call IncUnitAbilityLevel(dummy, FEAR)
                call DecUnitAbilityLevel(dummy, FEAR)

                if IssueTargetOrder(dummy, "drunkenhaze", whichUnit) then
                    if struct[id] != 0 then
                        set this = struct[id]
                    else
                        set this = thistype.allocate()
                        set .id = id
                        set unit = whichUnit
                        set change = 0
                        set key = key + 1
                        set array[key] = this
                        set struct[id] = this
  
                        if model != null and point != null then
                            set effect = AddSpecialEffectTarget(model, whichUnit, point)
                        endif
  
                        if key == 0 then
                            call TimerStart(timer, PERIOD, true, function thistype.onPeriod)
                        endif
                    endif
  
                    set flag[id] = true
                    set x[id] = GetRandomReal(GetUnitX(whichUnit) - MAX_CHANGE, GetUnitX(whichUnit) + MAX_CHANGE)
                    set y[id] = GetRandomReal(GetUnitY(whichUnit) - MAX_CHANGE, GetUnitY(whichUnit) + MAX_CHANGE)
                    call IssuePointOrder(whichUnit, "move", x[id], y[id])
                endif
            endif
        endmethod

        private static method onOrder takes nothing returns nothing
            local unit source = GetOrderedUnit()
            local integer id

            if feared(source) and GetIssuedOrderId() != 851973 then
                set id = GetUnitUserData(source)

                if not flag[id] then
                    set flag[id] = true
                    call IssuePointOrder(source, "move", x[id], y[id])
                else
                    set flag[id] = false
                endif
            endif

            set source = null
        endmethod

        private static method onInit takes nothing returns nothing
            set dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0)

            call UnitAddAbility(dummy, TRUE_SIGHT)
            call UnitAddAbility(dummy, FEAR)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function thistype.onOrder)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function thistype.onOrder)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.onOrder)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, function thistype.onOrder)

            set ability = BlzGetUnitAbility(dummy, FEAR)
        endmethod
    endstruct

    /* -------------------------------------------- Taunt ------------------------------------------- */
    struct Taunt
        private static constant real PERIOD = 0.2
        private static unit dummy
        private static integer key = -1
        private static thistype array array
        private static integer array struct
        private static timer timer = CreateTimer()
        private static ability ability

        readonly static unit array source

        unit unit
        effect effect
        integer id
        boolean selected

        static method taunted takes unit target returns boolean
            return GetUnitAbilityLevel(target, TAUNT_BUFF) > 0
        endmethod

        method remove takes integer i returns integer
            call UnitDispelCrowdControl(unit, CROWD_CONTROL_TAUNT)
            call IssueImmediateOrder(unit, "stop")
            call DestroyEffect(effect)

            if selected and UnitAlive(unit) then
                call SelectUnitAddForPlayer(unit, GetOwningPlayer(unit))
            endif

            set struct[id] = 0
            set source[id] = null
            set unit = null
            set effect = null
            set array[i] = array[key]
            set key = key - 1

            call deallocate()

            if key == -1 then
                call PauseTimer(timer)
            endif

            return i - 1
        endmethod

        private static method onPeriod takes nothing returns nothing
            local integer i = 0
            local thistype this

            loop
                exitwhen i > key
                    set this = array[i]

                    if GetUnitAbilityLevel(unit, TAUNT_BUFF) > 0 and UnitAlive(source[id]) and UnitAlive(unit) then
                        if IsUnitVisible(source[id],  GetOwningPlayer(unit)) then
                            call IssueTargetOrderById(unit, 851983, source[id])
                        else
                            call IssuePointOrderById(unit, 851986, GetUnitX(source[id]), GetUnitY(source[id]))
                        endif
                    else
                        set i = remove(i)
                    endif
                set i = i + 1
            endloop
        endmethod

        static method apply takes unit source, unit target, real duration, string model, string point returns nothing
            local integer id = GetUnitUserData(target)
            local thistype this

            if duration > 0 and UnitAlive(source) and UnitAlive(target) then
                call BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_NORMAL, 0, duration)
                call BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, duration)
                call IncUnitAbilityLevel(dummy, TAUNT)
                call DecUnitAbilityLevel(dummy, TAUNT)

                if IssueTargetOrder(dummy, "drunkenhaze", target) then
                    if struct[id] != 0 then
                        set this = struct[id]
                    else
                        set this = thistype.allocate()
                        set .id = id
                        set unit = target
                        set selected = IsUnitSelected(target, GetOwningPlayer(target))
                        set key = key + 1
                        set array[key] = this
                        set struct[id] = this
  
                        if selected then
                            call SelectUnit(target, false)
                        endif

                        if model != null and point != null then
                            set effect = AddSpecialEffectTarget(model, target, point)
                        endif
  
                        if key == 0 then
                            call TimerStart(timer, PERIOD, true, function thistype.onPeriod)
                        endif
                    endif

                    set .source[id] = source
                  
                    if IsUnitVisible(source, GetOwningPlayer(target)) then
                        call IssueTargetOrderById(target, 851983, source)
                    else
                        call IssuePointOrderById(target, 851986, GetUnitX(source), GetUnitY(source))
                    endif
                endif
            endif
        endmethod

        private static method onOrder takes nothing returns nothing
            local unit target = GetOrderedUnit()
            local integer order = GetIssuedOrderId()
            local integer id
          
            if taunted(target) and order != 851973 then
                set id = GetUnitUserData(target)

                if order != 851983 and order != 851986 then
                    if IsUnitVisible(source[id],  GetOwningPlayer(target)) then
                        call IssueTargetOrderById(target, 851983, source[id])
                    else
                        call IssuePointOrderById(target, 851986, GetUnitX(source[id]), GetUnitY(source[id]))
                    endif
                else
                    if GetOrderTargetUnit() != source[id] and GetOrderTargetUnit() != null then
                        if IsUnitVisible(source[id],  GetOwningPlayer(target)) then
                            call IssueTargetOrderById(target, 851983, source[id])
                        else
                            call IssuePointOrderById(target, 851986, GetUnitX(source[id]), GetUnitY(source[id]))
                        endif
                    endif
                endif
            endif

            set target = null
        endmethod

        private static method onSelect takes nothing returns nothing
            local unit target = GetTriggerUnit()
          
            if taunted(target) then
                if IsUnitSelected(target, GetOwningPlayer(target)) then
                    call SelectUnit(target, false)
                endif
            endif
          
            set target = null
        endmethod

        private static method onInit takes nothing returns nothing
            set dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0)

            call UnitAddAbility(dummy, TRUE_SIGHT)
            call UnitAddAbility(dummy, TAUNT)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function thistype.onOrder)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function thistype.onOrder)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.onOrder)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_UNIT_ORDER, function thistype.onOrder)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SELECTED, function thistype.onSelect)

            set ability = BlzGetUnitAbility(dummy, TAUNT)
        endmethod
    endstruct

    /* ---------------------------------------- Crowd Control --------------------------------------- */
    struct CrowdControl extends array
        private static trigger trigger = CreateTrigger()
        private static hashtable timer = InitHashtable()
        private static trigger array event
        private static integer array ability
        private static integer array buff
        private static string array order
        private static integer count = 0
        private static unit dummy

        readonly static integer key = -1
      
        static unit array unit
        static unit array source
        static real array amount
        static real array duration
        static real array angle
        static real array distance
        static real array height
        static string array model
        static string array point
        static boolean array stack
        static boolean array cliff
        static boolean array destructable
        static boolean array agent
        static integer array type

        private static method onInit takes nothing returns nothing
            set dummy = DummyRetrieve(Player(PLAYER_NEUTRAL_PASSIVE), GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()), 0, 0) 
          
            call UnitAddAbility(dummy, SILENCE)
            call UnitAddAbility(dummy, STUN)
            call UnitAddAbility(dummy, ATTACK_SLOW)
            call UnitAddAbility(dummy, MOVEMENT_SLOW)
            call UnitAddAbility(dummy, BANISH)
            call UnitAddAbility(dummy, ENSNARE)
            call UnitAddAbility(dummy, PURGE)
            call UnitAddAbility(dummy, HEX)
            call UnitAddAbility(dummy, SLEEP)
            call UnitAddAbility(dummy, CYCLONE)
            call UnitAddAbility(dummy, ENTANGLE)
            call UnitAddAbility(dummy, DISARM)
            call UnitAddAbility(dummy, TRUE_SIGHT)

            call BlzUnitDisableAbility(dummy, SILENCE, true, true)
            call BlzUnitDisableAbility(dummy, STUN, true, true)
            call BlzUnitDisableAbility(dummy, ATTACK_SLOW, true, true)
            call BlzUnitDisableAbility(dummy, MOVEMENT_SLOW, true, true)
            call BlzUnitDisableAbility(dummy, BANISH, true, true)
            call BlzUnitDisableAbility(dummy, ENSNARE, true, true)
            call BlzUnitDisableAbility(dummy, PURGE, true, true)
            call BlzUnitDisableAbility(dummy, HEX, true, true)
            call BlzUnitDisableAbility(dummy, SLEEP, true, true)
            call BlzUnitDisableAbility(dummy, CYCLONE, true, true)
            call BlzUnitDisableAbility(dummy, ENTANGLE, true, true)
            call BlzUnitDisableAbility(dummy, DISARM, true, true)

            set ability[CROWD_CONTROL_SILENCE] = SILENCE
            set ability[CROWD_CONTROL_STUN] = STUN
            set ability[CROWD_CONTROL_SLOW] = MOVEMENT_SLOW
            set ability[CROWD_CONTROL_SLOW_ATTACK] = ATTACK_SLOW
            set ability[CROWD_CONTROL_BANISH] = BANISH
            set ability[CROWD_CONTROL_ENSNARE] = ENSNARE
            set ability[CROWD_CONTROL_PURGE] = PURGE
            set ability[CROWD_CONTROL_HEX] = HEX
            set ability[CROWD_CONTROL_SLEEP] = SLEEP
            set ability[CROWD_CONTROL_CYCLONE] = CYCLONE
            set ability[CROWD_CONTROL_ENTANGLE] = ENTANGLE
            set ability[CROWD_CONTROL_DISARM] = DISARM
            set ability[CROWD_CONTROL_FEAR] = FEAR
            set ability[CROWD_CONTROL_TAUNT] = TAUNT

            set buff[CROWD_CONTROL_SILENCE] = SILENCE_BUFF
            set buff[CROWD_CONTROL_STUN] = STUN_BUFF
            set buff[CROWD_CONTROL_SLOW] = MOVEMENT_SLOW_BUFF
            set buff[CROWD_CONTROL_SLOW_ATTACK] = ATTACK_SLOW_BUFF
            set buff[CROWD_CONTROL_BANISH] = BANISH_BUFF
            set buff[CROWD_CONTROL_ENSNARE] = ENSNARE_BUFF
            set buff[CROWD_CONTROL_PURGE] = PURGE_BUFF
            set buff[CROWD_CONTROL_HEX] = HEX_BUFF
            set buff[CROWD_CONTROL_SLEEP] = SLEEP_BUFF
            set buff[CROWD_CONTROL_CYCLONE] = CYCLONE_BUFF
            set buff[CROWD_CONTROL_ENTANGLE] = ENTANGLE_BUFF
            set buff[CROWD_CONTROL_DISARM] = DISARM_BUFF
            set buff[CROWD_CONTROL_FEAR] = FEAR_BUFF
            set buff[CROWD_CONTROL_TAUNT] = TAUNT_BUFF

            set order[CROWD_CONTROL_SILENCE] = "drunkenhaze"
            set order[CROWD_CONTROL_STUN] = "thunderbolt"
            set order[CROWD_CONTROL_SLOW] = "cripple"
            set order[CROWD_CONTROL_SLOW_ATTACK] = "cripple"
            set order[CROWD_CONTROL_BANISH] = "banish"
            set order[CROWD_CONTROL_ENSNARE] = "ensnare"
            set order[CROWD_CONTROL_PURGE] = "purge"
            set order[CROWD_CONTROL_HEX] = "hex"
            set order[CROWD_CONTROL_SLEEP] = "sleep"
            set order[CROWD_CONTROL_CYCLONE] = "cyclone"
            set order[CROWD_CONTROL_ENTANGLE] = "entanglingroots"
            set order[CROWD_CONTROL_DISARM] = "drunkenhaze"
        endmethod

        private static method onExpire takes nothing returns nothing
            local timer t = GetExpiredTimer()

            call RemoveSavedHandle(timer, GetHandleId(LoadUnitHandle(timer, GetHandleId(t), 0)), LoadInteger(timer, GetHandleId(t), 1))
            call FlushChildHashtable(timer, GetHandleId(t))
            call DestroyTimer(t)

            set t = null
        endmethod

        private static method onEvent takes integer key returns nothing
            local integer i = 0
            local integer next = -1
            local integer prev = -2

            set count = count + 1

            if count - CROWD_CONTROL_KNOCKUP < RECURSION_LIMIT then
                loop
                    exitwhen type[key] == next or (i - CROWD_CONTROL_KNOCKUP > RECURSION_LIMIT)
                        set next = type[key]
  
                        if event[next] != null then
                            call TriggerEvaluate(event[next])
                        endif

                        if type[key] != next then
                            set i = i + 1
                        else
                            if next != prev then
                                call TriggerEvaluate(trigger)

                                if type[key] != next then
                                    set i = i + 1
                                    set prev = next
                                endif
                            endif
                        endif
                endloop
            endif
          
            set count = count - 1
            set .key = key
        endmethod

        private static method cast takes unit source, unit target, real amount, real angle, real distance, real height, real duration, string model, string point, boolean stack, boolean onCliff, boolean onDestructable, boolean onUnit, integer id returns nothing
            local ability spell
            local timer t
  
            if not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) and UnitAlive(target) and duration > 0 then
                set key = key + 1
                set .unit[key] = target
                set .source[key] = source
                set .amount[key] = amount
                set .angle[key] = angle
                set .distance[key] = distance
                set .height[key] = height
                set .duration[key] = duration
                set .model[key] = model
                set .point[key] = point
                set .stack[key] = stack
                set .cliff[key] = onCliff
                set .destructable[key] = onDestructable
                set .agent[key] = onUnit
                set .type[key] = id

                call onEvent(key)
      
                static if LIBRARY_Tenacity then
                    set .duration[key] = GetTenacityDuration(unit[key], .duration[key])
                endif

                if .duration[key] > 0 and UnitAlive(unit[key]) then
                    if not HaveSavedHandle(timer, GetHandleId(unit[key]), type[key]) then
                        set t = CreateTimer()
                        call SaveTimerHandle(timer, GetHandleId(unit[key]), type[key], t)
                        call SaveUnitHandle(timer, GetHandleId(t), 0, unit[key])
                        call SaveInteger(timer, GetHandleId(t), 1, type[key])
                    endif

                    if .stack[key] then
                        if type[key] != CROWD_CONTROL_TAUNT then
                            set .duration[key] = .duration[key] + TimerGetRemaining(LoadTimerHandle(timer, GetHandleId(unit[key]), type[key]))
                        else
                            if Taunt.source[GetUnitUserData(unit[key])] == .source[key] then
                                set .duration[key] = .duration[key] + TimerGetRemaining(LoadTimerHandle(timer, GetHandleId(unit[key]), type[key]))
                            endif
                        endif
                    endif

                    if type[key] != CROWD_CONTROL_FEAR and type[key] != CROWD_CONTROL_TAUNT and type[key] != CROWD_CONTROL_KNOCKBACK and type[key] != CROWD_CONTROL_KNOCKUP then
                        set spell = BlzGetUnitAbility(dummy, ability[type[key]])

                        call BlzUnitDisableAbility(dummy, ability[type[key]], false, false)
                        call BlzSetAbilityRealLevelField(spell, ABILITY_RLF_DURATION_NORMAL, 0, .duration[key])
                        call BlzSetAbilityRealLevelField(spell, ABILITY_RLF_DURATION_HERO, 0, .duration[key])

                        if type[key] == CROWD_CONTROL_SLOW then
                            call BlzSetAbilityRealLevelField(spell, ABILITY_RLF_MOVEMENT_SPEED_REDUCTION_PERCENT_CRI1, 0, .amount[key])
                        elseif type[key] == CROWD_CONTROL_SLOW_ATTACK then
                            call BlzSetAbilityRealLevelField(spell, ABILITY_RLF_ATTACK_SPEED_REDUCTION_PERCENT_CRI2, 0, .amount[key])
                        endif

                        call IncUnitAbilityLevel(dummy, ability[type[key]])
                        call DecUnitAbilityLevel(dummy, ability[type[key]])

                        if IssueTargetOrder(dummy, order[type[key]], unit[key]) then
                            call UnitRemoveAbility(unit[key], buff[type[key]])
                            call IssueTargetOrder(dummy, order[type[key]], unit[key])
                            call TimerStart(LoadTimerHandle(timer, GetHandleId(unit[key]), type[key]), .duration[key], false, function thistype.onExpire)

                            if .model[key] != null and .model[key] != "" then
                                if .point[key] != null and .point[key] != "" then
                                    call LinkEffectToBuff(unit[key], buff[type[key]], .model[key], .point[key])
                                else
                                    call DestroyEffect(AddSpecialEffect(.model[key], GetUnitX(unit[key]), GetUnitY(unit[key])))
                                endif
                            endif
                        else
                            call RemoveSavedHandle(timer, GetHandleId(unit[key]), type[key])
                            call FlushChildHashtable(timer, GetHandleId(t))
                            call DestroyTimer(t)
                        endif

                        call BlzUnitDisableAbility(dummy, ability[type[key]], true, true)
                    else
                        if type[key] == CROWD_CONTROL_FEAR then
                            call Fear.apply(unit[key], .duration[key], .model[key], .point[key])
                        elseif type[key] == CROWD_CONTROL_TAUNT then
                            call Taunt.apply(.source[key], unit[key], .duration[key], .model[key], .point[key])
                        elseif type[key] == CROWD_CONTROL_KNOCKBACK then
                            call Knockback.apply(unit[key], .angle[key], .distance[key], .duration[key], .model[key], .point[key], .cliff[key], .destructable[key], .agent[key])
                        elseif type[key] == CROWD_CONTROL_KNOCKUP then
                            call Knockup.apply(unit[key], .duration[key], .height[key], .model[key], .point[key])
                        endif

                        call TimerStart(LoadTimerHandle(timer, GetHandleId(unit[key]), type[key]), .duration[key], false, function thistype.onExpire)
                    endif
                endif
  
                if key > -1 then
                    set key = key - 1
                endif
            endif
  
            set t = null
            set spell = null
        endmethod

        static method silence takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SILENCE)
        endmethod
  
        static method silenced takes unit target returns boolean
            return GetUnitAbilityLevel(target, SILENCE_BUFF) > 0
        endmethod

        static method stun takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_STUN)
        endmethod
  
        static method stunned takes unit target returns boolean
            return GetUnitAbilityLevel(target, STUN_BUFF) > 0
        endmethod

        static method slow takes unit target, real amount, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, amount, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLOW)
        endmethod
  
        static method slowed takes unit target returns boolean
            return GetUnitAbilityLevel(target, MOVEMENT_SLOW_BUFF) > 0
        endmethod

        static method slowAttack takes unit target, real amount, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, amount, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLOW_ATTACK)
        endmethod
  
        static method attackSlowed takes unit target returns boolean
            return GetUnitAbilityLevel(target, ATTACK_SLOW_BUFF) > 0
        endmethod

        static method banish takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_BANISH)
        endmethod
  
        static method banished takes unit target returns boolean
            return GetUnitAbilityLevel(target, BANISH_BUFF) > 0
        endmethod

        static method ensnare takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_ENSNARE)
        endmethod
  
        static method ensnared takes unit target returns boolean
            return GetUnitAbilityLevel(target, ENSNARE_BUFF) > 0
        endmethod

        static method purge takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_PURGE)
        endmethod
  
        static method purged takes unit target returns boolean
            return GetUnitAbilityLevel(target, PURGE_BUFF) > 0
        endmethod

        static method hex takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_HEX)
        endmethod
  
        static method hexed takes unit target returns boolean
            return GetUnitAbilityLevel(target, HEX_BUFF) > 0
        endmethod

        static method sleep takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_SLEEP)
        endmethod
  
        static method sleeping takes unit target returns boolean
            return GetUnitAbilityLevel(target, SLEEP_BUFF) > 0
        endmethod

        static method cyclone takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_CYCLONE)
        endmethod
  
        static method cycloned takes unit target returns boolean
            return GetUnitAbilityLevel(target, CYCLONE_BUFF) > 0
        endmethod

        static method entangle takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_ENTANGLE)
        endmethod
  
        static method entangled takes unit target returns boolean
            return GetUnitAbilityLevel(target, ENTANGLE_BUFF) > 0
        endmethod
      
        static method knockback takes unit target, real angle, real distance, real duration, string model, string point, boolean onCliff, boolean onDestructable, boolean onUnit, boolean stack returns nothing
            call cast(null, target, 0, angle, distance, 0, duration, model, point, stack, onCliff, onDestructable, onUnit, CROWD_CONTROL_KNOCKBACK)
        endmethod
  
        static method knockedback takes unit target returns boolean
            return Knockback.knocked(target)
        endmethod

        static method knockup takes unit target, real maxHeight, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, maxHeight, duration, model, point, stack, false, false, false, CROWD_CONTROL_KNOCKUP)
        endmethod
  
        static method knockedup takes unit target returns boolean
            return Knockup.isUnitKnocked(target)
        endmethod

        static method fear takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_FEAR)
        endmethod
  
        static method feared takes unit target returns boolean
            return Fear.feared(target)
        endmethod

        static method disarm takes unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(null, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_DISARM)
        endmethod
  
        static method disarmed takes unit target returns boolean
            return GetUnitAbilityLevel(target, DISARM_BUFF) > 0
        endmethod

        static method taunt takes unit source, unit target, real duration, string model, string point, boolean stack returns nothing
            call cast(source, target, 0, 0, 0, 0, duration, model, point, stack, false, false, false, CROWD_CONTROL_TAUNT)
        endmethod
  
        static method taunted takes unit target returns boolean
            return Taunt.taunted(target)
        endmethod

        static method dispel takes unit target, integer id returns nothing
            local timer t

            if buff[id] != 0 then
                call UnitRemoveAbility(target, buff[id])

                if HaveSavedHandle(timer, GetHandleId(target), id) then
                    set t = LoadTimerHandle(timer, GetHandleId(target), id)
                    call RemoveSavedHandle(timer, GetHandleId(target), id)
                    call FlushChildHashtable(timer, GetHandleId(t))
                    call DestroyTimer(t)
                endif
            endif

            set t = null
        endmethod

        static method dispelAll takes unit target returns nothing
            call dispel(target, CROWD_CONTROL_SILENCE)
            call dispel(target, CROWD_CONTROL_STUN)
            call dispel(target, CROWD_CONTROL_SLOW)
            call dispel(target, CROWD_CONTROL_SLOW_ATTACK)
            call dispel(target, CROWD_CONTROL_BANISH)
            call dispel(target, CROWD_CONTROL_ENSNARE)
            call dispel(target, CROWD_CONTROL_PURGE)
            call dispel(target, CROWD_CONTROL_HEX)
            call dispel(target, CROWD_CONTROL_SLEEP)
            call dispel(target, CROWD_CONTROL_CYCLONE)
            call dispel(target, CROWD_CONTROL_ENTANGLE)
            call dispel(target, CROWD_CONTROL_DISARM)
            call dispel(target, CROWD_CONTROL_FEAR)
            call dispel(target, CROWD_CONTROL_TAUNT)
        endmethod

        static method remaining takes unit target, integer id returns real
            return TimerGetRemaining(LoadTimerHandle(timer, GetHandleId(target), id))
        endmethod

        static method register takes integer id, code c returns nothing
            if id >= CROWD_CONTROL_SILENCE and id <= CROWD_CONTROL_KNOCKUP then
                if event[id] == null then
                    set event[id] = CreateTrigger()
                endif
                call TriggerAddCondition(event[id], Filter(c))
            else
                call TriggerAddCondition(trigger, Filter(c))
            endif
        endmethod
    endstruct
endlibrary
 
Top