1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. 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
  3. 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
  4. 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
  5. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  6. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  7. The results are out! Check them out.
    Dismiss Notice
  8. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  9. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  10. 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
This bundle is marked as approved. It works and satisfies the submission rules.
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.

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.
Notable features retained from earlier versions of Damage Engine:
  • 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.
Notes:
  • 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 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!

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.
        .
  • 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).
        .
  • 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:
  1. Use WarCraft 3 Version 1.31
  2. If you're upgrading from 3.8 or prior, please delete the entire "Damage Engine" category from your map.
  3. Copy & Paste the Damage Engine category from the attached map to your own map.
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.
In September 2015, cross-compatibility with looking_for_help's Physical Damage Detection was added to the ranks of Weep's compatibility script (which had been added in 2012). Users do not have to change any of their custom triggers to switch from either system to Damage Engine. Simply delete the custom script in their systems and replace it with the following scripts:

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

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

Damage Engine 5.4.2.3 (Map)

Lua Damage Engine 1.0.2.3 (Map)

Reviews
Moderator
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...
  1. 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 use.

    Overall very read-able and well documentated code.
    Also the demo map is good enough to help users to quickly learn the API provided by Damage Engine.

    I don't want to diminish the finished performance of established damage detection systems
    by other authors namely Nestharus ( DDS ), Cokemonkey ( StructuredDD ) and looking_for_help ( PDD ).
    Quite the opposite is the case, these sometimes can fit better to what a user expects from a damage system.

    Still I want to recommend Damage Engine in the first place for every user, looking for a damage detection system.

    After having a chat with IcemanBo and PurgeandFire we decided to give Damage Engine a moderator rating of 6/5


    Director's Cut
     
  2. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Bribe.. now I'm mad on you..
    WHY HAVEN"T YOU RELEASED IT EALIER?

    I would make use of it in my project for hero contest, since I already made use of IsUnitMoving and Unit Indexer (you would take credits for all the systems =)). GDD is fine too, it uses same feature: Event - Value of Real variable and was here for years. Some things get old, new ones must replace them, like your's table did with Vexorian's one.
    Great additional to unit indexer, allowing user to stick with one model of system and still gives efficient way to detect damage event.

    Outstanding work done for GUI section. Deserves dc considering other GUI friendly systems related by you.
     
  3. Magtheridon96

    Magtheridon96

    Joined:
    Dec 12, 2008
    Messages:
    6,006
    Resources:
    26
    Maps:
    1
    Spells:
    8
    Tutorials:
    7
    JASS:
    10
    Resources:
    26
    Thanks for teaching me that
    • Custom script: endfunction

    trick :p

    Nice System :)
    Now, GUIers have an 'Event' method :p
    A Unit Indexing System
    An IsUnitMoving System
    And a Damage Detection System ^^

    I wonder what's next..
    BoundSentinel?
    PowerupSentinel?
    xe? xD
     
  4. Jazztastic

    Jazztastic

    Joined:
    Apr 4, 2011
    Messages:
    895
    Resources:
    7
    Spells:
    6
    Tutorials:
    1
    Resources:
    7
    Golly wizz, everytime I think it just cant get any better, it does.

    Now I can finally make passive abilities based on attack.
     
  5. Adiktuz

    Adiktuz

    Joined:
    Oct 16, 2008
    Messages:
    9,674
    Resources:
    23
    Models:
    2
    Packs:
    1
    Maps:
    1
    Spells:
    16
    Tutorials:
    1
    JASS:
    2
    Resources:
    23
    Weep's DDS also allows that... ^_^

    anyway, this system is so cool!!! and its short!!! now it's a lot easier for GUIers to use a DDS...
     
  6. RiotApe

    RiotApe

    Joined:
    Jul 17, 2011
    Messages:
    51
    Resources:
    0
    Resources:
    0
    sorry for sounding like a noob, but what are damage detection systems for? why can't we use the event function, "Unit - Takes Damage"?
     
  7. Adiktuz

    Adiktuz

    Joined:
    Oct 16, 2008
    Messages:
    9,674
    Resources:
    23
    Models:
    2
    Packs:
    1
    Maps:
    1
    Spells:
    16
    Tutorials:
    1
    JASS:
    2
    Resources:
    23
    Why? because Unit - Takes Damage is a SPECIFIC UNIT EVENT, meaning you will need to register all possible units on the map... And it can cause leaks once the units die since the event stays...

    That is where DDS comes in, they automate the registration for you, so that you can have a generic damage event (in this system for example, your generic damage event will be Game - DamageEvent becomes Equal to 1.00)... And they also avoid the leaks via several methods... (like in this case, once 15 units are removed from the detection list, they recreate the trigger to clear the events that are not needed anymore)
     
  8. RiotApe

    RiotApe

    Joined:
    Jul 17, 2011
    Messages:
    51
    Resources:
    0
    Resources:
    0
    oh finally, that made perfect sense, thanks dude! though, i was wondering if there's a system that can detect if the damage was dealt by an attack, or if it was dealt by a spell?

    anyways, cool system, i think i'll use it :D

    10/10
     
  9. Adiktuz

    Adiktuz

    Joined:
    Oct 16, 2008
    Messages:
    9,674
    Resources:
    23
    Models:
    2
    Packs:
    1
    Maps:
    1
    Spells:
    16
    Tutorials:
    1
    JASS:
    2
    Resources:
    23
    Nestharus' system can do that, though you need vJASS knowledge...
     
  10. Magtheridon96

    Magtheridon96

    Joined:
    Dec 12, 2008
    Messages:
    6,006
    Resources:
    26
    Maps:
    1
    Spells:
    8
    Tutorials:
    7
    JASS:
    10
    Resources:
    26
    Maybe you could a ZeroDamage Event (Just for the heck of it)

    I know GUIers could easily check if the amount was 0, but that would cause a huge amount of trigger executions and if/else evaluations for nothing :p
     
  11. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,037
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    A Zero damage event would probably be better suited from Damage Modifier because that system has a much higher chance of blocking. How often is a 0 damage event going to happen naturally in a game? I can only think if something is attacking divine armor?
     
  12. Magtheridon96

    Magtheridon96

    Joined:
    Dec 12, 2008
    Messages:
    6,006
    Resources:
    26
    Maps:
    1
    Spells:
    8
    Tutorials:
    7
    JASS:
    10
    Resources:
    26
    Possibly from Faerie Fire too :p
     
  13. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,037
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    I'll have to check that out. By my preliminary checks I've seen that both Faerie Fire and Critical Strike could cause this event, so if these are still true in the latest patch then I should certainly seperate these out.

    That's really weird and counter-intuitive that Faerie Fire and/or Critical Strike procure a 0 damage event.
     
  14. EloTheMan

    EloTheMan

    Joined:
    Mar 18, 2009
    Messages:
    468
    Resources:
    0
    Resources:
    0
    GUI was really lacking a Damage functions, but there is one thing I was wondering about this trigger.

    How does "Game - UnitIndexEvent becomes Equal to 1.00" work as a damage event?
     
  15. Adiktuz

    Adiktuz

    Joined:
    Oct 16, 2008
    Messages:
    9,674
    Resources:
    23
    Models:
    2
    Packs:
    1
    Maps:
    1
    Spells:
    16
    Tutorials:
    1
    JASS:
    2
    Resources:
    23
    First and Foremost, the event which works as a Damage detection is

    "Game - UnitDamageEvent becomes Equal to 1.00" (which you should already know if you read the HOW TO USE part on the first post...)

    the function "Game - UnitIndexEvent becomes Equal to 1.00" is used for the set-up and maintenance of this system...

    First the system runs when a unit is indexed and deindexed by the UnitIndexer (which sets the UnitIndexEvent to 1 or 2)

    Now the system checks if a damage event trigger is present, if not it creates one and registers some functions to it...

    Next it will check if the unit is already registered within that trigger and if not, it registers the Unit gets damage event for that unit into the trigger (making that trigger run when the unit gets damaged)...

    Now the function linked to this Damage event trigger is the one which causes the variable UnitDamageEvent to be equal to 1.00 and then sets the variables thus enabling you to catch the damage event by using the Event,

    "Game - UnitDamageEvent becomes Equal to 1.00"

    This system also recreates the damage event trigger every 15 deindexed units to remove already useless damage events (since there is no function for deregistering events)...
     
  16. EloTheMan

    EloTheMan

    Joined:
    Mar 18, 2009
    Messages:
    468
    Resources:
    0
    Resources:
    0
    Reading triggers shouldn't be done at 2 in the morning lol, well things got a lot simpler now.

    Thanks :D
     
  17. Adiktuz

    Adiktuz

    Joined:
    Oct 16, 2008
    Messages:
    9,674
    Resources:
    23
    Models:
    2
    Packs:
    1
    Maps:
    1
    Spells:
    16
    Tutorials:
    1
    JASS:
    2
    Resources:
    23
    ooh... Didn't know, were from diff time zones so it showed as 8:11am on my comp... ^_^
     
  18. I don't understand these damage systems. Or the indexing.

    Can't you just take the "when a unit is attacked" and then use floating text to display the damage? Or is this about controlling the damage?

    Explanation?
     
  19. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,037
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    "Unit is attacked" does not include "unit is attacked by spell/by triggers". You also can't get the damage from that event.

    "Unit is attacked" is also an abusable event because it doesn't fire the instant the projectile leaves the unit or when the sword strikes its target, it fires the instant the unit's animation starts playing, so it is unreliable and abusable by spamming the "stop" button.

    "Unit is damaged" fires when the sword connects with its target or when the missile hits its mark. It is the only way to get the amount of damage.

    This system is necessary because there is no "any unit takes damage" event.