-
Are you planning to upload your awesome spell or system to Hive? Please review the rules here.Dismiss Notice
-
Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.Dismiss Notice
-
DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.Dismiss Notice
-
The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.Dismiss Notice
-
The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.Dismiss Notice
-
The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.Dismiss Notice
-
The results are out! Check them out.Dismiss Notice
-
Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!Dismiss Notice
-
The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!Dismiss Notice
-
Check out the Staff job openings thread.Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.
Damage Engine 5.4.2.3
Submitted by
Bribe
Damage Engine 5.4.2.3
The most powerful - yet easy to use - Damage Engine. Ever.
Developed for GUI, with the speed of native JASS - or Lua - script.
The most powerful - yet easy to use - Damage Engine. Ever.
Developed for GUI, with the speed of native JASS - or Lua - script.
New features in Damage Engine 5:
- 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.
- 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.
- For more information, check the How to upgrade and How to use sections below.
- For even more information, check out the video my daughter and I made below:
- For even more information, check out the Damage Engine 5.4.1 "deep dive":
Featured uses for Damage Engine:
- Bone Armor 1.14.1 by @Devalut beautifully shows how powerful the DamageModifier 4.00 processing can be.
- Sunken City v2.4.4b by @SpasMaster shows how Damage Engine can be used throughout a map, visually evident by the abundant text tags.
- Damage Blocking, reducing, amplifying and/or conversion to healing;
- Does not require you to know nor use Jass NewGen Pack nor any custom script;
- Included cross-compatibility with looking_for_help's PDD and Weep's GDD;
- 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.
- Damage Engine 5 and higher requires the latest Warcraft 3 patch (currently 1.31).
- 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.
Thank you to the users of this system who have helped me mold this project into the stable, powerful form it is in today!
Before diving in, I'd like to walk you through the complex process of how this engine and WarCraft 3 interact together to process damage:
Click if you dare
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 DamageModifierEvents 1.00 through 3.00 |
|||||||
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, 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 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). |
√ |
|||||||
FAQs
- 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.
.
- Use the following custom variables to reference the event responses:
- 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 <DamageModifierEvent> becomes Equal to 1.00".
- 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).
.
- You can change the following variables to affect the damage that will be dealt:
- 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 again recursive damage?
- A: Damage Engine 5.4 and above is completely recursion-proof, using vJass hooks.
.
- Q: I've been using Weep's GDD or looking_for_help's PDD. Can I use those with Damage Engine?
- A: Better - Damage Engine has integrated cross-compatibility with those two resources. Use one of the provided custom JASS scripts instead of using their scripts in order to make the variables work with Damage Engine.
.
- 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 "DamageModifierEvent 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".
.
- Q: How do I upgrade to the latest Damage Engine?
- A: Depending on the complexity, you'll either need to re-copy the Damage Engine category or just the Damage Engine script. Generally, I do this:
- 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._._ - copy over the entire Damage Engine category as variables were added. The Config trigger is typically updated at the same time.
- Damage Engine x._._._ - this occurs very infrequently. Typically requires changes to the way Damage Engine needs to be installed and used.
Damage Engine Triggers
-
Damage Engine Config
-
Events
-
Map initialization
-
-
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 DamageTypePureExplosive = -2
-
Set DamageTypeExplosive = -1
-
Set DamageTypeCode = 1
-
Set DamageTypePure = 2
-
-------- - --------
-
Set DamageTypeHeal = 3
-
Set DamageTypeBlocked = 4
-
Set DamageTypeReduced = 5
-
-------- - --------
-
Set DamageTypeCriticalStrike = 6
-
-------- - --------
-
-------- Added 25 July 2017 to allow detection of things like Bash or Pulverize or AOE spread --------
-
-------- - --------
-
Set DamageEventAOE = 1
-
Set DamageEventLevel = 1
-
-------- - --------
-
-------- In-game World Editor doesn't allow Attack Type and Damage Type comparisons. Therefore I need to code them as integers into GUI --------
-
-------- - --------
-
Set ATTACK_TYPE_SPELLS = 0
-
Set ATTACK_TYPE_NORMAL = 1
-
Set ATTACK_TYPE_PIERCE = 2
-
Set ATTACK_TYPE_SIEGE = 3
-
Set ATTACK_TYPE_MAGIC = 4
-
Set ATTACK_TYPE_CHAOS = 5
-
Set ATTACK_TYPE_HERO = 6
-
-------- - --------
-
Set DAMAGE_TYPE_UNKNOWN = 0
-
Set DAMAGE_TYPE_NORMAL = 4
-
Set DAMAGE_TYPE_ENHANCED = 5
-
Set DAMAGE_TYPE_FIRE = 8
-
Set DAMAGE_TYPE_COLD = 9
-
Set DAMAGE_TYPE_LIGHTNING = 10
-
Set DAMAGE_TYPE_POISON = 11
-
Set DAMAGE_TYPE_DISEASE = 12
-
Set DAMAGE_TYPE_DIVINE = 13
-
Set DAMAGE_TYPE_MAGIC = 14
-
Set DAMAGE_TYPE_SONIC = 15
-
Set DAMAGE_TYPE_ACID = 16
-
Set DAMAGE_TYPE_FORCE = 17
-
Set DAMAGE_TYPE_DEATH = 18
-
Set DAMAGE_TYPE_MIND = 19
-
Set DAMAGE_TYPE_PLANT = 20
-
Set DAMAGE_TYPE_DEFENSIVE = 21
-
Set DAMAGE_TYPE_DEMOLITION = 22
-
Set DAMAGE_TYPE_SLOW_POISON = 23
-
Set DAMAGE_TYPE_SPIRIT_LINK = 24
-
Set DAMAGE_TYPE_SHADOW_STRIKE = 25
-
Set DAMAGE_TYPE_UNIVERSAL = 26
-
-------- - --------
-
-------- The below variables don't affect damage amount, but do affect the sound played --------
-
-------- They also give important information about the type of attack used. --------
-
-------- They can differentiate between ranged and melee for units who are both --------
-
-------- - --------
-
Set WEAPON_TYPE_NONE = 0
-
-------- Metal Light/Medium/Heavy --------
-
Set WEAPON_TYPE_ML_CHOP = 1
-
Set WEAPON_TYPE_MM_CHOP = 2
-
Set WEAPON_TYPE_MH_CHOP = 3
-
Set WEAPON_TYPE_ML_SLICE = 4
-
Set WEAPON_TYPE_MM_SLICE = 5
-
Set WEAPON_TYPE_MH_SLICE = 6
-
Set WEAPON_TYPE_MM_BASH = 7
-
Set WEAPON_TYPE_MH_BASH = 8
-
Set WEAPON_TYPE_MM_STAB = 9
-
Set WEAPON_TYPE_MH_STAB = 10
-
-------- Wood Light/Medium/Heavy --------
-
Set WEAPON_TYPE_WL_SLICE = 11
-
Set WEAPON_TYPE_WM_SLICE = 12
-
Set WEAPON_TYPE_WH_SLICE = 13
-
Set WEAPON_TYPE_WL_BASH = 14
-
Set WEAPON_TYPE_WM_BASH = 15
-
Set WEAPON_TYPE_WH_BASH = 16
-
Set WEAPON_TYPE_WL_STAB = 17
-
Set WEAPON_TYPE_WM_STAB = 18
-
-------- Claw Light/Medium/Heavy --------
-
Set WEAPON_TYPE_CL_SLICE = 19
-
Set WEAPON_TYPE_CM_SLICE = 20
-
Set WEAPON_TYPE_CH_SLICE = 21
-
-------- Axe Medium --------
-
Set WEAPON_TYPE_AM_CHOP = 22
-
-------- Rock Heavy --------
-
Set WEAPON_TYPE_RH_BASH = 23
-
-------- - --------
-
-------- Since GUI still doesn't provide Defense Type and Armor Types, I needed to include the below --------
-
-------- - --------
-
Set ARMOR_TYPE_NONE = 0
-
Set ARMOR_TYPE_FLESH = 1
-
Set ARMOR_TYPE_METAL = 2
-
Set ARMOR_TYPE_WOOD = 3
-
Set ARMOR_TYPE_ETHEREAL = 4
-
Set ARMOR_TYPE_STONE = 5
-
-------- - --------
-
Set DEFENSE_TYPE_LIGHT = 0
-
Set DEFENSE_TYPE_MEDIUM = 1
-
Set DEFENSE_TYPE_HEAVY = 2
-
Set DEFENSE_TYPE_FORTIFIED = 3
-
Set DEFENSE_TYPE_NORMAL = 4
-
Set DEFENSE_TYPE_HERO = 5
-
Set DEFENSE_TYPE_DIVINE = 6
-
Set DEFENSE_TYPE_UNARMORED = 7
-
-------- - --------
-
Custom script: call DamageEngine_DebugStr()
-
-
Code (vJASS):
//===========================================================================
//
// Damage Engine 5.4.2.3 - update requires copying of the JASS script
//
//===========================================================================
library DamageEngine initializer Init
globals
private timer alarm = CreateTimer()
private boolean alarmSet = false
//Values to track the original pre-spirit Link/defensive damage values
private boolean canKick = true
private boolean totem = false
private real lastAmount = 0.00
private real lastPrevAmt = 0.00
private integer lastType = 0
private boolean lastCode = false
private real lastPierced = 0.00
private integer armorType = 0
private integer lastArmor = 0
private integer lastPrevArmor = 0
private integer defenseType = 0
private integer lastDefense = 0
private integer lastPrevDefense = 0
//Stuff to track recursive UnitDamageTarget calls.
private boolean eventsRun = false
private boolean kicking = false
private integer damageStack = 0
private unit array sourceStack
private unit array targetStack
private real array amountStack
private attacktype array attackTStack
private damagetype array damageTStack
private weapontype array weaponTStack
private integer array userTrigStack
private integer array typeStack
//Added in 5.4 to silently eliminate infinite recursion.
private integer userTrigs = 9
private integer eventTrig = 0
private integer array nextTrig
private trigger array userTrig
private boolean array trigFrozen
//Added/re-tooled in 5.4.1 to allow forced recursion (for advanced users only).
private constant integer LIMBO = 16 //Recursion will never go deeper than LIMBO.
private integer array levelsDeep //How deep the user recursion currently is.
public boolean inception = false //You must set DamageEngine_inception = true before dealing damage to utlize this.
//When true, it allows your trigger to potentially go recursive up to LIMBO.
private boolean dreaming = false
private boolean array inceptionTrig //Added in 5.4.2 to simplify the inception variable for very complex DamageEvent trigger.
private integer sleepLevel = 0
private group proclusGlobal = CreateGroup() //track sources of recursion
private group fischerMorrow = CreateGroup() //track targets of recursion
//Improves readability in the code to have these as named constants.
private constant integer MOD_EVENT = 1
private constant integer SHIELD_EVENT = 4
private constant integer DAMAGE_EVENT = 5
private constant integer ZERO_EVENT = 6
private constant integer AFTER_EVENT = 7
private constant integer LETHAL_EVENT = 8
private constant integer AOE_EVENT = 9
//private string crashStr = ""
endglobals
//GUI Vars:
/*
Retained from 3.8 and prior:
----------------------------
unit udg_DamageEventSource
unit udg_DamageEventTarget
unit udg_EnhancedDamageTarget
group udg_DamageEventAOEGroup
integer udg_DamageEventAOE
integer udg_DamageEventLevel
real udg_DamageModifierEvent
real udg_DamageEvent
real udg_AfterDamageEvent
real udg_DamageEventAmount
real udg_DamageEventPrevAmt
real udg_AOEDamageEvent
boolean udg_DamageEventOverride
boolean udg_NextDamageType
boolean udg_DamageEventType
boolean udg_IsDamageSpell
//Added in 5.0:
boolean udg_IsDamageMelee
boolean udg_IsDamageRanged
unit udg_AOEDamageSource
real udg_LethalDamageEvent
real udg_LethalDamageHP
real udg_DamageScalingWC3
integer udg_DamageEventAttackT
integer udg_DamageEventDamageT
integer udg_DamageEventWeaponT
//Added in 5.1:
boolean udg_IsDamageCode
//Added in 5.2:
integer udg_DamageEventArmorT
integer udg_DamageEventDefenseT
//Addded in 5.3:
real DamageEventArmorPierced
real udg_DamageScalingUser
//Added in 5.4.2 to allow GUI users to re-issue the exact same attack and damage type at the attacker.
attacktype array udg_CONVERTED_ATTACK_TYPE
damagetype array udg_CONVERTED_DAMAGE_TYPE
*/
private function RunTrigs takes integer i returns nothing
local integer cat = i
if dreaming then
//call BJDebugMsg("Tried to run triggers while triggers were already running.")
return
endif
set dreaming = true
//call BJDebugMsg("Start of event running")
loop
set i = nextTrig[i]
exitwhen i == 0
exitwhen cat == MOD_EVENT and (udg_DamageEventOverride or udg_DamageEventType*udg_DamageEventType == 4)
exitwhen cat == SHIELD_EVENT and udg_DamageEventAmount <= 0.00
exitwhen cat == LETHAL_EVENT and udg_LethalDamageHP > 0.405
//set crashStr = "Bout to inspect " + I2S(i)
if not trigFrozen[i] and IsTriggerEnabled(userTrig[i]) then
set eventTrig = i
//set crashStr = "Bout to evaluate " + I2S(i)
if TriggerEvaluate(userTrig[i]) then
//set crashStr = "Bout to execute " + I2S(i)
call TriggerExecute(userTrig[i])
endif
//set crashStr = "Ran " + I2S(i)
//call BJDebugMsg("Ran " + I2S(i))
//if not (udg_DamageEventPrevAmt == 0.00 or udg_DamageScalingWC3 == 0.00 or udg_DamageEventAmount == 0.00) then
// if cat == MOD_EVENT then
// set udg_DamageScalingUser = udg_DamageEventAmount/udg_DamageEventPrevAmt
// elseif cat == SHIELD_EVENT then
// set udg_DamageScalingUser = udg_DamageEventAmount/udg_DamageEventPrevAmt/udg_DamageScalingWC3
// endif
//elseif udg_DamageEventPrevAmt == 0.00 then
// call BJDebugMsg("Prev amount 0.00 and User Amount " + R2S(udg_DamageEventAmount))
//elseif udg_DamageEventAmount == 0.00 then
// call BJDebugMsg("User amount 0.00 and Prev Amount " + R2S(udg_DamageEventPrevAmt))
//elseif udg_DamageScalingWC3 == 0.00 then
// call BJDebugMsg("WC3 amount somehow 0.00")
//endif
//set crashStr = "Filtered " + I2S(i)
//elseif i > 9 then
// if trigFrozen[i] then
// call BJDebugMsg("User Trigger is frozen")
// else
// call BJDebugMsg("User Trigger is off")
// endif
endif
endloop
//call BJDebugMsg("End of event running")
set dreaming = false
endfunction
private function OnAOEEnd takes nothing returns nothing
if udg_DamageEventAOE > 1 then
call RunTrigs(AOE_EVENT)
set udg_DamageEventAOE = 1
endif
set udg_DamageEventLevel = 1
set udg_EnhancedDamageTarget = null
set udg_AOEDamageSource = null
call GroupClear(udg_DamageEventAOEGroup)
endfunction
private function AfterDamage takes nothing returns nothing
if udg_DamageEventPrevAmt != 0.00 and udg_DamageEventDamageT != udg_DAMAGE_TYPE_UNKNOWN then
call RunTrigs(AFTER_EVENT)
endif
endfunction
private function Finish takes nothing returns nothing
local integer i = 0
local integer exit
if eventsRun then
//call BJDebugMsg("events ran")
set eventsRun = false
call AfterDamage()
endif
if canKick and not kicking then
//call BJDebugMsg("can kick")
if damageStack > 0 then
set kicking = true
//call BJDebugMsg("Clearing queued damage instances: " + I2S(damageStack))
loop
set exit = damageStack
set sleepLevel = sleepLevel + 1
loop
set udg_NextDamageType = typeStack[i]
//call BJDebugMsg("Stacking on " + R2S(amountStack[i]))
call UnitDamageTarget(sourceStack[i], targetStack[i], amountStack[i], true, false, attackTStack[i], damageTStack[i], weaponTStack[i])
call AfterDamage()
set i = i + 1 //Need to loop bottom to top to make sure damage order is preserved.
exitwhen i == exit
endloop
//call BJDebugMsg("Exit at: " + I2S(i))
exitwhen i == damageStack
endloop
//call BJDebugMsg("Terminate at at: " + I2S(i))
set sleepLevel = 0
loop
set i = i - 1
set trigFrozen[userTrigStack[i]] = false //Only re-enable recursive triggers AFTER all damage is dealt.
set levelsDeep[userTrigStack[i]] = 0 //Reset this stuff if the user tried some nonsense
exitwhen i == 0
endloop
//call BJDebugMsg("Cleared queued damage instances: " + I2S(damageStack))
set damageStack = 0 //Can only be set after all the damage has successfully ended.
set kicking = false
endif
call GroupClear(proclusGlobal)
call GroupClear(fischerMorrow)
//elseif kicking then
// call BJDebugMsg("Somehow still kicking")
//else
// call BJDebugMsg("Cannot kick")
endif
endfunction
private function ResetArmor takes nothing returns nothing
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
endfunction
private function FailsafeClear takes nothing returns nothing
//call BJDebugMsg("Damage from " + GetUnitName(udg_DamageEventSource) + " to " + GetUnitName(udg_DamageEventTarget) + " has been messing up Damage Engine.")
//call BJDebugMsg(R2S(udg_DamageEventAmount) + " " + " " + R2S(udg_DamageEventPrevAmt) + " " + udg_AttackTypeDebugStr[udg_DamageEventAttackT] + " " + udg_DamageTypeDebugStr[udg_DamageEventDamageT])
call ResetArmor()
set canKick = true
set totem = false
set udg_DamageEventAmount = 0.00
set udg_DamageScalingWC3 = 0.00
if udg_DamageEventDamageT != udg_DAMAGE_TYPE_UNKNOWN then
call RunTrigs(DAMAGE_EVENT) //Run the normal on-damage event based on this failure.
set eventsRun = true //Run the normal after-damage event based on this failure.
endif
call Finish()
endfunction
private function WakeUp takes nothing returns nothing
set alarmSet = false //The timer has expired. Flag off to allow it to be restarted when needed.
//if dreaming then
// set dreaming= false
// call BJDebugMsg("Timer set dreaming to False")
// call BJDebugMsg(crashStr)
//endif
if totem then
//Something went wrong somewhere; the WarCraft 3 engine didn't run the DAMAGED event despite running the DAMAGING event.
call FailsafeClear()
else
if not canKick and damageStack > 0 then
//call BJDebugMsg("Damage Engine recursion deployment was failing with application of: " + R2S(udg_DamageEventAmount))
set canKick = true
endif
call Finish() //Wrap up any outstanding damage instance
endif
call OnAOEEnd() //Reset things so they don't perpetuate for AoE/Level target detection
set udg_DamageEventPrevAmt = 0.00 //Added in 5.4.2.1 to try to squash the Cold Arrows glitch (failed to do it)
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()
//call BJDebugMsg("First damage event running")
if dreaming then
//call BJDebugMsg("Dreaming")
if amt != 0.00 then
//Store recursive damage into a queue from index "damageStack" (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 amountStack[damageStack] = amt
set sourceStack[damageStack] = src
set targetStack[damageStack] = tgt
set attackTStack[damageStack] = at
set damageTStack[damageStack] = dt
set weaponTStack[damageStack] = wt
set userTrigStack[damageStack] = eventTrig
if udg_NextDamageType == 0 then
set typeStack[damageStack] = udg_DamageTypeCode
else
set typeStack[damageStack] = udg_NextDamageType
endif
//Next block added in 5.4.1 to allow *some* control over whether recursion should kick
//in. Also it's important to track whether the source and target were both involved at
//some earlier point, so this is a more accurate and lenient method than before.
set inception = inception or inceptionTrig[eventTrig]
call GroupAddUnit(proclusGlobal, udg_DamageEventSource)
call GroupAddUnit(fischerMorrow, udg_DamageEventTarget)
if kicking and IsUnitInGroup(src, proclusGlobal) and IsUnitInGroup(tgt, fischerMorrow) then
if inception and not trigFrozen[eventTrig] then
set inceptionTrig[eventTrig] = true
if levelsDeep[eventTrig] < sleepLevel then
set levelsDeep[eventTrig] = levelsDeep[eventTrig] + 1
if levelsDeep[eventTrig] >= LIMBO then
set trigFrozen[eventTrig] = true
endif
endif
else
set trigFrozen[eventTrig] = true
endif
endif
set damageStack = damageStack + 1
//call BJDebugMsg("damageStack: " + I2S(damageStack) + " levelsDeep: " + I2S(levelsDeep[eventTrig]) + " sleepLevel: " + I2S(sleepLevel))
call BlzSetEventDamage(0.00) //queue the damage instance instead of letting it run recursively
endif
else
if not kicking then
//Added 25 July 2017 to detect AOE damage or multiple single-target damage
if alarmSet then
if totem then
if dt != DAMAGE_TYPE_SPIRIT_LINK and dt != DAMAGE_TYPE_DEFENSIVE and dt != DAMAGE_TYPE_PLANT then
//if 'totem' is still set and it's not due to spirit link distribution or defense retaliation,
//the next function must be called as a debug. This reverts an issue I created in patch 5.1.3.
call FailsafeClear()
else
set totem = false
set lastAmount = udg_DamageEventAmount
set lastPrevAmt = udg_DamageEventPrevAmt //Store the actual pre-armor value.
set lastType = udg_DamageEventType //also store the damage type.
set lastCode = udg_IsDamageCode //store this as well.
set lastArmor = udg_DamageEventArmorT
set lastPrevArmor = armorType
set lastDefense = udg_DamageEventDefenseT
set lastPrevDefense = defenseType
set lastPierced = udg_DamageEventArmorPierced
set canKick = false
endif
else
call Finish()
endif
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
else
call TimerStart(alarm, 0.00, false, function WakeUp)
set alarmSet = true
set udg_AOEDamageSource = src
set udg_EnhancedDamageTarget= tgt
endif
call GroupAddUnit(udg_DamageEventAOEGroup, tgt)
endif
set udg_DamageEventType = udg_NextDamageType
set udg_IsDamageCode = udg_NextDamageType != 0
set udg_DamageEventOverride = dt == null //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
set udg_DamageScalingUser = 1.00
set udg_DamageScalingWC3 = 1.00
if amt != 0.00 then
if not udg_DamageEventOverride then
call RunTrigs(MOD_EVENT)
//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
//call BJDebugMsg("Ready to deal " + R2S(udg_DamageEventAmount))
set totem = true
else
call RunTrigs(ZERO_EVENT)
set canKick = true
call Finish()
endif
endif
set src = null
set tgt = null
set inception = false
set udg_NextDamageType = 0
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()
//call BJDebugMsg("Second damage event running")
if dreaming or udg_DamageEventPrevAmt == 0.00 then
//if dreaming then
// call BJDebugMsg("Dreaming")
//else
// call BJDebugMsg("Prev amount is zero")
//endif
return false
endif
if totem then
set totem = false //This should be the case in almost all circumstances
else
call AfterDamage() //Wrap up the outstanding damage instance
set canKick = true
//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_DamageEventSource = GetEventDamageSource()
set udg_DamageEventTarget = GetTriggerUnit()
set udg_DamageEventAmount = lastAmount
set udg_DamageEventPrevAmt = lastPrevAmt
set udg_DamageEventAttackT = GetHandleId(BlzGetEventAttackType())
set udg_DamageEventDamageT = GetHandleId(BlzGetEventDamageType())
set udg_DamageEventWeaponT = GetHandleId(BlzGetEventWeaponType())
set udg_DamageEventType = lastType
set udg_IsDamageCode = lastCode
set udg_DamageEventArmorT = lastArmor
set udg_DamageEventDefenseT = lastDefense
set udg_DamageEventArmorPierced = lastPierced
set armorType = lastPrevArmor
set defenseType = lastPrevDefense
call CalibrateMR() //Apply melee/ranged settings once again.
endif
call ResetArmor()
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
set udg_DamageScalingUser = udg_DamageEventAmount / udg_DamageEventPrevAmt
endif
set udg_DamageEventAmount = udg_DamageEventAmount*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(SHIELD_EVENT)
set udg_LethalDamageHP = GetWidgetLife(udg_DamageEventTarget) - udg_DamageEventAmount
if udg_LethalDamageHP <= 0.405 then
call RunTrigs(LETHAL_EVENT) //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 udg_DamageEventDamageT != udg_DAMAGE_TYPE_UNKNOWN then
call RunTrigs(DAMAGE_EVENT)
endif
set eventsRun = true
if udg_DamageEventAmount == 0.00 then
call Finish()
endif
return false
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
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))
set trig = null
endfunction
public function DebugStr takes nothing returns nothing
local integer i = 0
loop
set udg_CONVERTED_ATTACK_TYPE[i] = ConvertAttackType(i)
exitwhen i == 6
set i = i + 1
endloop
set i = 0
loop
set udg_CONVERTED_DAMAGE_TYPE[i] = ConvertDamageType(i)
exitwhen i == 26
set i = i + 1
endloop
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" //Typically deals flat damage to all armor types
set udg_DefenseTypeDebugStr[5] = "HERO"
set udg_DefenseTypeDebugStr[6] = "DIVINE"
set udg_DefenseTypeDebugStr[7] = "UNARMORED"
set udg_ArmorTypeDebugStr[0] = "NONE" //ARMOR_TYPE_WHOKNOWS in JASS, added in 1.31
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 dreaming
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 //MOD_EVENT 1-4 -> Events 1-4
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 //DAMAGE_EVENT 1,2 -> Events 5,6
set max = 2
set off = 4
elseif var == "udg_AfterDamageEvent" then //AFTER_EVENT -> Event 7
set off = 6
elseif var == "udg_LethalDamageEvent" then //LETHAL_EVENT -> Event 8
set off = 7
elseif var == "udg_AOEDamageEvent" then //AOE_EVENT -> Event 9
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 //User list runs from index 10 and up
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
Lua Script
Code (Lua):
--[[
===========================================================================
Lua Version
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 was dealt to the unit): use the event "DamageEvent Equal to 1.00"
- To change damage before it is 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".
GUI Vars:
Retained from 3.8 and prior:
----------------------------
unit udg_DamageEventSource
unit udg_DamageEventTarget
unit udg_EnhancedDamageTarget
group udg_DamageEventAOEGroup
integer udg_DamageEventAOE
integer udg_DamageEventLevel
real udg_DamageModifierEvent
real udg_DamageEvent
real udg_AfterDamageEvent
real udg_DamageEventAmount
real udg_DamageEventPrevAmt
real udg_AOEDamageEvent
boolean udg_DamageEventOverride
boolean udg_NextDamageType
boolean udg_DamageEventType
boolean udg_IsDamageSpell
Added in 5.0:
boolean udg_IsDamageMelee
boolean udg_IsDamageRanged
unit udg_AOEDamageSource
real udg_LethalDamageEvent
real udg_LethalDamageHP
real udg_DamageScalingWC3
integer udg_DamageEventAttackT
integer udg_DamageEventDamageT
integer udg_DamageEventWeaponT
Added in 5.1:
boolean udg_IsDamageCode
Added in 5.2:
integer udg_DamageEventArmorT
integer udg_DamageEventDefenseT
Addded in 5.3:
real DamageEventArmorPierced
real udg_DamageScalingUser
Added in 5.4.2 to allow GUI users to re-issue the exact same attack and damage type at the attacker.
attacktype array udg_CONVERTED_ATTACK_TYPE
damagetype array udg_CONVERTED_DAMAGE_TYPE
=============================================================================
--]]
do
local alarm = CreateTimer()
local alarmSet = false
--Values to track the original pre-spirit Link/defensive damage values
local canKick = true
local totem = false
local armorType = 0
local defenseType = 0
local prev = {}
--Stuff to track recursive UnitDamageTarget calls.
local eventsRun = false
local kicking = false
local stack = {}
--Added in 5.4 to silently eliminate infinite recursion.
local userTrigs = 9
local eventTrig = 0
local nextTrig = {}
local userTrig = {}
local trigFrozen = {}
--Added/re-tooled in 5.4.1 to allow forced recursion (for advanced users only).
local levelsDeep = {} --How deep the user recursion currently is.
local LIMBO = 16 --Recursion will never go deeper than LIMBO.
DamageEngine_inception= false --You must set DamageEngine_inception = true before dealing damage to utlize this.
--When true, it allows your trigger to potentially go recursive up to LIMBO.
local dreaming = false
local fischerMorrow = {} --track targets of recursion
local inceptionTrig = {} --Added in 5.4.2 to simplify the inception variable for very complex DamageEvent trigger.
local proclusGlobal = {} --track sources of recursion
local sleepLevel = 0
--Improves readability in the code to have these as named constants.
local event = {
mod = 1,
shield = 4,
damage = 5,
zero = 6,
after = 7,
lethal = 8,
aoe = 9
}
local function runTrigs(i)
local cat = i
dreaming = true
--print("Running " .. cat)
while (true) do
i = nextTrig[i]
if (i == 0)
or (cat == event.mod and (udg_DamageEventOverride or udg_DamageEventType*udg_DamageEventType == 4))
or (cat == event.shield and udg_DamageEventAmount <= 0.00)
or (cat == event.lethal and udg_LethalDamageHP > 0.405) then
break
end
if not trigFrozen[i] then
eventTrig = i
if RunTrigger then --Added 10 July 2019 to enable FastTriggers mode.
RunTrigger(userTrig[i])
elseif IsTriggerEnabled(userTrig[i])
and TriggerEvaluate(userTrig[i]) then
TriggerExecute(userTrig[i])
end
--print("Ran " .. i)
end
end
--print("Ran")
dreaming = false
end
local function onAOEEnd()
if udg_DamageEventAOE > 1 then
runTrigs(event.aoe)
udg_DamageEventAOE = 1
end
udg_DamageEventLevel = 1
udg_EnhancedDamageTarget= nil
udg_AOEDamageSource = nil
GroupClear(udg_DamageEventAOEGroup)
end
local function afterDamage()
if udg_DamageEventPrevAmt ~= 0.00 and udg_DamageEventDamageT ~= udg_DAMAGE_TYPE_UNKNOWN then
runTrigs(event.after)
end
end
local oldUDT = UnitDamageTarget
local function finish()
if eventsRun then
--print "events ran"
eventsRun = false
afterDamage()
end
if canKick and not kicking then
local n = #stack
if n > 0 then
kicking = true
--print("Clearing Recursion: " .. n)
local i = 0
local open
repeat
sleepLevel = sleepLevel + 1
repeat
i = i + 1 --Need to loop bottom to top to make sure damage order is preserved.
open = stack[i]
udg_NextDamageType = open.type
--print("Stacking on " .. open.amount)
oldUDT(open.source, open.target, open.amount, true, false, open.attack, open.damage, open.weapon)
afterDamage()
until (i == n)
--print("Exit at: " .. i)
n = #stack
until (i == n)
--print("Terminate at: " .. i)
sleepLevel = 0
repeat
open = stack[i].trig
stack[i] = nil
proclusGlobal[open] = nil
fischerMorrow[open] = nil
trigFrozen[open] = false -- Only re-enable recursive triggers AFTER all damage is dealt.
levelsDeep[open] = 0 --Reset this stuff if the user tried some nonsense
--print("unfreezing " .. open)
i = i - 1
until (i == 0)
kicking = false
end
end
end
function UnitDamageTarget(src, tgt, amt, a, r, at, dt, wt)
if udg_NextDamageType == 0 then
udg_NextDamageType = udg_DamageTypeCode
end
local b = false
if dreaming then
if amt ~= 0.00 then
-- Store triggered, recursive damage into a stack.
-- This damage will be fired after the current damage instance has wrapped up its events.
stack[#stack + 1] = {
type = udg_NextDamageType,
source = src,
target = tgt,
amount = amt,
attack = at,
damage = dt,
weapon = wt,
trig = eventTrig
}
--print("increasing damage stack: " .. #stack)
-- Next block added in 5.4.1 to allow *some* control over whether recursion should kick
-- in. Also it's important to track whether the source and target were both involved at
-- some earlier point, so this is a more accurate and lenient method than before.
DamageEngine_inception = DamageEngine_inception or inceptionTrig[eventTrig]
local sg = proclusGlobal[eventTrig]
if not sg then
sg = {}
proclusGlobal[eventTrig] = sg
end
sg[udg_DamageEventSource] = true
local tg = fischerMorrow[eventTrig]
if not tg then
tg = {}
fischerMorrow[eventTrig] = tg
end
tg[udg_DamageEventTarget] = true
if kicking and sg[src] and tg[tgt] then
if DamageEngine_inception and not trigFrozen[eventTrig] then
inceptionTrig[eventTrig] = true
if levelsDeep[eventTrig] < sleepLevel then
levelsDeep[eventTrig] = levelsDeep[eventTrig] + 1
if levelsDeep[eventTrig] >= LIMBO then
--print("freezing inception trig: " .. eventTrig)
trigFrozen[eventTrig] = true
end
end
else
--print("freezing standard trig: " .. eventTrig)
trigFrozen[eventTrig] = true
end
end
end
else
b = oldUDT(src, tgt, amt, a, r, at, dt, wt)
end
--print("setting inception to false")
DamageEngine_inception = false
udg_NextDamageType = 0
if b and not dreaming then
finish() -- Wrap up the outstanding damage instance right away.
end
return b
end
local function resetArmor()
if udg_DamageEventArmorPierced ~= 0.00 then
BlzSetUnitArmor(udg_DamageEventTarget, BlzGetUnitArmor(udg_DamageEventTarget) + udg_DamageEventArmorPierced)
end
if armorType ~= udg_DamageEventArmorT then
BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_ARMOR_TYPE, armorType) --revert changes made to the damage instance
end
if defenseType ~= udg_DamageEventDefenseT then
BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_DEFENSE_TYPE, defenseType)
end
end
local function failsafeClear()
--print("Damage from " .. GetUnitName(udg_DamageEventSource) .. " to " .. GetUnitName(udg_DamageEventTarget) .. " has been messing up Damage Engine.")
--print(udg_DamageEventAmount .. " " .. " " .. udg_DamageEventPrevAmt .. " " .. udg_AttackTypeDebugStr[udg_DamageEventAttackT] .. " " .. udg_DamageTypeDebugStr[udg_DamageEventDamageT])
resetArmor()
canKick = true
totem = false
udg_DamageEventAmount = 0.00
udg_DamageScalingWC3 = 0.00
if udg_DamageEventDamageT ~= udg_DAMAGE_TYPE_UNKNOWN then
runTrigs(event.damage) --Run the normal on-damage event based on this failure.
eventsRun = true --Run the normal after-damage event based on this failure.
end
finish()
end
local function calibrateMR()
udg_IsDamageMelee = false
udg_IsDamageRanged = false
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.
udg_IsDamageMelee = IsUnitType(udg_DamageEventSource, UNIT_TYPE_MELEE_ATTACKER)
udg_IsDamageRanged = IsUnitType(udg_DamageEventSource, UNIT_TYPE_RANGED_ATTACKER)
if udg_IsDamageMelee and udg_IsDamageRanged then
udg_IsDamageMelee = udg_DamageEventWeaponT > 0-- Melee units play a sound when damaging
udg_IsDamageRanged = not udg_IsDamageMelee -- In the case where a unit is both ranged and melee, the ranged attack plays no sound.
end -- The Huntress has a melee sound for her ranged projectile, however it is only an issue
end --if she also had a melee attack, because by default she is only UNIT_TYPE_RANGED_ATTACKER.
end
local t1 = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(t1, EVENT_PLAYER_UNIT_DAMAGING)
TriggerAddCondition(t1, Filter(function()
local src = GetEventDamageSource()
local tgt = BlzGetEventDamageTarget()
local amt = GetEventDamage()
local at = BlzGetEventAttackType()
local dt = BlzGetEventDamageType()
local wt = BlzGetEventWeaponType()
--print "First damage event running"
if not kicking then
if alarmSet then
if totem then
if dt ~= DAMAGE_TYPE_SPIRIT_LINK and dt ~= DAMAGE_TYPE_DEFENSIVE and dt ~= DAMAGE_TYPE_PLANT then
-- if 'totem' is still set and it's not due to spirit link distribution or defense retaliation,
-- the next function must be called as a debug. This reverts an issue I created in patch 5.1.3.
failsafeClear()
else
totem = false
canKick = false
prev.type = udg_DamageEventType -- also store the damage type.
prev.amount = udg_DamageEventAmount
prev.preAmt = udg_DamageEventPrevAmt -- Store the actual pre-armor value.
prev.pierce = udg_DamageEventArmorPierced
prev.armor = udg_DamageEventArmorT
prev.preArm = armorType
prev.defense= udg_DamageEventDefenseT
prev.preDef = defenseType
prev.code = udg_IsDamageCode -- store this as well.
end
end
if src ~= udg_AOEDamageSource then -- Source has damaged more than once
onAOEEnd() -- New damage source - unflag everything
udg_AOEDamageSource = src
elseif tgt == udg_EnhancedDamageTarget then
udg_DamageEventLevel= udg_DamageEventLevel + 1 -- The number of times the same unit was hit.
elseif not IsUnitInGroup(tgt, udg_DamageEventAOEGroup) then
udg_DamageEventAOE = udg_DamageEventAOE + 1 -- Multiple targets hit by this source - flag as AOE
end
else
TimerStart(alarm, 0.00, false, function()
alarmSet = false --The timer has expired. Flag off to allow it to be restarted when needed.
finish() --Wrap up any outstanding damage instance
onAOEEnd() --Reset things so they don't perpetuate for AoE/Level target detection
end)
alarmSet = true
udg_AOEDamageSource = src
udg_EnhancedDamageTarget= tgt
end
GroupAddUnit(udg_DamageEventAOEGroup, tgt)
end
udg_DamageEventType = udg_NextDamageType
udg_IsDamageCode = udg_NextDamageType ~= 0
udg_DamageEventOverride = dt == nil -- Got rid of NextDamageOverride in 5.1 for simplicity
udg_DamageEventPrevAmt = amt
udg_DamageEventSource = src
udg_DamageEventTarget = tgt
udg_DamageEventAmount = amt
udg_DamageEventAttackT = GetHandleId(at)
udg_DamageEventDamageT = GetHandleId(dt)
udg_DamageEventWeaponT = GetHandleId(wt)
calibrateMR() -- Set Melee and Ranged settings.
udg_DamageEventArmorT = BlzGetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_ARMOR_TYPE) -- Introduced in Damage Engine 5.2.0.0
udg_DamageEventDefenseT = BlzGetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_DEFENSE_TYPE)
armorType = udg_DamageEventArmorT
defenseType = udg_DamageEventDefenseT
udg_DamageEventArmorPierced = 0.00
udg_DamageScalingUser = 1.00
udg_DamageScalingWC3 = 1.00
if amt ~= 0.00 then
if not udg_DamageEventOverride then
runTrigs(event.mod)
-- All events have run and the pre-damage amount is finalized.
BlzSetEventAttackType(ConvertAttackType(udg_DamageEventAttackT))
BlzSetEventDamageType(ConvertDamageType(udg_DamageEventDamageT))
BlzSetEventWeaponType(ConvertWeaponType(udg_DamageEventWeaponT))
if udg_DamageEventArmorPierced ~= 0.00 then
BlzSetUnitArmor(udg_DamageEventTarget, BlzGetUnitArmor(udg_DamageEventTarget) - udg_DamageEventArmorPierced)
end
if armorType ~= udg_DamageEventArmorT then
BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_ARMOR_TYPE, udg_DamageEventArmorT) -- Introduced in Damage Engine 5.2.0.0
end
if defenseType ~= udg_DamageEventDefenseT then
BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_DEFENSE_TYPE, udg_DamageEventDefenseT) -- Introduced in Damage Engine 5.2.0.0
end
BlzSetEventDamage(udg_DamageEventAmount)
end
totem = true
-- print("Ready to deal " .. udg_DamageEventAmount)
else
runTrigs(event.zero)
canKick = true
finish()
end
return false
end))
local t2 = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DAMAGED)
TriggerAddCondition(t2, Filter(function()
if udg_DamageEventPrevAmt == 0.00 then
return false
end
--print "Second event running"
if totem then
totem = false --This should be the case in almost all circumstances
else
afterDamage() --Wrap up the outstanding damage instance
canKick = true
--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.
udg_DamageEventSource = GetEventDamageSource()
udg_DamageEventTarget = GetTriggerUnit()
udg_DamageEventAmount = prev.amount
udg_DamageEventPrevAmt = prev.preAmt
udg_DamageEventAttackT = GetHandleId(BlzGetEventAttackType())
udg_DamageEventDamageT = GetHandleId(BlzGetEventDamageType())
udg_DamageEventWeaponT = GetHandleId(BlzGetEventWeaponType())
udg_DamageEventType = prev.type
udg_IsDamageCode = prev.code
udg_DamageEventArmorT = prev.armor
udg_DamageEventDefenseT = prev.defense
udg_DamageEventArmorPierced= prev.pierce
armorType = prev.preArm
defenseType = prev.preDef
calibrateMR() --Apply melee/ranged settings once again.
end
resetArmor()
local r = GetEventDamage()
if udg_DamageEventAmount ~= 0.00 and r ~= 0.00 then
udg_DamageScalingWC3 = r/udg_DamageEventAmount
else
if udg_DamageEventAmount > 0.00 then
udg_DamageScalingWC3 = 0.00
else
udg_DamageScalingWC3 = 1.00
end
udg_DamageScalingUser = udg_DamageEventAmount/udg_DamageEventPrevAmt
end
udg_DamageEventAmount = udg_DamageEventAmount*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.
runTrigs(event.shield)
udg_LethalDamageHP = GetWidgetLife(udg_DamageEventTarget) - udg_DamageEventAmount
if udg_LethalDamageHP <= 0.405 then
runTrigs(event.lethal) -- 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).
udg_DamageEventAmount = GetWidgetLife(udg_DamageEventTarget) - udg_LethalDamageHP
if udg_DamageEventType < 0 and udg_LethalDamageHP <= 0.405 then
SetUnitExploded(udg_DamageEventTarget, true) --Explosive damage types should blow up the target.
end
end
udg_DamageScalingUser = udg_DamageEventAmount/udg_DamageEventPrevAmt/udg_DamageScalingWC3
end
BlzSetEventDamage(udg_DamageEventAmount) --Apply the final damage amount.
if udg_DamageEventDamageT ~= udg_DAMAGE_TYPE_UNKNOWN then
runTrigs(event.damage)
end
eventsRun = true
--print(canKick)
if udg_DamageEventAmount == 0.00 then
finish()
end
return false
end))
onGlobalInit(function()
local i
for i = 0, 6 do udg_CONVERTED_ATTACK_TYPE[i] = ConvertAttackType(i) end
for i = 0, 26 do udg_CONVERTED_DAMAGE_TYPE[i] = ConvertDamageType(i) end
udg_AttackTypeDebugStr[0] = "SPELLS" -- ATTACK_TYPE_NORMAL in JASS
udg_AttackTypeDebugStr[1] = "NORMAL" -- ATTACK_TYPE_MELEE in JASS
udg_AttackTypeDebugStr[2] = "PIERCE"
udg_AttackTypeDebugStr[3] = "SIEGE"
udg_AttackTypeDebugStr[4] = "MAGIC"
udg_AttackTypeDebugStr[5] = "CHAOS"
udg_AttackTypeDebugStr[6] = "HERO"
udg_DamageTypeDebugStr[0] = "UNKNOWN"
udg_DamageTypeDebugStr[4] = "NORMAL"
udg_DamageTypeDebugStr[5] = "ENHANCED"
udg_DamageTypeDebugStr[8] = "FIRE"
udg_DamageTypeDebugStr[9] = "COLD"
udg_DamageTypeDebugStr[10] = "LIGHTNING"
udg_DamageTypeDebugStr[11] = "POISON"
udg_DamageTypeDebugStr[12] = "DISEASE"
udg_DamageTypeDebugStr[13] = "DIVINE"
udg_DamageTypeDebugStr[14] = "MAGIC"
udg_DamageTypeDebugStr[15] = "SONIC"
udg_DamageTypeDebugStr[16] = "ACID"
udg_DamageTypeDebugStr[17] = "FORCE"
udg_DamageTypeDebugStr[18] = "DEATH"
udg_DamageTypeDebugStr[19] = "MIND"
udg_DamageTypeDebugStr[20] = "PLANT"
udg_DamageTypeDebugStr[21] = "DEFENSIVE"
udg_DamageTypeDebugStr[22] = "DEMOLITION"
udg_DamageTypeDebugStr[23] = "SLOW_POISON"
udg_DamageTypeDebugStr[24] = "SPIRIT_LINK"
udg_DamageTypeDebugStr[25] = "SHADOW_STRIKE"
udg_DamageTypeDebugStr[26] = "UNIVERSAL"
udg_WeaponTypeDebugStr[0] = "NONE" -- WEAPON_TYPE_WHOKNOWS in JASS
udg_WeaponTypeDebugStr[1] = "METAL_LIGHT_CHOP"
udg_WeaponTypeDebugStr[2] = "METAL_MEDIUM_CHOP"
udg_WeaponTypeDebugStr[3] = "METAL_HEAVY_CHOP"
udg_WeaponTypeDebugStr[4] = "METAL_LIGHT_SLICE"
udg_WeaponTypeDebugStr[5] = "METAL_MEDIUM_SLICE"
udg_WeaponTypeDebugStr[6] = "METAL_HEAVY_SLICE"
udg_WeaponTypeDebugStr[7] = "METAL_MEDIUM_BASH"
udg_WeaponTypeDebugStr[8] = "METAL_HEAVY_BASH"
udg_WeaponTypeDebugStr[9] = "METAL_MEDIUM_STAB"
udg_WeaponTypeDebugStr[10] = "METAL_HEAVY_STAB"
udg_WeaponTypeDebugStr[11] = "WOOD_LIGHT_SLICE"
udg_WeaponTypeDebugStr[12] = "WOOD_MEDIUM_SLICE"
udg_WeaponTypeDebugStr[13] = "WOOD_HEAVY_SLICE"
udg_WeaponTypeDebugStr[14] = "WOOD_LIGHT_BASH"
udg_WeaponTypeDebugStr[15] = "WOOD_MEDIUM_BASH"
udg_WeaponTypeDebugStr[16] = "WOOD_HEAVY_BASH"
udg_WeaponTypeDebugStr[17] = "WOOD_LIGHT_STAB"
udg_WeaponTypeDebugStr[18] = "WOOD_MEDIUM_STAB"
udg_WeaponTypeDebugStr[19] = "CLAW_LIGHT_SLICE"
udg_WeaponTypeDebugStr[20] = "CLAW_MEDIUM_SLICE"
udg_WeaponTypeDebugStr[21] = "CLAW_HEAVY_SLICE"
udg_WeaponTypeDebugStr[22] = "AXE_MEDIUM_CHOP"
udg_WeaponTypeDebugStr[23] = "ROCK_HEAVY_BASH"
udg_DefenseTypeDebugStr[0] = "LIGHT"
udg_DefenseTypeDebugStr[1] = "MEDIUM"
udg_DefenseTypeDebugStr[2] = "HEAVY"
udg_DefenseTypeDebugStr[3] = "FORTIFIED"
udg_DefenseTypeDebugStr[4] = "NORMAL"
udg_DefenseTypeDebugStr[5] = "HERO"
udg_DefenseTypeDebugStr[6] = "DIVINE"
udg_DefenseTypeDebugStr[7] = "UNARMORED"
udg_ArmorTypeDebugStr[0] = "NONE"
udg_ArmorTypeDebugStr[1] = "FLESH"
udg_ArmorTypeDebugStr[2] = "METAL"
udg_ArmorTypeDebugStr[3] = "WOOD"
udg_ArmorTypeDebugStr[4] = "ETHEREAL"
udg_ArmorTypeDebugStr[5] = "STONE"
end)
function DamageEngine_SetupEvent(whichTrig, var, val)
--print("Setup event: " .. var)
local mx = 1
local off = 0
local ex = 0
if var == "udg_DamageModifierEvent" then --event.mod 1-4 -> Events 1-4
if (val < 3) then
ex = val + 1
end
mx = 4
elseif var == "udg_DamageEvent" then --event.damage 1,2 -> Events 5,6
mx = 2
off = 4
elseif var == "udg_AfterDamageEvent" then --event.after -> Event 7
off = 6
elseif var == "udg_LethalDamageEvent" then --event.lethal -> Event 8
off = 7
elseif var == "udg_AOEDamageEvent" then --event.aoe -> Event 9
off = 8
else
return false
end
local i
if userTrigs == 9 then
nextTrig[1] = 2
nextTrig[2] = 3
trigFrozen[2] = true
trigFrozen[3] = true
for i = 3, 9 do nextTrig[i] = 0 end
end
i = math.max(math.min(val, mx), 1) + off
--print("Root index: " .. i .. " nextTrig: " .. nextTrig[i] .. " exit: " .. ex)
repeat
val = i
i = nextTrig[i]
until (i == ex)
userTrigs = userTrigs + 1 --User list runs from index 10 and up
nextTrig[val] = userTrigs
nextTrig[userTrigs] = ex
userTrig[userTrigs] = whichTrig
levelsDeep[userTrigs] = 0
trigFrozen[userTrigs] = false
inceptionTrig[userTrigs] = false
--print("Registered " .. userTrigs .. " to " .. val)
return true
end
onRegisterVar(function(trig, var, val)
DamageEngine_SetupEvent(trig, var, math.floor(val))
end)
end
Example triggers in the test map
-
Set Damage
-
Events
-
Game - DamageModifierEvent becomes Equal to 1.00
-
-
Conditions
-
Actions
-
Game - Display to (All players) the text: (String((Mana of DamageEventTarget)))
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
(Unit-type of DamageEventSource) Equal to Peasant
-
-
Then - Actions
-
Set DamageEventAmount = 0.00
-
Set DamageEventArmorT = ARMOR_TYPE_NONE
-
Set DamageEventWeaponT = WEAPON_TYPE_NONE
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
IsDamageSpell Equal to False
-
(Unit-type of DamageEventSource) Equal to Archimonde
-
DamageEventTarget Equal to EnhancedDamageTarget
-
-
Then - Actions
-
Set DamageEventAmount = (0.00 - DamageEventAmount)
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
(DamageEventTarget is A Hero) Equal to True
-
(DamageEventSource is Summoned) Equal to True
-
-
Then - Actions
-
Set DamageEventAmount = (0.00 - DamageEventAmount)
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
(Unit-type of DamageEventSource) Equal to Knight
-
(Random integer number between 1 and 10) Less than 6
-
IsDamageSpell Equal to False
-
-
Then - Actions
-
-------- --------
-
-------- Set DamageEventType to Critical Strike so we can show a critical strike text tag using the trigger "Critical Strike" --------
-
-------- --------
-
Set DamageEventType = DamageTypeCriticalStrike
-
Set DamageEventAmount = (DamageEventAmount x 4.00)
-
Set DamageEventArmorPierced = 50.00
-
-
Else - Actions
-
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
(DamageEventTarget is Summoned) Equal to True
-
-
Then - Actions
-
-------- --------
-
-------- Summoned units take 200% damage --------
-
-------- They also retaliate with the damage amount when attacked. --------
-
-------- --------
-
Set DamageEventTrigger = (This trigger)
-
Unit - Cause DamageEventTarget to damage DamageEventSource, dealing DamageEventAmount damage of attack type Spells and damage type Magic
-
Set DamageEventAmount = (DamageEventAmount x 2.00)
-
-
Else - Actions
-
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
IsDamageRanged Equal to True
-
-
Then - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
UnitMoving[(Custom value of DamageEventTarget)] Equal to True
-
-
Then - Actions
-
-------- --------
-
-------- Units that are moving take 50% damage from ranged attacks --------
-
-------- --------
-
Set DamageEventAmount = (DamageEventAmount x 0.50)
-
-
Else - Actions
-
Set DamageEventDefenseT = DEFENSE_TYPE_UNARMORED
-
Set DamageEventArmorPierced = (Armor of DamageEventTarget)
-
-
-
-
Else - Actions
-
-
-
-
-
-
-
-
-
-
Damage Tag
-
Events
-
Game - DamageEvent becomes Equal to 1.00
-
-
Conditions
-
Actions
-
Game - Display to (All players) the text: (String((Mana of DamageEventTarget)))
-
Set DmgStr = |cffffffff
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
DamageEventAmount Equal to 0.00
-
-
Then - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
(Unit-type of DamageEventSource) Equal to Peasant
-
-
Then - Actions
-
Set DamageEventTarget = DamageEventSource
-
Set DmgStr = |c00AAAAAAFAIL!|r
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
DamageScalingWC3 Equal to 0.00
-
-
Then - Actions
-
Set DmgStr = (|c00AAAAAABlocked + ((String((Integer(DamageEventPrevAmt)))) + !|r))
-
-
Else - Actions
-
Set DmgStr = (|c00AAAAAABlocked + ((String((Integer((DamageEventPrevAmt x DamageScalingWC3))))) + !|r))
-
-
-
-
-
Custom script: call ArcingTextTag.create(udg_DmgStr, udg_DamageEventTarget)
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
DamageEventAmount Less than -0.99
-
-
Then - Actions
-
Set DmgStr = |cff00ff00+
-
Custom script: call ArcingTextTag.create(udg_DmgStr + I2S(R2I(-udg_DamageEventAmount)) + "|r", udg_DamageEventTarget)
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
DamageEventAmount Greater than or equal to (DamageEventPrevAmt x 1.50)
-
-
Then - Actions
-
Set DmgStr = (|cffff0000 + ((String((Integer(DamageEventAmount)))) + !|r))
-
Custom script: call ArcingTextTag.create(udg_DmgStr, udg_DamageEventTarget)
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
DamageEventAmount Greater than 0.99
-
-
Then - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
DamageEventAmount Less than (DamageEventPrevAmt x 0.60)
-
-
Then - Actions
-
Set DmgStr = |cff808000
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
IsDamageSpell Equal to True
-
-
Then - Actions
-
Set DmgStr = |cff3264c8
-
-
Else - Actions
-
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
-
If - Conditions
-
IsDamageRanged Equal to True
-
-
Then - Actions
-
Set DmgStr = |cffffff00
-
-
Else - Actions
-
-
-
-
-
-
Custom script: call ArcingTextTag.create(udg_DmgStr + I2S(R2I(udg_DamageEventAmount)) + "|r", udg_DamageEventTarget)
-
-
Else - Actions
-
-
-
-
-
-
-
-
Set ReportLife = (Life of DamageEventTarget)
-
-
How to install or upgrade to Damage Engine 5:
- Use WarCraft 3 Version 1.31
- 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.
- 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.
How to Use
- Detect damage: use the event "DamageEvent Equal to 1.00". 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.
- 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 "DamageModifierEvent Becomes Equal to 1.00, 2.00 or 3.00". 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 "DamageModifierEvent Becomes Equal to 4.00". 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 "DamageModifierEvent" 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.
- DamageEventAmount
- 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.
Cross-Compatibility
For looking_for_help's system, you can delete the entire custom script code in the "DamageEvent" trigger and replace it with the following:
Code (vJASS):
//PDD for Damage Engine 5.1:
function GetUnitLife takes unit u returns real
return GetWidgetLife(u)
endfunction
function SetUnitLife takes unit u, real r returns nothing
call SetWidgetLife(u, r)
endfunction
function GetUnitMaxLife takes unit u returns real
return GetUnitState(u, UNIT_STATE_MAX_LIFE)
endfunction
//UnitDamageTargetEx is integrated directly into Damage Engine 5.1, so I removed this function.
function PDD_OnDamage takes nothing returns boolean
set udg_PDD_source = udg_DamageEventSource
set udg_PDD_target = udg_DamageEventTarget
set udg_PDD_amount = udg_DamageEventAmount
if udg_IsDamageSpell then
set udg_PDD_damageType = udg_PDD_SPELL
elseif udg_IsDamageCode then
set udg_PDD_damageType = udg_PDD_CODE
else
set udg_PDD_damageType = udg_PDD_PHYSICAL
endif
set udg_PDD_damageEventTrigger = 0.00
set udg_PDD_damageEventTrigger = 1.00
set udg_DamageEventAmount = udg_PDD_amount
return false
endfunction
function InitTrig_DamageEvent takes nothing returns nothing
set udg_PDD_PHYSICAL = 0
set udg_PDD_SPELL = 1
set udg_PDD_CODE = 2
set udg_PDD_damageEvent = CreateTrigger()
call TriggerRegisterVariableEvent(udg_PDD_damageEvent, "udg_DamageModifierEvent", EQUAL, 1.00)
call TriggerAddCondition(udg_PDD_damageEvent, Filter(function PDD_OnDamage))
endfunction
function GetUnitLife takes unit u returns real
return GetWidgetLife(u)
endfunction
function SetUnitLife takes unit u, real r returns nothing
call SetWidgetLife(u, r)
endfunction
function GetUnitMaxLife takes unit u returns real
return GetUnitState(u, UNIT_STATE_MAX_LIFE)
endfunction
//UnitDamageTargetEx is integrated directly into Damage Engine 5.1, so I removed this function.
function PDD_OnDamage takes nothing returns boolean
set udg_PDD_source = udg_DamageEventSource
set udg_PDD_target = udg_DamageEventTarget
set udg_PDD_amount = udg_DamageEventAmount
if udg_IsDamageSpell then
set udg_PDD_damageType = udg_PDD_SPELL
elseif udg_IsDamageCode then
set udg_PDD_damageType = udg_PDD_CODE
else
set udg_PDD_damageType = udg_PDD_PHYSICAL
endif
set udg_PDD_damageEventTrigger = 0.00
set udg_PDD_damageEventTrigger = 1.00
set udg_DamageEventAmount = udg_PDD_amount
return false
endfunction
function InitTrig_DamageEvent takes nothing returns nothing
set udg_PDD_PHYSICAL = 0
set udg_PDD_SPELL = 1
set udg_PDD_CODE = 2
set udg_PDD_damageEvent = CreateTrigger()
call TriggerRegisterVariableEvent(udg_PDD_damageEvent, "udg_DamageModifierEvent", EQUAL, 1.00)
call TriggerAddCondition(udg_PDD_damageEvent, Filter(function PDD_OnDamage))
endfunction
For Weep's system, you can delete the entire custom script code in the "GUI Friendly Damage Detection" trigger, and replace it with the following:
Code (vJASS):
//GDD Compatibility for Damage Engine 5.1
//===========================================================================
function GDD_Event takes nothing returns boolean
set udg_GDD_DamageSource = udg_DamageEventSource
set udg_GDD_DamagedUnit = udg_DamageEventTarget
set udg_GDD_Damage = udg_DamageEventAmount
set udg_GDD_Event = 1.00
set udg_GDD_Event = 0.00
return false //Code is much shorter due to not having to be concerned with recursion.
endfunction
//===========================================================================
function InitTrig_GUI_Friendly_Damage_Detection takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 1.00)
call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 2.00)
call TriggerAddCondition(t, Filter(function GDD_Event))
set t = null
endfunction
Change log v4.0 and up
For 3.8.0.0 and prior, see: Damage Engine 3.8.0.0 | HIVE
4.0.0.0 - Never officially released, but was the first iteration which used SetEventDamage,
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"
5.1.1.0 - Fixed issues related to Spirit Link. Now works intuitively as expected.
5.1.1.1 - Minor tweak to Spirit Link in rare situation.
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.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.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.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.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.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.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.4.0.0 - By using an innovative approach of hooking TriggerRegisterVariableEvent, I've permanently eliminated all risks of recursion in the engine.
5.4.0.1 - Hotfixed that modifiers 2 and 3 weren't running.
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.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.2.1 - A fix which should hopefully quell the recent reports of damage events failing in complex situations involving Cold Arrows.
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.3 - Fixed a mis-flag of the IsDamageSpell value when not being actual spell damage.
Lua 1.0.0.0 - Release based on Damage Engine 5.4.2.0
Lua 1.0.1.0 - Fixed encapsulation issue and recursion issue with DamageEngine_inception. Now hooks UnitDamageTarget.
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.2.1 - Fixed to match adjustment made in vJass version 5.4.2.1.
Lua 1.0.2.2 - Fixed to match adjustment made in vJass version 5.4.2.2.
Lua 1.0.2.3 - Fixed to match adjustment made in vJass version 5.4.2.3.
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
Contents
Page 44 of 59