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

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...

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Since my last flowchart was so well-received, here's the general flow of Damage Engine 3.8 (in retrospect):


1
A unit enters the game and is processed by GUI Unit Indexer or GUI Unit Event.

→ →


2
Add an event for the unit to the monolithic DamageEventTrigger and add the Detect Spell Damage Ability to it.

→ →


3
WarCraft 3 detects incoming damage, distributes it into Spirit Link/Mana Shield, then applies armor type & amount

→ ↓


↓ ←


6
If any recursive damage is detected from any of those events, it is executed immediately. Any recursive events from those recursive events are also executed immediately.

← ←

5
Damage Engine deploys the DamageModifierEvents 1.00 through 3.00

← ←

4
The EVENT_UNIT_DAMAGED event runs. If the damage is negative, flag IsDamageSpell as True and invert the damage back to a positive.

7
If DamageEventAmount is greater than 0.00, run the DamageModifierEvent 4.00. Same old recursive events apply as with 1.00-3.00.

→ →


8
Now that DamageEventAmount is finalized, run DamageEvent 1.00. Same old recursive events apply as with DamageModifier events.

→ →


9
The user can access DamageEventPrevAmount if they want to know the damage amount before user modifications (before in-game modifications was impossible to know).

→ ↓


↓ ←


12
Set a flag to indicate the removal of the Cheat Death Ability and start a timer as a failsafe.

← ←

11
If the target’s maximum HP is too low to handle the incoming damage, give it the Cheat Death Ability +500,000

← ←

10
Compensate for (DamageEventAmount - Life of DamageEventTarget) by increasing or decreasing the target unit’s HP.

13
The timer expires/a new damage event is run/the UNIT_STATE_EVENT detects the HP change/the user calls ClearDamageEvent (whichever happens first)

→ →


14
If the unit was wielding the Cheat Death Ability, remove it.

→ →


15
If the damage was above 0.00, run AfterDamageEvent 1.00. Same old recursive events apply as with all previous events.

→ ↓


√


18
Once enough units have left, rebuilt the massive DamageEventTrigger and re-add events only for active units.

← ←

17
When a unit is removed from the game, increment a tracking integer to know how many units have been removed.

← ←

16
Once the timer expires (if it hadn’t already) or if a new DamageEventSource was applied, run AOEDamageEvent 1.00.
 
Last edited:
Level 4
Joined
Sep 29, 2018
Messages
74
I'm just gonna leave this here to show what a journey it has been to get Damage Engine set up with all the new mechanics:


1
Unit attacks or casts a spell

→ →


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 DamageModifierEvents 1.00 through 3.00

7
WarCraft 3 processes the user damage.

→ →


8
WarCraft 3 then distributes the processed damage into Spirit Link/Mana Shield, then applies armor type & amount

→ →


9
WarCraft 3 runs any interrupting events, such as spirit link or defensive damage like Thorns Aura

→ ↓


↓ ←


12
Damage Engine will either keep using the original variables with their values, or will recover the original damage values in the event of recursive Spirit Link/defensive retaliating damage.

← ←

11
Once any potential recursive WarCraft 3 damage is processed, the EVENT_UNIT_DAMAGED event runs. This is the original event we always had access to.

← ←

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, DamageModifierEvent 4.00 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 DamageEvent 1.00. If any recursive damage is detected, it is postponed.

→ →


20
Run all potential recursive Unit - Damage Target function calls in chronological order (first in, first out).

→ →


21
After a 0.00 second timer expires, run the AOEDamageEvent (if multiple units were hit by the same source in the same instant).

√

lovin this thanks
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Coming next in 5.2: Defense and armor type detection and (maybe) modification.

