Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
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.)
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:
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).
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:
Use WarCraft 3 Version 1.32
If you're upgrading from 3.8 or prior, please delete the entire "Damage Engine" category from your map.
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. --------
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.
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
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...
Is there a method to make system work with negative damage from abilities, created via Object Editor? For me it convets all negative damage to positive. I tried to use DamageEvent = 1.00, then check if DamageEventAmount < 0, but it doesnt work, looks like system converts damage prior that event.
I try to make flexible leveling system for neutral creeps and for their spells - and your system makes it incredibly easy (just one trigger, which scale all creep spell damage from mana pool). Problem is healing, because i need to scale amount of healing from mana pool of source of healing. So i use spells with negative damage instead of heals like Rejuvenation.
I find a solution for this, but it is very clumsy. I check if spell damage was dealt to ally by unit, which have passive "Healer" and revert it to healing via DamageEvent = 1.00 trigger. It works fine, but with downside - a can't create creep with abilities to heal and damage allies
simultaneously.
For those who are on the 1.29 patch, this version of Damage Engine (attached) is now using the SetEventDamage native.
Benefits: no longer needs Cheat Death Ability, no longer needs custom fixes for Life Drain and Finger of Death, no longer creates a trigger for each damage instance.
Also, the Damage Return Value on Locust Swarm can be switched back to its default 0.75 instead of -0.75 which was previously needed.
Disadvantages: not compatible with code pre-1.29, may have bugs which I haven't spotted (though unlikely).
For now, I will leave the actual resource unchanged, but if enough people request it I will make this 1.29 version the default Damage Engine version.
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: use the event "DamageEvent Equal to 1.00"
// - To change damage before it's dealt: use the event "DamageModifierEvent Equal to 1.00"
// - Detect damage after it was applied, use the event "AfterDamageEvent 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" (an AfterDamageEvent will not fire for this)
//
// You can specify the DamageEventType before dealing triggered damage, then run the trigger "ClearDamageEvent (Checking Conditions)" after dealing triggered damage from within a damage event:
// - Set NextDamageType = DamageTypeWhatever
// - Unit - Cause...
// - Trigger - Run ClearDamageEvent (Checking Conditions)
//
// 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".
//
//===========================================================================
// Programming note about "integer i" and "udg_DmgEvRecursionN": integer i
// ranges from -1 upwards. "udg_DmgEvRecursionN" ranges from 0 upwards.
// "integer i" is always 1 less than "udg_DmgEvRecursionN"
//
function DmgEvResetVars takes nothing returns nothing
local integer i = udg_DmgEvRecursionN - 2
set udg_DmgEvRecursionN = i + 1
if i >= 0 then
set udg_DamageEventPrevAmt = udg_LastDmgPrevAmount[i]
set udg_DamageEventAmount = udg_LastDmgValue[i]
set udg_DamageEventSource = udg_LastDmgSource[i]
set udg_DamageEventTarget = udg_LastDmgTarget[i]
set udg_IsDamageSpell = udg_LastDmgWasSpell[i]
set udg_DamageEventType = udg_LastDmgPrevType[i]
endif
endfunction
function ClearDamageEvent takes boolean clear returns nothing
if clear then
set udg_NextDamageOverride = false
set udg_NextDamageType = 0
endif
if udg_DmgEvQueued then
//The 0-second timer has expired, or there had been multiple sequential damage events running in parallel.
set udg_DmgEvQueued = false
set udg_AfterDamageEvent = 0.00
set udg_AfterDamageEvent = 1.00 //No longer fires with Unit State Event detection - this is
set udg_AfterDamageEvent = 0.00 //lighter on the system but will fire slightly later than before.
//It now only serves for counter-damage after the initial damage.
call ClearDamageEvent(true) //Check for any potential failed recursive damage from the previous event.
call DmgEvResetVars()
endif
endfunction
function DmgEvOnAOEEnd takes nothing returns nothing
if udg_DamageEventAOE > 1 then
set udg_AOEDamageEvent = 0.00
set udg_AOEDamageEvent = 1.00
set udg_AOEDamageEvent = 0.00
set udg_DamageEventAOE = 1
endif
set udg_DamageEventLevel = 1
set udg_EnhancedDamageTarget = null
call GroupClear(udg_DamageEventAOEGroup)
endfunction
function DmgEvOnExpire takes nothing returns nothing
set udg_DmgEvStarted = false //The timer has expired. Flag off to allow it to be restarted when needed.
call ClearDamageEvent(true) //Check for any lingering damage
//Reset things so they don't perpetuate for AoE/Level target detection
call DmgEvOnAOEEnd()
set udg_DamageEventTarget = null
set udg_DamageEventSource = null
endfunction
function PreClearDamageEvent takes nothing returns boolean
call ClearDamageEvent(true)
return false
endfunction
function OnUnitDamage takes nothing returns boolean
local boolean override = udg_DamageEventOverride
local integer i
local integer e = udg_DamageEventLevel
local integer a = udg_DamageEventAOE
local string s
local unit u
local unit f
call ClearDamageEvent(false) //in case the 0.00 second timer hasn't yet expired
set i = udg_DmgEvRecursionN - 1 //Had to be moved here due to false recursion tracking
if i < 0 then
//Added 25 July 2017 to detect AOE damage or multiple single-target damage
set u = udg_DamageEventTarget
set f = udg_DamageEventSource
elseif i < 16 then
set udg_LastDmgPrevAmount[i]= udg_DamageEventPrevAmt
set udg_LastDmgValue[i] = udg_DamageEventAmount
set udg_LastDmgSource[i] = udg_DamageEventSource
set udg_LastDmgTarget[i] = udg_DamageEventTarget
set udg_LastDmgWasSpell[i] = udg_IsDamageSpell
set udg_LastDmgPrevType[i] = udg_DamageEventType
else
set s = "WARNING: Recursion error when dealing damage! Make sure when you deal damage from within a DamageEvent trigger, do it like this:"
set s = s + "Trigger - Turn off (This Trigger)"
set s = s + "Unit - Cause..."
set s = s + "Trigger - Run ClearDamageEvent (Checking Conditions)"
set s = s + "Trigger - Turn on (This Trigger)"
//Delete the next couple of lines to disable the in-game recursion crash warnings
call ClearTextMessages()
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.00, 0.00, 999.00, s)
return false
endif
set udg_DmgEvRecursionN = i + 2
set udg_DamageEventAmount = GetEventDamage()
set udg_DamageEventTarget = GetTriggerUnit()
set udg_DamageEventSource = GetEventDamageSource()
set udg_DamageEventType = udg_NextDamageType
set udg_NextDamageType = 0
set udg_DamageEventOverride = udg_NextDamageOverride
set udg_NextDamageOverride = false
if i < 0 then
//Added 25 July 2017 to detect AOE damage or multiple single-target damage
if udg_DamageEventType == 0 then
if f == udg_DamageEventSource then
//Source has damaged more than once
if IsUnitInGroup(udg_DamageEventTarget, udg_DamageEventAOEGroup) then
//Added 5 August 2017 to improve tracking of enhanced damage against, say, Pulverize
set udg_DamageEventLevel = udg_DamageEventLevel + 1
set udg_EnhancedDamageTarget = udg_DamageEventTarget
else
//Multiple targets hit by this source - flag as AOE
set udg_DamageEventAOE = udg_DamageEventAOE + 1
endif
else
//New damage source - unflag everything
set u = udg_DamageEventSource
set udg_DamageEventSource = f
call DmgEvOnAOEEnd()
set udg_DamageEventSource = u
endif
call GroupAddUnit(udg_DamageEventAOEGroup, udg_DamageEventTarget)
endif
if not udg_DmgEvStarted then
set udg_DmgEvStarted = true
call TimerStart(udg_DmgEvTimer, 0.00, false, function DmgEvOnExpire)
endif
endif
if udg_DamageEventAmount == 0.00 then
if not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
set udg_DamageEventPrevAmt = 0.00
set udg_DamageEvent = 0.00
set udg_DamageEvent = 2.00
set udg_DamageEvent = 0.00
endif
call DmgEvResetVars()
else
set u = udg_DamageEventTarget
set udg_IsDamageSpell = udg_DamageEventAmount < 0.00
if udg_IsDamageSpell then
set udg_DamageEventAmount = -udg_DamageEventAmount
if IsUnitType(u, UNIT_TYPE_ETHEREAL) and not IsUnitType(u, UNIT_TYPE_HERO) then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_ETHEREAL //1.67
endif
if GetUnitAbilityLevel(u, 'Aegr') > 0 then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_ELUNES //0.80
endif
if udg_DmgEvBracers != 0 and IsUnitType(u, UNIT_TYPE_HERO) then
//Inline of UnitHasItemOfTypeBJ without the potential handle ID leak.
set i = bj_MAX_INVENTORY
loop
set i = i - 1
if GetItemTypeId(UnitItemInSlot(u, i)) == udg_DmgEvBracers then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_BRACERS //0.67
exitwhen true
endif
exitwhen i == 0
endloop
endif
endif
set udg_DamageEventPrevAmt = udg_DamageEventAmount
set udg_DamageModifierEvent = 0.00
if not udg_DamageEventOverride then
set udg_DamageModifierEvent = 1.00 //Primary modification - multiplication, blocking, reversing, etc.
set udg_DamageModifierEvent = 2.00 //Secondary Modification - addition/subtraction
set udg_DamageModifierEvent = 3.00 //Similar to 2.00 but with knowing all previous addition/subtraction have run.
endif
if udg_DamageEventAmount > 0.00 then
set udg_DamageModifierEvent = 4.00 //Apply finite damage modification such as Mana Shield or Anti-Magic Shell.
endif
set udg_DamageEventOverride = override
set udg_DamageModifierEvent = 0.00
if not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
set udg_DamageEvent = 0.00
set udg_DamageEvent = 1.00
set udg_DamageEvent = 0.00
endif
call ClearDamageEvent(true) //in case the unit state event failed from a recursive damage event
//All events have run and the damage amount is finalized.
call SetEventDamage(udg_DamageEventAmount)
set udg_DmgEvQueued = true
if udg_DamageEventType < 0 and GetWidgetLife(u) - udg_DamageEventAmount <= 0.405 then
call SetUnitExploded(u, true) //In 1.29 Finger of Death works as intended.
endif
endif
set u = null
set f = null
return false
endfunction
function CreateDmgEvTrg takes nothing returns nothing
set udg_DamageEventTrigger = CreateTrigger()
call TriggerAddCondition(udg_DamageEventTrigger, Filter(function OnUnitDamage))
endfunction
function SetupDmgEv takes nothing returns boolean
local integer i = udg_UDex
local unit u = udg_UDexUnits[i]
if GetUnitAbilityLevel(u, 'Aloc') == 0 and TriggerEvaluate(gg_trg_Damage_Engine_Config) then
set udg_UnitDamageRegistered[i] = true
call TriggerRegisterUnitEvent(udg_DamageEventTrigger, u, EVENT_UNIT_DAMAGED)
call UnitAddAbility(u, udg_SpellDamageAbility)
call UnitMakeAbilityPermanent(u, true, udg_SpellDamageAbility)
endif
set u = null
return false
endfunction
function RemoveDmgEv takes nothing returns boolean
local integer i = udg_UDex
set udg_HideDamageFrom[i] = false
if udg_UnitDamageRegistered[i] then
set udg_UnitDamageRegistered[i] = false
set udg_DamageEventsWasted = udg_DamageEventsWasted + 1
if udg_DamageEventsWasted == 32 then //After 32 registered units have been removed...
set udg_DamageEventsWasted = 0
//Rebuild the mass EVENT_UNIT_DAMAGED trigger:
call DestroyTrigger(udg_DamageEventTrigger)
call CreateDmgEvTrg()
set i = udg_UDexNext[0]
loop
exitwhen i == 0
if udg_UnitDamageRegistered[i] then
call TriggerRegisterUnitEvent(udg_DamageEventTrigger, udg_UDexUnits[i], EVENT_UNIT_DAMAGED)
endif
set i = udg_UDexNext[i]
endloop
endif
endif
return false
endfunction
//===========================================================================
function InitTrig_Damage_Engine takes nothing returns nothing
local unit u = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), 'uloc', 0, 0, 0)
local integer i = bj_MAX_PLAYERS //Fixed in 3.8
//Create trigger to add units to the system.
local trigger t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
call TriggerAddCondition(t, Filter(function SetupDmgEv))
//Create trigger to remove units from the system.
set t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 2.00)
call TriggerAddCondition(t, Filter(function RemoveDmgEv))
set t = null
//Execute the configuration function to first set all GUI variables:
call ExecuteFunc("Trig_Damage_Engine_Config_Actions")
//Create trigger for storing all EVENT_UNIT_DAMAGED events.
call CreateDmgEvTrg()
//Create GUI-friendly trigger for cleaning up after UnitDamageTarget.
set udg_ClearDamageEvent = CreateTrigger()
call TriggerAddCondition(udg_ClearDamageEvent, Filter(function PreClearDamageEvent))
//Disable SpellDamageAbility for every player.
loop
set i = i - 1
call SetPlayerAbilityAvailable(Player(i), udg_SpellDamageAbility, false)
exitwhen i == 0
endloop
//Preload ability
call UnitAddAbility(u, udg_SpellDamageAbility)
call RemoveUnit(u)
set u = null
endfunction
Blizzard added "Blz" as Prefix to all new natives beeing publish with 1.29, on the PTR they did not have that prefix. Bribe seems to have used the naming from the PTR, SetEventDamage -> BlzSetEventDamage.
Probably because someone reported that some old map broke since it used functions with the same name as the native. My own map would have a SetUnitName if I had made it before the Producer Update was announced.
Adding the Blz prefix makes it much less likely that an old map would have a function with the same name. I believe they will probably follow the same pattern for all new natives in the future.
Not natively possible, but probably the most frequently requested feature. There are buggy solutions which can sometimes estimate or get those values, but they are prone to errors.
With patch 1.29+it is possible to get a unit's armor value (not type) and it is possible to calculate the average, min and max physical damage a unit can produce (but not the raw damage value from an event - Mana Shield for example is a 0 damage event).
Got a question sir: Is it possible to do a healing fan of knives spell with Damage Engine? Here's the trigger:
FoK heal
Events
Game - DamageModifierEvent becomes Equal to 1.00
Conditions
(Current Order of DamageEventSource) Equal to (Order(fanofknives))
IsDamageSpell Equal to True
(Unit-type of DamageEventSource) Equal to Dummy
Actions
Set DamageEventAmount = (0.00 - DamageEventAmount)
Set DamageEventType = DamageTypeHealDamageTypeHeal
Game - Display to (All players) the text: It works!
The problem is that it damage instead of heal, whether damage in OE is set either positive or negative. It doesn't even show the "It works" text. How can I get it to work?
Ahh, okay. I think adding Berserk to dummy, order it to use it and check if dummy has Berserk buff may work. Thanks for replying sir
EDIT: It worked, thank you for this system ^^
Outstanding system! Never thought GUI can do this much
A little bit unrelated, but can you help me with the damage tag trigger? I only want the floating texts showing damage higher than 5. With damage higher than 10000, the floating texts will show 'EXECUTED' instead of the actual damage.
Another question: How to detect pure damage? I have created a DamageTypePure variable, set it to 5 (so it won't interfere with other damage types) and every ability intended to deal pure damage have had the DamageEvenType set to DamageTypePure. However, the damage type keep gets mistaken as physical damage type from attacks. How can I fix this? Did I miss out a step in the configuration stage? Could you please provide an example trigger?
*If you are unsure what pure damage is, it is basically the pre-mitigated damage before armor and magic resist. If an ability deals 300 damage, the target loses exactly 300 health, no reduction
EDIT: Fixed it. I forgot the DamageModifierEvent trigger.
I found something weird thing, or maybe its just weird for me only
I created Spell Resistance spell based off Elunes Grace, set magic damage reduction to 50%
spell breaker type of unit does not have any benefits from it at all, BUT when i gave him hero inventory and item with the same ability - it worked and stacking well
dunno is this a glitch or else
and i know, i can catch it easily in DE and modify it, but i want to do it with passive, if it possible
Updated
looks like the only working version of Elune Grace is an original, any based off copy wont work at all :\
You have to set its resistance to a negative value or remove the resistance altogether and just use Damage Engine to check if the unit carries the item and reduce accordingly.
Yeah that's because Damage Engine doesn't recognize an Elune's Grace item ability unless you insert it into the system manually. It only recognizes the default Elune's Grace item.
My recommendation is either base the reduction of of Spell Reduction Ability and use a negative value inverse to the positive value you want to block, or again just use your own damage modifying trigger.
@Bribe Dear Mr B. With patch 1.29 , we now has func that detect the "number of armor" unit have.
Could you some way implant the current wc3 physical damage caculation into the system.
Like: Dmg Multiplier = 1 - ( 1 - 0.06)^armor
Or maybe some option to create your own armor system like : Dmg Multiplier = 1 - (0.05 × armor ÷ (1 + 0.05 × |armor|))
we know that the current wc3 Dmg Multiplier cap at 200% and -71% so this is might change.
I just think that with this we can know the physical dmg before reduction Like we could use to create "Cleave/Splash" skill that effect by armor amount ,
Dmg Block/Mana Shield that block dmg before any armor
Dmg "Return" skill
Ect.
It's a useless attempt at getting the damage before reduction unless all in-game armor and damage types are inherently known. Until/if THOSE natives are added, I'm not wasting my time integrating a half-assed solution to a (universally) unsolvable problem.
If at least a native existed to determine the pre-in-game modified amount (before Mana Shield and Hardened Skin are applied), I could get rid of the remaining "spell fixer" triggers.
Just begun to use this system and I'm totally loving it, just a question.
The code for your mana shield fix seems like intended to absorb 100% of the damage, if in the case of 50% absorb, I was thinking to set 50% of DamageEventAmount to a variable and add to back at the end. Can it work that way?
Yes, before any math is done, set Damage Event Amount to a temporary Real variable, divide that variable by 2.00, then add that amount to Damage Event Amount at the bottom of the Man's Shield Fix trigger.
Can this system track pre-mitigated damage (the damage before any reductions occur)? I have an ability that cause affected unit to take pre-mitigated damage for a while. I would like to know which events/variables in your system that can track this damage.
Can this system track pre-mitigated damage (the damage before any reductions occur)? I have an ability that cause affected unit to take pre-mitigated damage for a while. I would like to know which events/variables in your system that can track this damage.
It's impossible to know what armor type a unit has, even though the latest patches allow you to know how much armor one has. So you can get part of the way there, but not enough to guarantee accuracy.
One thing you can do is not rely on in-game attack types and armor types to apply (ie. set them all to 100%), then use Damage Engine to process any further corrections. This gives you more control and allows to change effective armor and damage types at your whim.
Lastly, even if Blizzard adds GetEventDamageType and GetUnitArmorType, they'd also need to either make natives to get the Map settings for those multipliers, or the user would have to manually configure them.
Yes, before any math is done, set Damage Event Amount to a temporary Real variable, divide that variable by 2.00, then add that amount to Damage Event Amount at the bottom of the Man's Shield Fix trigger.
Thanks for the clarification. I do come across another problem, IsDamageSpell = true is not working in my custom map. Perhaps some external factors affecting this, any ideas?
Thanks for the clarification. I do come across another problem, IsDamageSpell = true is not working in my custom map. Perhaps some external factors affecting this, any ideas?
For those who are on the 1.29 patch, this version of Damage Engine (attached) is now using the SetEventDamage native.
Benefits: no longer needs Cheat Death Ability, no longer needs custom fixes for Life Drain and Finger of Death, no longer creates a trigger for each damage instance.
Also, the Damage Return Value on Locust Swarm can be switched back to its default 0.75 instead of -0.75 which was previously needed.
Disadvantages: not compatible with code pre-1.29, may have bugs which I haven't spotted (though unlikely).
For now, I will leave the actual resource unchanged, but if enough people request it I will make this 1.29 version the default Damage Engine version.
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: use the event "DamageEvent Equal to 1.00"
// - To change damage before it's dealt: use the event "DamageModifierEvent Equal to 1.00"
// - Detect damage after it was applied, use the event "AfterDamageEvent 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" (an AfterDamageEvent will not fire for this)
//
// You can specify the DamageEventType before dealing triggered damage, then run the trigger "ClearDamageEvent (Checking Conditions)" after dealing triggered damage from within a damage event:
// - Set NextDamageType = DamageTypeWhatever
// - Unit - Cause...
// - Trigger - Run ClearDamageEvent (Checking Conditions)
//
// 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".
//
//===========================================================================
// Programming note about "integer i" and "udg_DmgEvRecursionN": integer i
// ranges from -1 upwards. "udg_DmgEvRecursionN" ranges from 0 upwards.
// "integer i" is always 1 less than "udg_DmgEvRecursionN"
//
function DmgEvResetVars takes nothing returns nothing
local integer i = udg_DmgEvRecursionN - 2
set udg_DmgEvRecursionN = i + 1
if i >= 0 then
set udg_DamageEventPrevAmt = udg_LastDmgPrevAmount[i]
set udg_DamageEventAmount = udg_LastDmgValue[i]
set udg_DamageEventSource = udg_LastDmgSource[i]
set udg_DamageEventTarget = udg_LastDmgTarget[i]
set udg_IsDamageSpell = udg_LastDmgWasSpell[i]
set udg_DamageEventType = udg_LastDmgPrevType[i]
endif
endfunction
function ClearDamageEvent takes boolean clear returns nothing
if clear then
set udg_NextDamageOverride = false
set udg_NextDamageType = 0
endif
if udg_DmgEvQueued then
//The 0-second timer has expired, or there had been multiple sequential damage events running in parallel.
set udg_DmgEvQueued = false
set udg_AfterDamageEvent = 0.00
set udg_AfterDamageEvent = 1.00 //No longer fires with Unit State Event detection - this is
set udg_AfterDamageEvent = 0.00 //lighter on the system but will fire slightly later than before.
//It now only serves for counter-damage after the initial damage.
call ClearDamageEvent(true) //Check for any potential failed recursive damage from the previous event.
call DmgEvResetVars()
endif
endfunction
function DmgEvOnAOEEnd takes nothing returns nothing
if udg_DamageEventAOE > 1 then
set udg_AOEDamageEvent = 0.00
set udg_AOEDamageEvent = 1.00
set udg_AOEDamageEvent = 0.00
set udg_DamageEventAOE = 1
endif
set udg_DamageEventLevel = 1
set udg_EnhancedDamageTarget = null
call GroupClear(udg_DamageEventAOEGroup)
endfunction
function DmgEvOnExpire takes nothing returns nothing
set udg_DmgEvStarted = false //The timer has expired. Flag off to allow it to be restarted when needed.
call ClearDamageEvent(true) //Check for any lingering damage
//Reset things so they don't perpetuate for AoE/Level target detection
call DmgEvOnAOEEnd()
set udg_DamageEventTarget = null
set udg_DamageEventSource = null
endfunction
function PreClearDamageEvent takes nothing returns boolean
call ClearDamageEvent(true)
return false
endfunction
function OnUnitDamage takes nothing returns boolean
local boolean override = udg_DamageEventOverride
local integer i
local integer e = udg_DamageEventLevel
local integer a = udg_DamageEventAOE
local string s
local unit u
local unit f
call ClearDamageEvent(false) //in case the 0.00 second timer hasn't yet expired
set i = udg_DmgEvRecursionN - 1 //Had to be moved here due to false recursion tracking
if i < 0 then
//Added 25 July 2017 to detect AOE damage or multiple single-target damage
set u = udg_DamageEventTarget
set f = udg_DamageEventSource
elseif i < 16 then
set udg_LastDmgPrevAmount[i]= udg_DamageEventPrevAmt
set udg_LastDmgValue[i] = udg_DamageEventAmount
set udg_LastDmgSource[i] = udg_DamageEventSource
set udg_LastDmgTarget[i] = udg_DamageEventTarget
set udg_LastDmgWasSpell[i] = udg_IsDamageSpell
set udg_LastDmgPrevType[i] = udg_DamageEventType
else
set s = "WARNING: Recursion error when dealing damage! Make sure when you deal damage from within a DamageEvent trigger, do it like this:"
set s = s + "Trigger - Turn off (This Trigger)"
set s = s + "Unit - Cause..."
set s = s + "Trigger - Run ClearDamageEvent (Checking Conditions)"
set s = s + "Trigger - Turn on (This Trigger)"
//Delete the next couple of lines to disable the in-game recursion crash warnings
call ClearTextMessages()
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.00, 0.00, 999.00, s)
return false
endif
set udg_DmgEvRecursionN = i + 2
set udg_DamageEventAmount = GetEventDamage()
set udg_DamageEventTarget = GetTriggerUnit()
set udg_DamageEventSource = GetEventDamageSource()
set udg_DamageEventType = udg_NextDamageType
set udg_NextDamageType = 0
set udg_DamageEventOverride = udg_NextDamageOverride
set udg_NextDamageOverride = false
if i < 0 then
//Added 25 July 2017 to detect AOE damage or multiple single-target damage
if udg_DamageEventType == 0 then
if f == udg_DamageEventSource then
//Source has damaged more than once
if IsUnitInGroup(udg_DamageEventTarget, udg_DamageEventAOEGroup) then
//Added 5 August 2017 to improve tracking of enhanced damage against, say, Pulverize
set udg_DamageEventLevel = udg_DamageEventLevel + 1
set udg_EnhancedDamageTarget = udg_DamageEventTarget
else
//Multiple targets hit by this source - flag as AOE
set udg_DamageEventAOE = udg_DamageEventAOE + 1
endif
else
//New damage source - unflag everything
set u = udg_DamageEventSource
set udg_DamageEventSource = f
call DmgEvOnAOEEnd()
set udg_DamageEventSource = u
endif
call GroupAddUnit(udg_DamageEventAOEGroup, udg_DamageEventTarget)
endif
if not udg_DmgEvStarted then
set udg_DmgEvStarted = true
call TimerStart(udg_DmgEvTimer, 0.00, false, function DmgEvOnExpire)
endif
endif
if udg_DamageEventAmount == 0.00 then
if not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
set udg_DamageEventPrevAmt = 0.00
set udg_DamageEvent = 0.00
set udg_DamageEvent = 2.00
set udg_DamageEvent = 0.00
endif
call DmgEvResetVars()
else
set u = udg_DamageEventTarget
set udg_IsDamageSpell = udg_DamageEventAmount < 0.00
if udg_IsDamageSpell then
set udg_DamageEventAmount = -udg_DamageEventAmount
if IsUnitType(u, UNIT_TYPE_ETHEREAL) and not IsUnitType(u, UNIT_TYPE_HERO) then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_ETHEREAL //1.67
endif
if GetUnitAbilityLevel(u, 'Aegr') > 0 then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_ELUNES //0.80
endif
if udg_DmgEvBracers != 0 and IsUnitType(u, UNIT_TYPE_HERO) then
//Inline of UnitHasItemOfTypeBJ without the potential handle ID leak.
set i = bj_MAX_INVENTORY
loop
set i = i - 1
if GetItemTypeId(UnitItemInSlot(u, i)) == udg_DmgEvBracers then
set udg_DamageEventAmount = udg_DamageEventAmount*udg_DAMAGE_FACTOR_BRACERS //0.67
exitwhen true
endif
exitwhen i == 0
endloop
endif
endif
set udg_DamageEventPrevAmt = udg_DamageEventAmount
set udg_DamageModifierEvent = 0.00
if not udg_DamageEventOverride then
set udg_DamageModifierEvent = 1.00 //Primary modification - multiplication, blocking, reversing, etc.
set udg_DamageModifierEvent = 2.00 //Secondary Modification - addition/subtraction
set udg_DamageModifierEvent = 3.00 //Similar to 2.00 but with knowing all previous addition/subtraction have run.
endif
if udg_DamageEventAmount > 0.00 then
set udg_DamageModifierEvent = 4.00 //Apply finite damage modification such as Mana Shield or Anti-Magic Shell.
endif
set udg_DamageEventOverride = override
set udg_DamageModifierEvent = 0.00
if not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
set udg_DamageEvent = 0.00
set udg_DamageEvent = 1.00
set udg_DamageEvent = 0.00
endif
call ClearDamageEvent(true) //in case the unit state event failed from a recursive damage event
//All events have run and the damage amount is finalized.
call BlzSetEventDamage(udg_DamageEventAmount)
set udg_DmgEvQueued = true
if udg_DamageEventType < 0 and GetWidgetLife(u) - udg_DamageEventAmount <= 0.405 then
call SetUnitExploded(u, true) //In 1.29 Finger of Death works as intended.
endif
endif
set u = null
set f = null
return false
endfunction
function CreateDmgEvTrg takes nothing returns nothing
set udg_DamageEventTrigger = CreateTrigger()
call TriggerAddCondition(udg_DamageEventTrigger, Filter(function OnUnitDamage))
endfunction
function SetupDmgEv takes nothing returns boolean
local integer i = udg_UDex
local unit u = udg_UDexUnits[i]
if GetUnitAbilityLevel(u, 'Aloc') == 0 and TriggerEvaluate(gg_trg_Damage_Engine_Config) then
set udg_UnitDamageRegistered[i] = true
call TriggerRegisterUnitEvent(udg_DamageEventTrigger, u, EVENT_UNIT_DAMAGED)
call UnitAddAbility(u, udg_SpellDamageAbility)
call UnitMakeAbilityPermanent(u, true, udg_SpellDamageAbility)
endif
set u = null
return false
endfunction
function RemoveDmgEv takes nothing returns boolean
local integer i = udg_UDex
set udg_HideDamageFrom[i] = false
if udg_UnitDamageRegistered[i] then
set udg_UnitDamageRegistered[i] = false
set udg_DamageEventsWasted = udg_DamageEventsWasted + 1
if udg_DamageEventsWasted == 32 then //After 32 registered units have been removed...
set udg_DamageEventsWasted = 0
//Rebuild the mass EVENT_UNIT_DAMAGED trigger:
call DestroyTrigger(udg_DamageEventTrigger)
call CreateDmgEvTrg()
set i = udg_UDexNext[0]
loop
exitwhen i == 0
if udg_UnitDamageRegistered[i] then
call TriggerRegisterUnitEvent(udg_DamageEventTrigger, udg_UDexUnits[i], EVENT_UNIT_DAMAGED)
endif
set i = udg_UDexNext[i]
endloop
endif
endif
return false
endfunction
//===========================================================================
function InitTrig_Damage_Engine takes nothing returns nothing
local unit u = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), 'uloc', 0, 0, 0)
local integer i = bj_MAX_PLAYERS //Fixed in 3.8
//Create trigger to add units to the system.
local trigger t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
call TriggerAddCondition(t, Filter(function SetupDmgEv))
//Create trigger to remove units from the system.
set t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 2.00)
call TriggerAddCondition(t, Filter(function RemoveDmgEv))
set t = null
//Execute the configuration function to first set all GUI variables:
call ExecuteFunc("Trig_Damage_Engine_Config_Actions")
//Create trigger for storing all EVENT_UNIT_DAMAGED events.
call CreateDmgEvTrg()
//Create GUI-friendly trigger for cleaning up after UnitDamageTarget.
set udg_ClearDamageEvent = CreateTrigger()
call TriggerAddCondition(udg_ClearDamageEvent, Filter(function PreClearDamageEvent))
//Disable SpellDamageAbility for every player.
loop
set i = i - 1
call SetPlayerAbilityAvailable(Player(i), udg_SpellDamageAbility, false)
exitwhen i == 0
endloop
//Preload ability
call UnitAddAbility(u, udg_SpellDamageAbility)
call RemoveUnit(u)
set u = null
endfunction
I've re-uploaded the map. The actual resource is still forwards- and backwards-compatible, but for those who want a fully 1.29+ resource, find it attached to this post.
Can this system track pre-mitigated damage (the damage before any reductions occur)? I have an ability that cause affected unit to take pre-mitigated damage for a while. I would like to know which events/variables in your system that can track this damage.
Sorry to double-post, but I want to make sure you get a notification in case this is of interest to you or one of the others asking.
While what I replied is true, you *can* use a calculation (1.29+) to get the unit's base damage from attack index 1 or 2 (though you wouldn't know which one of the two it was damaging with):
This won't work with Critical Strike or if the damage came from a spell. It also re-calculates the damage using a potentially different random number from what WC3 calculated. But it is a valid pre-armor value nevertheless.
Sorry to double-post, but I want to make sure you get a notification in case this is of interest to you or one of the others asking.
While what I replied is true, you *can* use a calculation (1.29+) to get the unit's base damage from attack index 1 or 2 (though you wouldn't know which one of the two it was damaging with):
This won't work with Critical Strike or if the damage came from a spell. It also re-calculates the damage using a potentially different random number from what WC3 calculated. But it is a valid pre-armor value nevertheless.
I see. Thanks a lot for the information. The problem with Blizzard is that they have too many damage and armor types. In my map all values are set to 1.0 ( which means damage is modified based on the value of armor rather than its type).
I see. Thanks a lot for the information. The problem with Blizzard is that they have too many damage and armor types. In my map all values are set to 1.0 ( which means damage is modified based on the value of armor rather than its type).
I'll explain each trigger what it's supposed to do
Blood Sword
Events
Game - DamageEvent becomes Equal to 1.00
Conditions
IsDamageSpell Equal to False
(DamageEventSource has an item of type Blood Lotus) Equal to True
Actions
Set Instant_Integer = (Instant_Integer + 1)
Set Instant_Caster[Instant_Integer] = DamageEventSource
Set Instant_Damage[Instant_Integer] = DamageEventAmount
Set Item_Same = 0
Set Item_Type = Blood Lotus
For each (Integer ItemCount) from 1 to 6, do (Actions)
Loop - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Item-type of (Item carried by Instant_Caster[Instant_Integer] in slot ItemCount)) Equal to Item_Type
Then - Actions
Set Item_Same = (Item_Same + 1)
Else - Actions
Unit - Set life of Instant_Caster[Instant_Integer] to ((Life of Instant_Caster[Instant_Integer]) + (Instant_Damage[Instant_Integer] x (0.25 x (Real(Item_Same)))))
Special Effect - Create a special effect attached to the chest of Instant_Caster[Instant_Integer] using Objects\Spawnmodels\Other\HumanBloodCinematicEffect\HumanBloodCinematicEffect.mdl
Special Effect - Destroy (Last created special effect)
Set Instant_Integer = (Instant_Integer - 1)
Dealing NON-SPELL damage grants lifesteal
Chaos Blade
Events
Game - DamageEvent becomes Equal to 1.00
Conditions
IsDamageSpell Equal to False
(DamageEventSource has an item of type Chaos Blade) Equal to True
Actions
Set Instant_Integer = (Instant_Integer + 1)
Set Instant_Caster[Instant_Integer] = DamageEventSource
Set Instant_Target[Instant_Integer] = DamageEventTarget
Set Item_Same = 0
Set Item_Type = Chaos Blade
For each (Integer ItemCount) from 1 to 6, do (Actions)
Loop - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Item-type of (Item carried by Instant_Caster[Instant_Integer] in slot ItemCount)) Equal to Item_Type
Then - Actions
Set Item_Same = (Item_Same + 1)
Else - Actions
Set Instant_Damage[Instant_Integer] = (75.00 x (Real(Item_Same)))
Unit - Cause Instant_Caster[Instant_Integer] to damage Instant_Target[Instant_Integer], dealing Instant_Damage[Instant_Integer] damage of attack type Spells and damage type Normal
Set Instant_Integer = (Instant_Integer - 1)
Dealing NON-SPELL damage deals bonus SPELL damage
Elven Greatsword
Events
Game - DamageEvent becomes Equal to 1.00
Conditions
IsDamageSpell Equal to True
(Peon_Unit[(Player number of (Owner of DamageEventSource))] has an item of type Elven Greatsword) Equal to True
Or - Any (Conditions) are true
Conditions
(Unit-type of DamageEventSource) Equal to Peon
(Unit-type of DamageEventSource) Equal to DummyUnit
Actions
Set Instant_Integer = (Instant_Integer + 1)
Set Instant_Caster[Instant_Integer] = DamageEventSource
Set Instant_Target[Instant_Integer] = DamageEventTarget
Set Instant_Point[Instant_Integer] = (Position of Instant_Target[Instant_Integer])
Set Item_Same = 0
Set Item_Type = Elven Greatsword
For each (Integer ItemCount) from 1 to 6, do (Actions)
Loop - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Item-type of (Item carried by Instant_Caster[Instant_Integer] in slot ItemCount)) Equal to Item_Type
Then - Actions
Set Item_Same = (Item_Same + 1)
Else - Actions
Unit - Create 1 DummyUnit for Instant_Owner[Instant_Integer] at Instant_Point[Instant_Integer] facing (Facing of Instant_Caster[Instant_Integer]) degrees
Unit - Add a 1.00 second Generic expiration timer to (Last created unit)
Unit - Add Faerie Fire (Item) to (Last created unit)
Unit - Set level of Faerie Fire (Item) for (Last created unit) to Item_Same
Unit - Order (Last created unit) to Night Elf Druid Of The Talon - Faerie Fire Instant_Target[Instant_Integer]
Dealing SPELL damage applies Faery Fire to enemies hit
Death Frost
Events
Game - DamageEvent becomes Equal to 1.00
Conditions
IsDamageSpell Equal to True
(Peon_Unit[(Player number of (Owner of DamageEventSource))] has an item of type Death Frost) Equal to True
(Level of Frost Nova (Custom) for DamageEventSource) Equal to 0
Or - Any (Conditions) are true
Conditions
(Unit-type of DamageEventSource) Equal to Peon
(Unit-type of DamageEventSource) Equal to DummyUnit
Actions
Set Instant_Integer = (Instant_Integer + 1)
Set Instant_Caster[Instant_Integer] = DamageEventSource
Set Instant_Owner[Instant_Integer] = (Owner of Instant_Caster[Instant_Integer])
Set Instant_Target[Instant_Integer] = DamageEventTarget
Set Instant_Point[Instant_Integer] = (Position of Instant_Target[Instant_Integer])
Set Item_Same = 0
Set Item_Type = Death Frost
For each (Integer ItemCount) from 1 to 6, do (Actions)
Loop - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Item-type of (Item carried by Instant_Caster[Instant_Integer] in slot ItemCount)) Equal to Item_Type
Then - Actions
Set Item_Same = (Item_Same + 1)
Else - Actions
Unit - Create 1 DummyUnit for Instant_Owner[Instant_Integer] at Instant_Point[Instant_Integer] facing (Facing of Instant_Caster[Instant_Integer]) degrees
Unit - Add a 1.00 second Generic expiration timer to (Last created unit)
Unit - Add Frost Nova (Custom) to (Last created unit)
Unit - Set level of Frost Nova (Custom) for (Last created unit) to Item_Same
Unit - Order (Last created unit) to Undead Lich - Frost Nova Instant_Target[Instant_Integer]
Dealing SPELL damage apply Frost Nova to enemy hit
Now I shall describe what was happening in game
Now if I were to have all 4 Items (At any slot location)
-Lifesteal - ABSENT (No lifesteal at all)
-Frost Nova - ABSENT
-Spell Damage - Present
-Faery Fire - Present
Removed Lifesteal Item
-Frost Nova - ABSENT
-Spell Damage - Present
-Faery Fire - Present
Removed Lifesteal AND Faery Fire Item
-Frost Nova - Present
-Spell Damage - Present
Removed Faery Fire Item
-Lifesteal - ABSENT
-Frost Nova - Present
-Spell Damage - Present
I think I got a little confused with handling your system and also got tangled with my own mechanics
I'm not sure why you're using the InstantCaster stuff instead of directly referencing the DamageEventSource and target. DamageEngine already handles recursion internally so you don't have to.
Additionally, you can turn off DamageEventTrigger, deal the damage, then turn it back on, if you don't want that additional damage to be processed by anything (or turn off the (this trigger))
It's probably the reason why the consistency in your code could be failing.
One thing you could also do is run the trigger (Clear Damage Event) after the UnitDamage function. Otherwise, turn off the DamageEventTrigger, deal damage, then turn it back on (though this will screw up the inverse spell damage detection, so you'd have to deal spell damage as negative damage).
Can you please send over the map in its current state and let me know what I need to do to reproduce the issue? I have no idea what's wrong based on these triggers.
PM me the map if you're concerned about someone else taking it. I can assure you I would not steal your work.
neh it's ok, Imma just post it here I'm planning to post it unprotected anyway. So in the triggers there is a folder named "HERE IT IS" I've already placed some of the items that caused non-stacking issues, I've also placed the items near the HERO relevant to the active trigger in the "HERE IT IS" Folder I've also disabled most of the Item related triggers but there might still be a trigger that inteferes though (I figured cause disabling some of them solved the lifesteal issue), it can reproduced by example from what I've noticed triggers that require a Dummy to cast a spell
oh try getting all the items on the ground except the Phase Gloves, its effect will only have Faery Fire(Might be caused by the spell damage though), Lifesteal
[EDIT]: Oh and in the trigger name the ending of "InstantVar Use" means I used the Instant_Caster and Instant_Target Variable while "NoVar Use" means I used the "DamageEventSource" and "DamageEventTarget"
Bribe, what is the most efficient way to detect a specific 0 damage projectile ability?
(i.e. Stormbolt/acidbomb with regular missile speed, homing, 0 damage, and to only one target)
My goal is to create a simple projectile ability that does damage based on a value like (Attribute/mana/health) while keeping it simple and mui.
Example is what I've been doing so far
Frost bolt
Events
Game - DamageEvent becomes Equal to 2.00
Conditions
(DamageEventTarget has buff Frost bolt ) Equal to True
Actions
Unit - Remove Frost bolt buff from DamageEventTarget
Set TempInteger = 0
For each (Integer A) from 1 to 4, do (Actions)
Loop - Actions
Set TempInteger = (TempInteger + (Random integer number between 1 and 6))
Unit - Cause DamageEventSource to damage DamageEventTarget, dealing (Real((10 x TempInteger))) damage of attack type Spells and damage type Universal
There's also the DamageEventLevel functionality which determines whether the unit might have any buff caused by that damage. It works with buff-adding physical damage like Bash, Dark Arrow, Frost Arrow, etc. Flame Arrow doesn't trigger it though as there's no buff on the target.
First of all those are spells, not physical damage enhanced with buffs. I haven't tested if those fire more than one damage instance, nor if it were to fire more than once would I know if it is a zero damage event first or second.
So I think given that it's a spell only, unless you want to experiment you can try to just play with the buffs.
Triggering spells is already quite trivial with Spell System
Hi Bribe,
I'm having an issue where when I create a unit as part of a unit index event, the damage event is not registering against the unit who was indexed. That unit is receiving an index and my triggers off that index are working. When that unit deals damage the event is firing, just not when that unit is dealt damage.
Any ideas?
Everything for the unit created in the index event seems to be working.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.