I have a working model, but I'm unsure if this system is best suited for modifying those things. If I do, it's going to only affect the current damage instance and then set each to their previous values (ie. changing DamageEventAttackT doesn't change the unit's attack type but DOES change it for the current damage.

The update will also very likely return AfterDamageEvent to the system. I need to be able to allow the user to delay damage until after the first damage has been applied to the unit.

Lastly, after carefully reviewing the previous posts in this thread, the one suggestion that I went over that hasn't been addressed in 5.1, is Evasion. Triggered evasion is never really going to work right, because on-hit effects will still proc. Also, you can turn off the on-hit sound of melee attacks by changing DamageEventWeaponT to WEAPON_TYPE_NONE, however you cannot change the audio nor visual of the projectile impacting on the target unit. Lastly, you can't even detect if a WarCraft 3 attack was actually evaded (without using some heavily complex attack indexing system with instant projectiles).

Maybe with a future WarCraft 3 update we'll have access to explore the mechanics of Evasion and how it interacts with the DDS, but in the mean time I can only provide more access for GUI users with regards to detection and modification of in-instance armor & defense types, as well as a very likely resurrection of the AfterDamageEvent.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Damage Engine 5.2.0.0 is here - this is the last foreseeable innovative update to Damage Engine. This release is stable (as is 5.1.3.1), but non-essential. Changes are:
  • You can now read/modify DamageEventArmorT/DefenseT. Please have a look at the Damage Engine Config trigger to understand what these two values can be. Armor types are things like Flesh/Stone, whereas Defense Types are things like Hero, Unarmored, Fortified.
  • Re-introduced AfterDamageEvent, which kicks in after the target unit's HP has been reduced.
  • Recursive damage is now dealt after the after damage event, instead of after the damage event, which has only one minor hero XP implication in the event of retaliating damage from one to the other where both damage instances are lethal.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I've added FAQs to address what I recall are the things that have been asked the most. I've also included steps on how to upgrade from one version of Damage Engine to the next, as there has typically been a pattern of how I decide which integer gets incremented with each update.

I've also spun off the Damage Engine 3.8 into a Pastebin, so now all the information here is relevant just to Damage Engine 5 and higher.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Bribe, i'm wondering how to this : while UnitA damages a unit, he pierces some of the target's armor. So it would dealt more damage.
Depends on the complexity.

You definitely want to use DamageModifierEvent 1.00, and then do one (or some) of the following:

Set DamageEventAmount = (DamageEventAmount x 2.00) (this doubles the damage)
or
Set DamageEventAttackT = ATTACK_TYPE_CHAOS (this causes the damage to be 100% regardless of defense type)
or
Set DamageEventDamageT = DAMAGE_TYPE_UNIVERSAL (this causes the target unit's armor value to be ignored)
or
Set DamageEventDefenseT = DEFENSE_TYPE_UNARMORED (this causes piercing and siege attacks to inflict bonus damage)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Hmm.. but how if want it like, the UnitA will pierce 7 armor. The target has 10 armor, so if target takes damage, the damage amount is same as if the target has 3 armor.

WarCraft 3 armor calculation, if the Gameplay Constants weren't changed, works like this (source: Warcraft III - Basics -> Armor and Weapon Types):

For positive Armor, damage reduction =((armor)*0.06)/(1+0.06*(armor))
For negative Armor, it is damage increase = 2-0.94^(-armor) since you take more damage for negative armor scores.

In order to make the target ignore a fixed amount of armor, I'd have to make an update to damage engine to change the target's actual armor value for just that moment. I can work on that, but it will take some time to get it sorted (making sure bonus armor is kept separate from base armor).

Gimme a few days to make sure it's bug-free before I do another update to the engine.
 
Level 5
Joined
Dec 25, 2014
Messages
111
Well, i actually make it. But it seems error will happens when you dealing converted physical damage from spells. That one isn't from this testmap, i actually tried once a week ago. It got so bad that the armor decreasing like crazy.
We can just reduce the target's armor within DamageEventModifier equal to 1.00 and then increasing it back at DamageEvent equal to 1.00. The testmap will show that the original paladin won't pierce any armor since the Uther will pierce 100 armors.
 

Attachments

  • DamageTest.w3x
    39.5 KB · Views: 22
Level 5
Joined
Dec 25, 2014
Messages
111
That doesn't have any issues with decreasing the armor. The Uther has wayyy too fast attack speed set in Object Editor. When you set his attack speed to normal, it works as expected.
Oh sorry i just updated the testmap right after i uploaded it so it's normal now, the attack speed and the peasant hp.

Edit : And what i mean about the error i got is as far as i can remember is when i changed phoenix fire damage to hero/normal and also changing the weapon type. It causes error that the armor didn't increase back.

Edit 2 : Nah, i just tested it again and nothing bad happens right now. I don't know why.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Is that "FAIL" I see in the screenshot?
What causes this to occur?
The peasant has always dealt zero damage in the demo map, but now that we have access to weapontype and armortype, we can control what on-hit sound is played. For the peasant, I set them both to NONE, so his attack "misses".

You can check the FAQs I wrote for a smidge more detail.
 
Level 9
Joined
Mar 6, 2012
Messages
64
@Bribe Is there an event that fires after the damage has been applied? I created a custom ui healthbar that updates when a "DamageEvent becomes Equal to 1.00" however that seemed to set the healthbar to the health of the unit before it was damaged.

@Devalut, based on that prof pic alone I can see that you're a man of culture as well :clol:
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
@Bribe Is there an event that fires after the damage has been applied? I created a custom ui healthbar that updates when a "DamageEvent becomes Equal to 1.00" however that seemed to set the healthbar to the health of the unit before it was damaged.

AfterDamageEvent 1.00 is what you want. I reintroduced it in 5.2.
 
Level 13
Joined
Jul 15, 2007
Messages
763
I am not sure if this feedback is particularly helpful, but prior to 1.31 my map ran pretty smooth with Damage Engine, even in situations where hundreds of damage events would occur per second and wouldn't cause any performance issues. Since the 5.0.0.0 version (and beyond) of Damage Engine its performance has been significantly worse, the map plays fine but during intense battles (where there are hundreds of instances of damage per second going on) there's a bit of choking. Nothing particularly worrisome but players on lower end machines complain about debilitating lag.

It's particularly bad during AoE effects when multiple instances of damage occur at the same time (e.g. Thunder Clap).

There's a bit of optimization I can do on my end to alleviate the issue but I felt like it was worth letting you know that the latest iteration of D.E performs less well (maybe you already know :p).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I am not sure if this feedback is particularly helpful, but prior to 1.31 my map ran pretty smooth with Damage Engine, even in situations where hundreds of damage events would occur per second and wouldn't cause any performance issues. Since the 5.0.0.0 version (and beyond) of Damage Engine its performance has been significantly worse, the map plays fine but during intense battles (where there are hundreds of instances of damage per second going on) there's a bit of choking. Nothing particularly worrisome but players on lower end machines complain about debilitating lag.

It's particularly bad during AoE effects when multiple instances of damage occur at the same time (e.g. Thunder Clap).

There's a bit of optimization I can do on my end to alleviate the issue but I felt like it was worth letting you know that the latest iteration of D.E performs less well (maybe you already know :p).

This is a very important question and one which I will answer in detail, giving you some choice which may help to circumvent this issue.

First, some details:
  1. WarCraft 3 is now significantly more CPU intensive. The FPS is about half of what it used to be. Most probably wouldn't notice, but the lower-end machines certainly would.


  2. Damage Engine is now based on two events, rather than one, per damage instance - so this has somewhat of a performance impact (however minor).


  3. It's impossible to know how much less efficient Damage Engine 5 is from what it would've been in previous iterations of WarCraft 3, because the new natives restrict it. However, I could run some speed benchmarks comparing the performance of Damage Engine 3.8 to 5.3 and see what kind of delta we're dealing with.
Next, onto some solutions:
  1. Avoid duplicate events (where possible). This means, instead of having multiple triggers each with DamageModifierEvent becomes Equal to 1.00, just have one trigger with an if/else chain that regulates the conditions of all the other triggers in the map that had been using that event, and if the conditions are successful then run the respective trigger.


  2. Make sure Damage Engine is on the latest version. I make performance improvements in the code with every update. Not saying it's the solution to the problem, but minor optimizations do go a long way.
Some final notes: Yesterday, I was thinking about writing a Lua Damage Engine, which would co-exist with GUI Damage Engine. I know currently maps cannot both have vJass and Lua, so this would serve as - yet another - bridge for those who need compatibility with however their map is implemented. However, there is also @TriggerHappy's JNGP Lua Edition - Combining JASS/vJass/Lua, which could, potentially, bridge that gap without me needing to code a second version.
 
Last edited:
Level 6
Joined
Sep 18, 2017
Messages
74
Just implemented this...my first DDS...what have I been doing all these years? This is FANTASTIC. Thank you SO MUCH for making something user-friendly. I'm now able to employ so many ideas in my maps. Wish I could give this more than 5 stars.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Major update! Infinite recursion loops are now 100% a thing of the past! No more annoying steps to mitigate it yourself, since the system now tracks the triggers it fires and will prevent your trigger from re-firing during the recursive sequence.

This is achieved by hooking the TriggerRegisterVariableEvent call, adding your trigger to a list, and running that list instead of actually using the variable event. So now the entire process is risk-free.

Yes, so you can stop doing weird things like setting the DamageEventTrigger to your own trigger before dealing damage. Damage Engine 5.4 has got you covered. No more recursion!
 
Level 5
Joined
Dec 25, 2014
Messages
111
Major update! Infinite recursion loops are now 100% a thing of the past! No more annoying steps to mitigate it yourself, since the system now tracks the triggers it fires and will prevent your trigger from re-firing during the recursive sequence.

This is achieved by hooking the TriggerRegisterVariableEvent call, adding your trigger to a list, and running that list instead of actually using the variable event. So now the entire process is risk-free.

Yes, so you can stop doing weird things like setting the DamageEventTrigger to your own trigger before dealing damage. Damage Engine 5.4 has got you covered. No more recursion!
I was just thinking about this and now you really made it, great job.

Edit : Hmm
 
Last edited:
Level 4
Joined
May 2, 2019
Messages
15
@Bribe Is there a way to turn on damage recursion? As the damage events no longer run, shield events cannot damage shields in this situation.

Here's a test map, reproduction steps are as follows
  1. Shield yourself, and the enemy peasant
  2. Let the enemy peasant break your shield
  3. Watch the shield explosion directly damage the enemy peasant's health, bypassing the shield
Also, I believe Sunken City makes use of damage recursion as well, with some procs being able to trigger procs in a chain, if very lucky or given a large enough mob density with AoE procs. Though in their case, removing recursion would just be an excuse to rebalance things without needing to consider recursive damage returns on stacking procs effects trying to hit some recursion critical mass.
 

Attachments

  • GUIShieldSystemRecursionTest.w3x
    101.2 KB · Views: 48

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
@Bribe Is there a way to turn on damage recursion? As the damage events no longer run, shield events cannot damage shields in this situation.

Here's a test map, reproduction steps are as follows
  1. Shield yourself, and the enemy peasant
  2. Let the enemy peasant break your shield
  3. Watch the shield explosion directly damage the enemy peasant's health, bypassing the shield
Also, I believe Sunken City makes use of damage recursion as well, with some procs being able to trigger procs in a chain, if very lucky or given a large enough mob density with AoE procs. Though in their case, removing recursion would just be an excuse to rebalance things without needing to consider recursive damage returns on stacking procs effects trying to hit some recursion critical mass.

A few things:
  1. I tested the map and I'm not sure what the expected behavior is there.
  2. I'm not sure which triggers pertain to the affected shields.
  3. The anti-recursion disables the core event trigger, and I see that you have a central event trigger from where all the little ones spawn. To "get around" the recursion protection I have, one must have more events (not necessarily more triggers, because I'm not turning off triggers - I'm turning off events). So this means:
    • Things that should always run regardless of how deep the recursion is (such as damage enhancement or reduction) should have their own event(s) and not deal damage from them.
    • Things which SHOULD deal damage recursively should have two events (get ready, shit's about to get real):
        • ForcedRecursion
          • Events
            • Game - DamageModifierEvent becomes Equal to 4.00
            • Game - DamageModifierEvent becomes Equal to 4.00
          • Conditions
          • Actions
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • IsDamageCode Equal to False
              • Then - Actions
                • -------- Normal event, do whatever you want as the source wasn't triggered --------
                • Unit - Cause Source to damage Target...
              • Else - Actions
                • -------- Recursive event - do some recursive calculations with your shields in order to come up with--------
                • -------- final numbers of damage/shield reduction for the DamageEventSource and DamageEventTarget --------
                • Unit - Cause Source to damage Target for <calculated total>...
                • Unit - Cause Target to damage Source for <calculated total>...
      • Actually, the above can't really be done either, since it would fire twice for the initial event. The system doesn't currently have a way to support forced recursion. It'd be harder for the user to figure it out than for me to just update the system for daredevils. Stay tuned.

Dummy question : the latest version only works with WE 1.31.x right ? So I cannot both use it and protect my map at the same time right (with Widgetizer, Vexorian's optimizer, ...) ?

Only 3.8 (found in the pastebin) works pre-1.31.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Ok, for those who insist on it, this code allows recursion. I'm hesitant to release this as an actual patch.

Basically, this is an advanced concept and against the design of the engine, so you'll need to use Custom script and set DamageEngine_allowRecursion = true.

It will allow that triggered damage to go recursive to the number of MAX_RECURSION.

JASS:
//===========================================================================
// Damage Engine lets you detect, amplify, block or nullify damage. It even
// lets you detect if the damage was physical or from a spell. Just reference
// DamageEventAmount/Source/Target or the boolean IsDamageSpell, to get the
// necessary damage event data.
//
// - Detect damage (after it's been dealt to the unit): use the event "DamageEvent Equal to 1.00"
// - To change damage before it's dealt: use the event "DamageModifierEvent Equal to 1.00"
// - Detect spell damage: use the condition "IsDamageSpell Equal to True"
// - Detect zero-damage: use the event "DamageEvent Equal to 2.00"
//
// You can specify the DamageEventType before dealing triggered damage:
// - Set NextDamageType = DamageTypeWhatever
// - Unit - Cause...
//
// You can modify the DamageEventAmount and the DamageEventType from a "DamageModifierEvent Equal to 1.00" trigger.
// - If the amount is modified to negative, it will count as a heal.
// - If the amount is set to 0, no damage will be dealt.
//
// If you need to reference the original in-game damage, use the variable "DamageEventPrevAmt".
//
//===========================================================================
library DamageEngine initializer Init
globals
    private constant integer MAX_RECURSION = 16
    public boolean allowRecursion = false
    private integer array daredevil
    private boolean recursive = false
    private integer recursion = -1
    
    private boolean started = false
    private boolean finished = false
    private boolean purge = false
    private timer ticker = CreateTimer()
    private trigger trig = CreateTrigger()
    private real previousAmount = 0.00      //Added to track the original modified damage pre-spirit Link
    private real previousValue = 0.00       //Added to track the original pure damage amount of Spirit Link
    private integer previousType = 0        //Track the type
    private boolean previousCode = false    //Was it caused by a trigger/script?
    private boolean preDamage = false
    private boolean holdClear = false
  
    //Added in 5.2 to detect and modify armor and defense types from within the damage instance.
    private real previousPierce     = 0.00
    private integer armorType       = 0
    private integer previousArmor   = 0
    private integer prevPreArmor    = 0
    private integer defenseType     = 0
    private integer previousDefense = 0
    private integer prevPreDefense  = 0
  
    private unit array lastSource
    private unit array lastTarget
    private real array lastAmount
    private attacktype array lastAttackT
    private damagetype array lastDamageT
    private weapontype array lastWeaponT
    private integer array lastTrig
    private integer array lastType
  
    //Added in 5.4 to silently eliminate recursion.
    private integer userTrigs = 9
    private integer eventTrig = 0
    private integer array nextTrig
    private trigger array userTrig
    private boolean array trigFrozen
endglobals
//GUI Vars:
/*
    boolean udg_DamageEventOverride
    boolean udg_NextDamageType
    boolean udg_DamageEventType
    boolean udg_IsDamageCode            //New in 5.1 as per request from chopinski
    boolean udg_IsDamageSpell
    boolean udg_IsDamageMelee           //New in 5.0
    boolean udg_IsDamageRanged          //New in 5.0
    unit udg_DamageEventSource
    unit udg_DamageEventTarget
    real    udg_AOEDamageEvent
    integer udg_DamageEventAOE
    group   udg_DamageEventAOEGroup
    unit    udg_AOEDamageSource         //New in 5.0
    integer udg_DamageEventLevel
    unit    udg_EnhancedDamageTarget
    real udg_DamageModifierEvent
    real udg_LethalDamageEvent          //New in 5.0
    real udg_DamageEvent
    real udg_AfterDamageEvent
    real udg_DamageEventAmount
    real udg_DamageEventPrevAmt
    real DamageEventArmorPierced
    real udg_DamageScalingWC3
    real udg_DamageScalingUser
    real udg_LethalDamageHP             //New in 5.0
    integer udg_DamageEventAttackT      //New in 5.0
    integer udg_DamageEventDamageT      //New in 5.0
    integer udg_DamageEventWeaponT      //New in 5.0
    integer udg_DamageEventArmorT
    integer udg_DamageEventDefenseT
*/
  
private function RunTrigs takes integer i, integer cat returns nothing
    set recursive = true
    loop
        set i = nextTrig[i]
        exitwhen i == 0
        exitwhen cat == 1 and (udg_DamageEventOverride or udg_DamageEventType*udg_DamageEventType == 4)
        exitwhen cat == 2 and udg_DamageEventAmount <= 0.00
        if not trigFrozen[i] and IsTriggerEnabled(userTrig[i]) then
            set eventTrig = i
            if TriggerEvaluate(userTrig[i]) then
                call TriggerExecute(userTrig[i])
            endif
            //call BJDebugMsg("Ran " + I2S(i))
        endif
    endloop
    set recursive = false
endfunction
  
private function OnAOEEnd takes nothing returns nothing
    if udg_DamageEventAOE > 1 then
        call RunTrigs(8, 0)
        set udg_DamageEventAOE      = 1
    endif
    set udg_DamageEventLevel        = 1
    set udg_EnhancedDamageTarget    = null
    set udg_AOEDamageSource         = null
    call GroupClear(udg_DamageEventAOEGroup)
endfunction
  
private function Finish takes nothing returns nothing
    local integer i = -1
    if finished then
        set finished = false
        if udg_DamageEventPrevAmt != 0.00 and udg_DamageEventDamageT != udg_DAMAGE_TYPE_UNKNOWN then
            call RunTrigs(7, 0)
        endif
        if recursion > -1 and not holdClear and not purge then
            set purge = true
            //call BJDebugMsg("Clearing Recursion: " + I2S(recursion))
            loop
                exitwhen i >= recursion
                set i = i + 1 //Need to loop bottom to top to make sure damage order is preserved.
              
                set udg_NextDamageType = lastType[i]
                //call BJDebugMsg("Stacking on " + R2S(lastAmount[i]))
                call UnitDamageTarget(lastSource[i], lastTarget[i], lastAmount[i], true, false, lastAttackT[i], lastDamageT[i], lastWeaponT[i])
                call Finish()
            endloop
            loop
                exitwhen i <= -1
                set trigFrozen[lastTrig[i]] = false //Only re-enable recursive triggers AFTER all damage is dealt.
                set daredevil[lastTrig[i]] = 0 //Reset this stuff if the user tried some nonsense
                set i = i - 1
            endloop
            //call BJDebugMsg("Cleared Recursion: " + I2S(recursion))
            set recursion = -1 //Can only be set after all the damage has successfully ended.
            set purge = false
        endif
    endif
endfunction
  
private function OnExpire takes nothing returns nothing
    set started = false //The timer has expired. Flag off to allow it to be restarted when needed.
    call Finish() //Wrap up any outstanding damage instance
    call OnAOEEnd() //Reset things so they don't perpetuate for AoE/Level target detection
endfunction
private function CalibrateMR takes nothing returns nothing
    set udg_IsDamageMelee           = false
    set udg_IsDamageRanged          = false
    set udg_IsDamageSpell           = udg_DamageEventAttackT == 0 //In Patch 1.31, one can just check the attack type to find out if it's a spell.
    if udg_DamageEventDamageT == udg_DAMAGE_TYPE_NORMAL and not udg_IsDamageSpell then //This damage type is the only one that can get reduced by armor.
        set udg_IsDamageMelee       = IsUnitType(udg_DamageEventSource, UNIT_TYPE_MELEE_ATTACKER)
        set udg_IsDamageRanged      = IsUnitType(udg_DamageEventSource, UNIT_TYPE_RANGED_ATTACKER)
        if udg_IsDamageMelee and udg_IsDamageRanged then
            set udg_IsDamageMelee   = udg_DamageEventWeaponT > 0// Melee units play a sound when damaging
            set udg_IsDamageRanged  = not udg_IsDamageMelee     // In the case where a unit is both ranged and melee, the ranged attack plays no sound.
        endif                                                   // The Huntress has a melee sound for her ranged projectile, however it is only an issue
    endif                                                       //if she also had a melee attack, because by default she is only UNIT_TYPE_RANGED_ATTACKER.
endfunction
private function OnPreDamage takes nothing returns boolean
    local unit src      = GetEventDamageSource()
    local unit tgt      = GetTriggerUnit()
    local real amt      = GetEventDamage()
    local attacktype at = BlzGetEventAttackType()
    local damagetype dt = BlzGetEventDamageType()
    local weapontype wt = BlzGetEventWeaponType()
  
    if recursive then
        if amt != 0.00 then
            set recursion = recursion + 1
          
            //Store recursive damage into a queue from index "recursion" (0-15)
            //This damage will be fired after the current damage instance has wrapped up its events.
            //This damage can only be caused by triggers.
            set lastAmount[recursion]   = amt
            set lastSource[recursion]   = src
            set lastTarget[recursion]   = tgt
            set lastAttackT[recursion]  = at
            set lastDamageT[recursion]  = dt
            set lastWeaponT[recursion]  = wt
            set lastTrig[recursion]     = eventTrig
            if udg_NextDamageType == 0 then
                set lastType[recursion] = udg_DamageTypeCode
            else
                set lastType[recursion] = udg_NextDamageType
            endif
            if allowRecursion and not trigFrozen[eventTrig] then
                set daredevil[eventTrig] = daredevil[eventTrig] + 1
                if daredevil[eventTrig] >= MAX_RECURSION then
                    set trigFrozen[eventTrig] = true
                endif
            else
                set trigFrozen[eventTrig] = true
            endif
        endif
        set udg_NextDamageType = 0
        call BlzSetEventDamage(0.00) //queue the damage instance instead of letting it run recursively
    else
        if not purge then
            call Finish()
            //Added 25 July 2017 to detect AOE damage or multiple single-target damage
            if started then
                if src != udg_AOEDamageSource then //Source has damaged more than once
                  
                    call OnAOEEnd() //New damage source - unflag everything
                    set udg_AOEDamageSource = src
                elseif tgt == udg_EnhancedDamageTarget then
                    set udg_DamageEventLevel= udg_DamageEventLevel + 1  //The number of times the same unit was hit.
                elseif not IsUnitInGroup(tgt, udg_DamageEventAOEGroup) then
                    set udg_DamageEventAOE  = udg_DamageEventAOE + 1    //Multiple targets hit by this source - flag as AOE
                endif
                if preDamage then
                    set preDamage           = false
                    set previousAmount      = udg_DamageEventAmount
                    set previousValue       = udg_DamageEventPrevAmt    //Store the actual pre-armor value.
                    set previousType        = udg_DamageEventType       //also store the damage type.
                    set previousCode        = udg_IsDamageCode          //store this as well.
                    set previousArmor       = udg_DamageEventArmorT
                    set prevPreArmor        = armorType
                    set previousDefense     = udg_DamageEventDefenseT
                    set prevPreDefense      = defenseType
                    set previousPierce      = udg_DamageEventArmorPierced
                    set holdClear           = true
                endif
            else
                call TimerStart(ticker, 0.00, false, function OnExpire)
                set started                 = true
                set udg_AOEDamageSource     = src
                set udg_EnhancedDamageTarget= tgt
            endif
            call GroupAddUnit(udg_DamageEventAOEGroup, tgt)
        endif
        set udg_DamageEventType             = udg_NextDamageType
        if udg_NextDamageType != 0 then
            set udg_DamageEventType         = udg_NextDamageType
            set udg_NextDamageType          = 0
            set udg_IsDamageCode            = true //New in 5.1 - requested by chopinski to allow user to detect Code damage
        endif
        set udg_DamageEventOverride         = dt == null or amt == 0.00 //Got rid of NextDamageOverride in 5.1 for simplicity
        set udg_DamageEventPrevAmt          = amt
        set udg_DamageEventSource           = src
        set udg_DamageEventTarget           = tgt
        set udg_DamageEventAmount           = amt
        set udg_DamageEventAttackT          = GetHandleId(at)
        set udg_DamageEventDamageT          = GetHandleId(dt)
        set udg_DamageEventWeaponT          = GetHandleId(wt)
      
        call CalibrateMR() //Set Melee and Ranged settings.
      
        set udg_DamageEventArmorT           = BlzGetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_ARMOR_TYPE) //Introduced in Damage Engine 5.2.0.0
        set udg_DamageEventDefenseT         = BlzGetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_DEFENSE_TYPE)
        set armorType                       = udg_DamageEventArmorT
        set defenseType                     = udg_DamageEventDefenseT
        set udg_DamageEventArmorPierced     = 0.00
      
        //Ignores event on various debuffs like Faerie Fire - alternatively,
        //the user can exploit UNKNOWN damage type to avoid damage detection.
        if not udg_DamageEventOverride then
            call RunTrigs(1, 1)
          
            //All events have run and the pre-damage amount is finalized.
            call BlzSetEventAttackType(ConvertAttackType(udg_DamageEventAttackT))
            call BlzSetEventDamageType(ConvertDamageType(udg_DamageEventDamageT))
            call BlzSetEventWeaponType(ConvertWeaponType(udg_DamageEventWeaponT))
            if udg_DamageEventArmorPierced != 0.00 then
                call BlzSetUnitArmor(udg_DamageEventTarget, BlzGetUnitArmor(udg_DamageEventTarget) - udg_DamageEventArmorPierced)
            endif
            if armorType != udg_DamageEventArmorT then
                call BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_ARMOR_TYPE, udg_DamageEventArmorT) //Introduced in Damage Engine 5.2.0.0
            endif
            if defenseType != udg_DamageEventDefenseT then
                call BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_DEFENSE_TYPE, udg_DamageEventDefenseT) //Introduced in Damage Engine 5.2.0.0
            endif
            call BlzSetEventDamage(udg_DamageEventAmount)
        endif
        set preDamage = true
        //call BJDebugMsg("Ready to deal " + R2S(udg_DamageEventAmount))
    endif
    set src = null
    set tgt = null
    set allowRecursion = false
    return false
endfunction
//The traditional on-damage response, where armor reduction has already been factored in.
private function OnDamage takes nothing returns boolean
    local real r = GetEventDamage()
    if recursive then
        return false
    endif
    //call BJDebugMsg("Second event running")
    if preDamage then
        set preDamage = false   //This should be the case in almost all circumstances
    else
        call Finish() //Wrap up the outstanding damage instance
        set holdClear                   = false
        //Unfortunately, Spirit Link and Thorns Aura/Spiked Carapace fire the DAMAGED event out of sequence with the DAMAGING event,
        //so I have to re-generate a buncha stuff here.
        set udg_DamageEventAmount       = previousAmount
        set udg_DamageEventPrevAmt      = previousValue
        set udg_DamageEventType         = previousType
        set udg_IsDamageCode            = previousCode
        set udg_DamageEventSource       = GetEventDamageSource()
        set udg_DamageEventTarget       = GetTriggerUnit()
        set udg_DamageEventAttackT      = GetHandleId(BlzGetEventAttackType())
        set udg_DamageEventDamageT      = GetHandleId(BlzGetEventDamageType())
        set udg_DamageEventWeaponT      = GetHandleId(BlzGetEventWeaponType())
        set udg_DamageEventArmorT       = previousArmor
        set udg_DamageEventDefenseT     = previousDefense
        set udg_DamageEventArmorPierced = previousPierce
        set armorType                   = prevPreArmor
        set defenseType                 = prevPreDefense
        call CalibrateMR() //Apply melee/ranged settings once again.
    endif
    if udg_DamageEventArmorPierced != 0.00 then
        call BlzSetUnitArmor(udg_DamageEventTarget, BlzGetUnitArmor(udg_DamageEventTarget) + udg_DamageEventArmorPierced)
    endif
    if armorType != udg_DamageEventArmorT then
        call BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_ARMOR_TYPE, armorType) //revert changes made to the damage instance
    endif
    if defenseType != udg_DamageEventDefenseT then
        call BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_DEFENSE_TYPE, defenseType)
    endif
    set recursive = true
    if udg_DamageEventPrevAmt == 0.00 then
        call RunTrigs(6, 0)
    else
        if udg_DamageEventAmount != 0.00 and r != 0.00 then
            set udg_DamageScalingWC3 = r / udg_DamageEventAmount
        elseif udg_DamageEventAmount > 0.00 then
            set udg_DamageScalingWC3 = 0.00
        else
            set udg_DamageScalingWC3 = 1.00
        endif
        set r = udg_DamageEventAmount
        set udg_DamageScalingUser = r/udg_DamageEventPrevAmt
        set udg_DamageEventAmount = r*udg_DamageScalingWC3
      
        if udg_DamageEventAmount > 0.00 then
            //This event is used for custom shields which have a limited hit point value
            //The shield here kicks in after armor, so it acts like extra hit points.
            call RunTrigs(4, 2)
            set udg_LethalDamageHP = GetWidgetLife(udg_DamageEventTarget) - udg_DamageEventAmount
            if udg_LethalDamageHP <= 0.405 then
                call RunTrigs(8, 0) //Added 10 May 2019 to detect and potentially prevent lethal damage. Instead of
                //modifying the damage, you need to modify LethalDamageHP instead (the final HP of the unit).
              
                set udg_DamageEventAmount = GetWidgetLife(udg_DamageEventTarget) - udg_LethalDamageHP
                if udg_DamageEventType < 0 and udg_LethalDamageHP <= 0.405 then
                    call SetUnitExploded(udg_DamageEventTarget, true)   //Explosive damage types should blow up the target.
                endif
            endif
            set udg_DamageScalingUser = udg_DamageEventAmount/(udg_DamageEventPrevAmt*udg_DamageScalingWC3)
        endif
        call BlzSetEventDamage(udg_DamageEventAmount)   //Apply the final damage amount.
        //if recursion > -1 then
            //call BJDebugMsg("Dealing " + R2S(udg_DamageEventAmount))
        //endif
        if udg_DamageEventDamageT != udg_DAMAGE_TYPE_UNKNOWN then
            call RunTrigs(5, 0)
        endif
    endif
    set recursive = false
    set finished = true
    if udg_DamageEventAmount == 0.00 then
        call Finish()
    endif
    return false
endfunction
//===========================================================================
private function Init takes nothing returns nothing
    call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DAMAGED) //Thanks to this I no longer have to create an event for every unit in the map.
    call TriggerAddCondition(trig, Filter(function OnDamage))
    set trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DAMAGING) //The new 1.31 event which fires before damage.
    call TriggerAddCondition(trig, Filter(function OnPreDamage))
endfunction
public function DebugStr takes nothing returns nothing
    set udg_AttackTypeDebugStr[0] = "SPELLS"    //ATTACK_TYPE_NORMAL in JASS
    set udg_AttackTypeDebugStr[1] = "NORMAL"    //ATTACK_TYPE_MELEE in JASS
    set udg_AttackTypeDebugStr[2] = "PIERCE"
    set udg_AttackTypeDebugStr[3] = "SIEGE"
    set udg_AttackTypeDebugStr[4] = "MAGIC"
    set udg_AttackTypeDebugStr[5] = "CHAOS"
    set udg_AttackTypeDebugStr[6] = "HERO"
  
    set udg_DamageTypeDebugStr[0]  = "UNKNOWN"
    set udg_DamageTypeDebugStr[4]  = "NORMAL"
    set udg_DamageTypeDebugStr[5]  = "ENHANCED"
    set udg_DamageTypeDebugStr[8]  = "FIRE"
    set udg_DamageTypeDebugStr[9]  = "COLD"
    set udg_DamageTypeDebugStr[10] = "LIGHTNING"
    set udg_DamageTypeDebugStr[11] = "POISON"
    set udg_DamageTypeDebugStr[12] = "DISEASE"
    set udg_DamageTypeDebugStr[13] = "DIVINE"
    set udg_DamageTypeDebugStr[14] = "MAGIC"
    set udg_DamageTypeDebugStr[15] = "SONIC"
    set udg_DamageTypeDebugStr[16] = "ACID"
    set udg_DamageTypeDebugStr[17] = "FORCE"
    set udg_DamageTypeDebugStr[18] = "DEATH"
    set udg_DamageTypeDebugStr[19] = "MIND"
    set udg_DamageTypeDebugStr[20] = "PLANT"
    set udg_DamageTypeDebugStr[21] = "DEFENSIVE"
    set udg_DamageTypeDebugStr[22] = "DEMOLITION"
    set udg_DamageTypeDebugStr[23] = "SLOW_POISON"
    set udg_DamageTypeDebugStr[24] = "SPIRIT_LINK"
    set udg_DamageTypeDebugStr[25] = "SHADOW_STRIKE"
    set udg_DamageTypeDebugStr[26] = "UNIVERSAL"
  
    set udg_WeaponTypeDebugStr[0]  = "NONE"     //WEAPON_TYPE_WHOKNOWS in JASS
    set udg_WeaponTypeDebugStr[1]  = "METAL_LIGHT_CHOP"
    set udg_WeaponTypeDebugStr[2]  = "METAL_MEDIUM_CHOP"
    set udg_WeaponTypeDebugStr[3]  = "METAL_HEAVY_CHOP"
    set udg_WeaponTypeDebugStr[4]  = "METAL_LIGHT_SLICE"
    set udg_WeaponTypeDebugStr[5]  = "METAL_MEDIUM_SLICE"
    set udg_WeaponTypeDebugStr[6]  = "METAL_HEAVY_SLICE"
    set udg_WeaponTypeDebugStr[7]  = "METAL_MEDIUM_BASH"
    set udg_WeaponTypeDebugStr[8]  = "METAL_HEAVY_BASH"
    set udg_WeaponTypeDebugStr[9]  = "METAL_MEDIUM_STAB"
    set udg_WeaponTypeDebugStr[10] = "METAL_HEAVY_STAB"
    set udg_WeaponTypeDebugStr[11] = "WOOD_LIGHT_SLICE"
    set udg_WeaponTypeDebugStr[12] = "WOOD_MEDIUM_SLICE"
    set udg_WeaponTypeDebugStr[13] = "WOOD_HEAVY_SLICE"
    set udg_WeaponTypeDebugStr[14] = "WOOD_LIGHT_BASH"
    set udg_WeaponTypeDebugStr[15] = "WOOD_MEDIUM_BASH"
    set udg_WeaponTypeDebugStr[16] = "WOOD_HEAVY_BASH"
    set udg_WeaponTypeDebugStr[17] = "WOOD_LIGHT_STAB"
    set udg_WeaponTypeDebugStr[18] = "WOOD_MEDIUM_STAB"
    set udg_WeaponTypeDebugStr[19] = "CLAW_LIGHT_SLICE"
    set udg_WeaponTypeDebugStr[20] = "CLAW_MEDIUM_SLICE"
    set udg_WeaponTypeDebugStr[21] = "CLAW_HEAVY_SLICE"
    set udg_WeaponTypeDebugStr[22] = "AXE_MEDIUM_CHOP"
    set udg_WeaponTypeDebugStr[23] = "ROCK_HEAVY_BASH"
  
    set udg_DefenseTypeDebugStr[0] = "LIGHT"
    set udg_DefenseTypeDebugStr[1] = "MEDIUM"
    set udg_DefenseTypeDebugStr[2] = "HEAVY"
    set udg_DefenseTypeDebugStr[3] = "FORTIFIED"
    set udg_DefenseTypeDebugStr[4] = "NORMAL"
    set udg_DefenseTypeDebugStr[5] = "HERO"
    set udg_DefenseTypeDebugStr[6] = "DIVINE"
    set udg_DefenseTypeDebugStr[7] = "UNARMORED"
    set udg_ArmorTypeDebugStr[0] = "NONE"
    set udg_ArmorTypeDebugStr[1] = "FLESH"
    set udg_ArmorTypeDebugStr[2] = "METAL"
    set udg_ArmorTypeDebugStr[3] = "WOOD"
    set udg_ArmorTypeDebugStr[4] = "ETHEREAL"
    set udg_ArmorTypeDebugStr[5] = "STONE"
endfunction
//This function exists mainly to make it easier to switch from another DDS, like PDD.
function UnitDamageTargetEx takes unit src, unit tgt, real amt, boolean a, boolean r, attacktype at, damagetype dt, weapontype wt returns boolean
    if udg_NextDamageType == 0 then
       set udg_NextDamageType = udg_DamageTypeCode
    endif
    call UnitDamageTarget(src, tgt, amt, a, r, at, dt, wt)
    return recursive
endfunction
public function SetupEvent takes trigger whichTrig, string var, integer index returns nothing
    local integer max = 1
    local integer off = 0
    local integer exit = 0
    local integer i
    if var == "udg_DamageModifierEvent" then
        if index < 3 then
            set exit = index + 1
        endif
        if nextTrig[1] == 0 then
            set nextTrig[1] = 2
            set nextTrig[2] = 3
            set trigFrozen[2] = true
            set trigFrozen[3] = true
        endif
        set max = 4
    elseif var == "udg_DamageEvent" then
        set max = 2
        set off = 4
    elseif var == "udg_AfterDamageEvent" then
        set off = 6
    elseif var == "udg_LethalDamageEvent" then
        set off = 7
    elseif var == "udg_AOEDamageEvent" then
        set off = 8
    else
        return
    endif
    set i = IMaxBJ(IMinBJ(index, max), 1) + off
    //call BJDebugMsg("Root index: " + I2S(i))
    loop
        set index = i
        set i = nextTrig[i]
        exitwhen i == exit
    endloop
    set userTrigs = userTrigs + 1
    set nextTrig[index] = userTrigs
    set nextTrig[userTrigs] = exit
    set userTrig[userTrigs] = whichTrig
    //call BJDebugMsg("Registered " + I2S(userTrigs) + " to " + I2S(index))
endfunction
  
private function PreSetup takes trigger whichTrig, string var, limitop op, real value returns nothing
    call SetupEvent(whichTrig, var, R2I(value))
endfunction
  
hook TriggerRegisterVariableEvent PreSetup
  
endlibrary
 
Last edited:
Level 4
Joined
May 2, 2019
Messages
15
The expected behaviour would be that when a flame shield breaks, it would damage units around it. If the units that are affected by the explosion have a shield, those shields should be able to block the damage.

Since the shield breaking happened in the damage event, the damage it dealt when broken would be considered recursive. When recursive damage gets ignored by the damage detection system, the shields can't block damage properly. Presumably this would also mean any form of recursive damage would end up unblocked.

Being able to enable/disable damage recursion pretty much solves that problem.

There are still some very strange things I'll need to ponder over though, such as how to have recursion be true only so that the damage event can catch shield events dealing damage - but otherwise revert back to what the user has opted to use. It's very tricky.


For example, what if someone wished to reflect 100% of damage taken to back their attacker, but not have the damage be recursive (otherwise two units reflecting each other will result both players taking about 8x the damage of the initial hit, since the damage engine tracks recursion level and it'll quit, after the 16th time or so).

They could simply disable recursion, and leave it at that.

But then, the shield system would then not block the reflected damage. Let's say, for gameplay reasons, that it should block this damage.

This means recursion needs to be set to true for it to work. But the trouble is in how recursion can be enabled, and have a unit deal damage so that the engine can pick it up so shields can apply, but otherwise still be set to disabled to everything else that runs on the damage detection engine.

Going back to the example, let's say one of the units has a shield that should explode when broken. If this shield breaks and explodes, the damage from the explosion would need to have recursion set to be true. If the shield explodes and damages a unit with recursion to true, it would also cause the reflection damage to be dealt, but wait, instead of the reflection damage being ignored as it should be, recursion is currently set to true, when it shouldn't have been. This would result in the 8x recursive damage reflection we originally didn't want.

Of course, the easiest solution is to not actually deal recursive/reactive damage instantly. But that would be a problem because it would be up to the user to accomplish.

I'd imagine most GUI users aren't too keen on using timers or projectile systems as opposed to instantly dealing the damage. Using waits/sleep is also out of the question, for obvious system breaking reasons.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
The expected behaviour would be that when a flame shield breaks, it would damage units around it. If the units that are affected by the explosion have a shield, those shields should be able to block the damage.

Since the shield breaking happened in the damage event, the damage it dealt when broken would be considered recursive. When recursive damage gets ignored by the damage detection system, the shields can't block damage properly. Presumably this would also mean any form of recursive damage would end up unblocked.

Being able to enable/disable damage recursion pretty much solves that problem.

There are still some very strange things I'll need to ponder over though, such as how to have recursion be true only so that the damage event can catch shield events dealing damage - but otherwise revert back to what the user has opted to use. It's very tricky.


For example, what if someone wished to reflect 100% of damage taken to back their attacker, but not have the damage be recursive (otherwise two units reflecting each other will result both players taking about 8x the damage of the initial hit, since the damage engine tracks recursion level and it'll quit, after the 16th time or so).

They could simply disable recursion, and leave it at that.

But then, the shield system would then not block the reflected damage. Let's say, for gameplay reasons, that it should block this damage.

This means recursion needs to be set to true for it to work. But the trouble is in how recursion can be enabled, and have a unit deal damage so that the engine can pick it up so shields can apply, but otherwise still be set to disabled to everything else that runs on the damage detection engine.

Going back to the example, let's say one of the units has a shield that should explode when broken. If this shield breaks and explodes, the damage from the explosion would need to have recursion set to be true. If the shield explodes and damages a unit with recursion to true, it would also cause the reflection damage to be dealt, but wait, instead of the reflection damage being ignored as it should be, recursion is currently set to true, when it shouldn't have been. This would result in the 8x recursive damage reflection we originally didn't want.

Of course, the easiest solution is to not actually deal recursive/reactive damage instantly. But that would be a problem because it would be up to the user to accomplish.

I'd imagine most GUI users aren't too keen on using timers or projectile systems as opposed to instantly dealing the damage. Using waits/sleep is also out of the question, for obvious system breaking reasons.

Even with the unupdated version, if you deal recursive damage from one event, it will ignore the recursive damage only from the trigger whose event was caught dealing the damage. So if you avoid dealing damage from DamageModifier 4, you'll ALWAYS be able to let them block damage.

What you could do is pair the shield event with another event further along in the sequence (ie. DamageEvent 1) and deal the damage from there. So you have two triggers instead of one - one whose damage can only fire once and another whose shields can fire for as many events there are.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
For anyone who needs a Lua version of this script to run in your map, I've added a Lua version inclusive with Lua Unit Indexer, Lua Is Unit Moving and Lua ArcingTextTag.

There may be a glitch somewhere in one of the converted Lua resources, as I encountered a crash once but couldn't reproduce it. Please keep me posted if you run into any issues (especially ones which can be replicated).

Big shout out to @MindWorX and Zed on Discord for illustrating how to "replace" natives in Lua. This Lua version has a lot of potential as I could potentially extract the Trig_YourTrigger_Conditions and Trig_YourTrigger_Actions variable names and call them directly rather than evaluating & executing them. This could improve performance dramatically.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
For those who have been wanting the most performance as possible from their Damage Events, today I've introduced an updated Lua script which is compatible with a groundbreaking resource [Lua] Ridiculously Fast Triggers. The Lua demo map features this script and I have benchmarked it as running SEVENTEEN TIMES FASTER than traditional trigger evaluate & trigger execute.

I HIGHLY recommend switching your map over to Lua. I am in the process of adding a Lua-compatible script to all of my most frequently-used resources.
 
Level 4
Joined
Jun 4, 2019
Messages
31
Bribe, thanks for this absolute fantastic system! There are so many helpful things in there, i really love your system.

Currently I'm trying to understand everything but there is something I'm wondering. There is the text floating system, which displays the text in blue for spells, what works in most cases but if a unit for example have a specific armour-type (in my case heavy, he receive 50% more dmg from spells), the font colour is changing to red.

Looks like the condition for DamageEventAmount is triggering:

Code:
DamageEventAmount Less than (DamageEventPrevAmt x 0.60)

Any idea why this is happening and what can I do?

Thanks in advance!
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Bribe, thanks for this absolute fantastic system! There are so many helpful things in there, i really love your system.

Currently I'm trying to understand everything but there is something I'm wondering. There is the text floating system, which displays the text in blue for spells, what works in most cases but if a unit for example have a specific armour-type (in my case heavy, he receive 50% more dmg from spells), the font colour is changing to red.

Looks like the condition for DamageEventAmount is triggering:

Code:
DamageEventAmount Less than (DamageEventPrevAmt x 0.60)

Any idea why this is happening and what can I do?

Thanks in advance!

In the conditions where you see "DamageEventAmount Greater than or equal to (DamageEventPrevAmt x 1.50)", change them to be "DamageScalingUser Greater than or equal to 1.50".

You can also change the other one "DamageEventAmount Less than (DamageEventPrevAmt x 0.60)" to "DamageScalingUser Less than 0.60" if that one is also giving you trouble.

I have had those variables in the demo map for a long time, but they really should proc off of user damage modification rather than WC3-processed damage. I'll update the demo maps sometime soon.
 
Last edited:
Level 3
Joined
Dec 21, 2012
Messages
15
Is it possible to detect attacks from frost arrows, searing arrows etc. ? In older version i used EnhancedDamageTarget to detect them but now it seems it isn't possible.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Just check if DamageEventLevel is greater than 1. If so, some kind of on-hit effect took place prior to the damage against that target.

However, Searing Arrows was never detectable.

Is it possible to detect attacks from frost arrows, searing arrows etc. ? In older version i used EnhancedDamageTarget to detect them but now it seems it isn't possible.
 
Level 4
Joined
Jun 4, 2019
Messages
31
In the conditions where you see "DamageEventAmount Greater than or equal to (DamageEventPrevAmt x 1.50)", change them to be "DamageScalingUser Greater than or equal to 1.50".

You can also change the other one "DamageEventAmount Less than (DamageEventPrevAmt x 0.60)" to "DamageScalingUser Less than 0.60" if that one is also giving you trouble.

I have had those variables in the demo map for a long time, but they really should proc off of user damage modification rather than WC3-processed damage. I'll update the demo maps sometime soon.

Works absolutly perfect, I am so grateful to you!
 
Level 13
Joined
Jul 15, 2007
Messages
763
I updated this resource in my biggest project and ran into a problem where the damage engine stops working early on in the game (no damage happens), I'm not entirely sure what the problem could be since the coding was fine before i updated DE, though I did notice that a lot of the older triggers in the map still use "NextDamageType = 0" from when you would run "set NextDamageType... cause unit to damage... Run ClearDamageEvent" in the pre-patch version. There is no defined 0 damage type in the config trigger, and I was wondering if this could this be the problem?

I think before i updated the version was 5.3.1.0 if that's helpful.

Any other ideas for the sudden issue?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I updated this resource in my biggest project and ran into a problem where the damage engine stops working early on in the game (no damage happens), I'm not entirely sure what the problem could be since the coding was fine before i updated DE, though I did notice that a lot of the older triggers in the map still use "NextDamageType = 0" from when you would run "set NextDamageType... cause unit to damage... Run ClearDamageEvent" in the pre-patch version. There is no defined 0 damage type in the config trigger, and I was wondering if this could this be the problem?

I think before i updated the version was 5.3.1.0 if that's helpful.

Any other ideas for the sudden issue?

Are you using Lua or vJass? Also could you please give me a rough idea of how long it takes or what kind of events led up to it (ie. a big fight or a certain kind of spell)?
 
Level 13
Joined
Jul 15, 2007
Messages
763
I am still using the vJass version.

The problem occurs in the early game during pretty standard PvE battles between a group of player heroes and a group of mobs ranging from 3 - 120 units, though when the damage engine breaks it usually happens early on the encounter when there aren't many creeps present. In the early game, most heroes don't have access to most of their skills, which has made the problem a bit easier to search for, though I haven't found it yet.

My replay analysis of one instance of the bug just showed a simple coded starfall-like ability was the last successful damage instance, followed by a melee attack by a hero that had its damage completely negated. This was a bit unexpected as these damage instances would only pass through the core damage engine trigger in the map. The problem doesn't seem to have a 100% incidence rate and my players are suggesting that particular coded abilities from certain heroes might be the root of the problem. I am continuing to comb through my spell triggers and optimize them in line with 5.4.2.0's guidelines (i have found a couple that were using older rules).

I figure at this point there may be a logic problem between a coded ability and the damage engine itself.

Either way when the bug occurs, the damage engine dies completely. Even if you cease all damage for a while, wait for all spells to finish etc., damage is incapable of happening ever again. Could that help pinpoint a particular kind of coding error within the map's script?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Could it be it's recursion related? Are you using that new DamageEngine_inception variable? I think I was able to break something by pushing it, but I can't exactly tell because the map lag was pushed to absolutely insane levels to make it seem to break.

I have attached the map with that stress test.

Regardless, the reporting on Damage Engine was active the whole time.

My recommendation is to go back to your triggers and make sure your are not turning off your on damage detection triggers. That's probably what is happening.
 

Attachments

  • Damage Engine v5.4.2.0.w3x
    59.9 KB · Views: 112
  • Damage Engine v5.4.2.0.w3x
    59.9 KB · Views: 22
Level 2
Joined
Apr 17, 2017
Messages
22
Either way when the bug occurs, the damage engine dies completely. Even if you cease all damage for a while, wait for all spells to finish etc., damage is incapable of happening ever again. Could that help pinpoint a particular kind of coding error within the map's script?

Well I see that i'm not the only one experiencing the same thing. I'm also having this issue for some reason.


Could it be it's recursion related? Are you using that new DamageEngine_inception variable? I think I was able to break something by pushing it, but I can't exactly tell because the map lag was pushed to absolutely insane levels to make it seem to break.

I have attached the map with that stress test.

Regardless, the reporting on Damage Engine was active the whole time.

My recommendation is to go back to your triggers and make sure your are not turning off your on damage detection triggers. That's probably what is happening.

I dunno if pick a chew was turning off his damage detection trigger but I'm not so probably this is a Damage Engine v5.4.2.0 specific bug? I'm trying to check the reason as to why this happens but with no luck.
 
Top