1. 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
  2. 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 haven't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. Fill your cup and take your pick among the maps best suited for this year's Hive Cup. The 6th Melee Mapping Contest Poll is up!
    Dismiss Notice
  4. Shoot to thrill, play to kill. Sate your hunger with the 33rd Modeling Contest!
    Dismiss Notice
  5. Do you hear boss music? It's the 17th Mini Mapping Contest!
    Dismiss Notice
  6. 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 3A.0.0.0 and 3.8.0.0

  • Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,327
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Click here to go back to the current version of Damage Engine

    Special Thanks:

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

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

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

    Thanks to @Spellbound for several crucial bug reports.


    Code (vJASS):

    //===========================================================================
    // Damage Engine 3A.0.0.0 - a new type of Damage Engine for users who don't
    // have access to the latest version of WarCraft 3, which incorporates new
    // features to inherited from Damage Engine 5.7 by hooking TriggerRegisterVariableEvent.
    // However, it requires having JassHelper installed.
    //
    // Stuff that doesn't work:
    // - Pre-armor modification
    // - Damage/Attack/Weapotype detection/modification
    // - Armor/defense detection/modification
    // - Melee/ranged detection
    // - Filters for u.
    // - Spirit link won't interact with custom damage.
    // - Still needs workarounds for Anti-Magic Shell/Mana Shield/Life Drain/etc.
    //
    // Stuff that is changed from how it worked with 3.8:
    // - Recursive damage now uses the Damage Engine 5 anti-recursion method. So
    //   all recursive damage will be postponed until the sequence has completed.
    // - No more need for using ClearDamageEvent
    // - No more need to disable the DamageEventTrigger in order to avoid things
    //   going recursively.
    //
    library DamageEngine
    globals
        private timer           alarm           = CreateTimer()
        private boolean         alarmSet        = false
        //Values to track the original pre-spirit Link/defensive damage values
        private Damage          lastInstance    = 0
        private boolean         canKick         = false
        //These variables coincide with Blizzard's "limitop" type definitions so as to enable users (GUI in particular) with some nice performance perks.
        public constant integer FILTER_ATTACK   = 0     //LESS_THAN
        public constant integer FILTER_OTHER    = 2     //EQUAL
        public constant integer FILTER_SPELL    = 4     //GREATER_THAN
        public constant integer FILTER_CODE     = 5     //NOT_EQUAL
        public constant integer FILTER_MAX      = 6
        private integer         eventFilter     = FILTER_OTHER
        private constant integer LIMBO          = 16        //When manually-enabled recursion is enabled via DamageEngine_recurion, the engine will never go deeper than LIMBO.
        public boolean          inception       = false     //When true, it allows your trigger to potentially go recursive up to LIMBO. However it must be set per-trigger throughout the game and not only once per trigger during map initialization.
        private boolean         dreaming        = false
        private integer         sleepLevel      = 0
        private group           proclusGlobal   = CreateGroup() //track sources of recursion
        private group           fischerMorrow   = CreateGroup() //track targets of recursion
        private boolean         kicking         = false
        private boolean         eventsRun       = false
        private unit            protectUnit     = null
        private real            protectLife     = 0.00
        private boolean         blocked         = false
        private keyword         run
        private keyword         trigFrozen
        private keyword         levelsDeep
        private keyword         inceptionTrig
        private keyword         checkLife
        private keyword         lifeTrigger
    endglobals
    private function CheckAddUnitToEngine takes unit u returns boolean
        if GetUnitAbilityLevel(u, 'Aloc') > 0 then
        elseif not TriggerEvaluate(gg_trg_Damage_Engine_Config) then
        //Add some more elseifs to rule out stuff you don't want to get registered, such as:
        //elseif IsUnitType(u, UNIT_TYPE_STRUCTURE) then
        else
            return true
        endif
        return false
    endfunction
    struct DamageTrigger extends array
       
        //The below variables are constant
        readonly static thistype        MOD             = 1
        readonly static thistype        DAMAGE          = 5
        readonly static thistype        ZERO            = 6
        readonly static thistype        AFTER           = 7
        readonly static thistype        AOE             = 9
        private static integer          count           = 9
        static thistype                 lastRegistered  = 0
        private static thistype array   trigIndexStack
        static thistype                 eventIndex = 0
        static boolean array            filters
        readonly string                 eventStr
        readonly real                   weight
        readonly boolean                configured
        boolean                         usingGUI
        //The below variables are private
        private thistype                next
        private trigger                 rootTrig
        boolean                         trigFrozen      //Whether the trigger is currently disabled due to recursion
        integer                         levelsDeep      //How deep the user recursion currently is.
        boolean                         inceptionTrig   //Added in 5.4.2 to simplify the inception variable for very complex DamageEvent trigger.
       
        static method operator enabled= takes boolean b returns nothing
            if b then
                call EnableTrigger(udg_DamageEventTrigger)
            else
                call DisableTrigger(udg_DamageEventTrigger)
            endif
        endmethod
        static method operator enabled takes nothing returns boolean
            return IsTriggerEnabled(udg_DamageEventTrigger)
        endmethod
       
        static method setGUIFromStruct takes boolean full returns nothing
            set udg_DamageEventAmount       = Damage.index.damage
            set udg_DamageEventType         = Damage.index.userType
            set udg_DamageEventOverride     = Damage.index.override
            if full then
                set udg_DamageEventSource   = Damage.index.sourceUnit
                set udg_DamageEventTarget   = Damage.index.targetUnit
                set udg_DamageEventPrevAmt  = Damage.index.prevAmt
                set udg_IsDamageSpell       = Damage.index.isSpell
                //! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_GDD()
                //! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_PDD()
                //! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_01()
                //! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_02()
                //! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_03()
                //! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_04()
                //! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_05()
            endif
        endmethod
        static method getVerboseStr takes string eventName returns string
            if eventName == "Modifier" or eventName == "Mod" then
                return "udg_DamageModifierEvent"
            endif
            return "udg_" + eventName + "DamageEvent"
        endmethod
        private static method getStrIndex takes string var, real lbs returns thistype
            local integer root = R2I(lbs)
            if var == "udg_DamageModifierEvent" then
                set root= MOD
            elseif var == "udg_DamageEvent" then
                if root == 2 or root == 0 then
                    set root= ZERO
                else
                    set root= DAMAGE //Above 0.00 but less than 2.00, generally would just be 1.00
                endif
            elseif var == "udg_AfterDamageEvent" then
                set root    = AFTER
            elseif var == "udg_AOEDamageEvent" then
                set root    = AOE
            else
                set root    = 0
                //! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_GDD()
                //! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_PDD()
                //! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_01()
                //! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_02()
                //! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_03()
                //! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_04()
                //! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_05()
            endif
            return root
        endmethod
        private method toggleAllFilters takes boolean flag returns nothing
            set filters[this + FILTER_ATTACK]   = flag
            set filters[this + FILTER_OTHER]    = flag
            set filters[this + FILTER_SPELL]    = flag
            set filters[this + FILTER_CODE]     = flag
        endmethod
        method operator filter= takes integer f returns nothing
            set this = this*FILTER_MAX
            if f == FILTER_OTHER then
                call this.toggleAllFilters(true)
            else
                if f == FILTER_ATTACK then
                    set filters[this + FILTER_ATTACK]   = true
                else
                    set filters[this + f] = true
                endif
            endif
        endmethod
        static method registerVerbose takes trigger whichTrig, string var, real lbs, boolean GUI, integer filt returns thistype
            local thistype index= getStrIndex(var, lbs)
            local thistype i    = 0
            local thistype id   = 0
         
            if index == 0 then
                return 0
            elseif lastRegistered.rootTrig == whichTrig and lastRegistered.usingGUI then
                set filters[lastRegistered*FILTER_MAX + filt] = true //allows GUI to register multiple different types of Damage filters to the same trigger
                return 0
            endif
         
            if trigIndexStack[0] == 0 then
                set count              = count + 1   //List runs from index 10 and up
                set id                 = count
            else
                set id                 = trigIndexStack[0]
                set trigIndexStack[0]  = trigIndexStack[id]
            endif
            set lastRegistered         = id
            set id.filter              = filt
            set id.rootTrig            = whichTrig
            set id.usingGUI            = GUI
            set id.weight              = lbs
            set id.eventStr            = var
         
            loop
                set i = index.next
                exitwhen i == 0 or lbs < i.weight
                set index = i
            endloop        
            set index.next = id
            set id.next    = i
         
            //call BJDebugMsg("Registered " + I2S(id) + " to " + I2S(index) + " and before " + I2S(i))
            return lastRegistered
        endmethod
        static method registerTrigger takes trigger t, string var, real lbs returns thistype
            return registerVerbose(t, DamageTrigger.getVerboseStr(var), lbs, false, FILTER_OTHER)
        endmethod
        private static thistype prev = 0
        static method getIndex takes trigger t, string eventName, real lbs returns thistype
            local thistype index = getStrIndex(getVerboseStr(eventName), lbs)
            loop
                set prev = index
                set index = index.next
                exitwhen index == 0 or index.rootTrig == t
            endloop
            return index
        endmethod
        static method unregister takes trigger t, string eventName, real lbs, boolean reset returns boolean
            local thistype index        = getIndex(t, eventName, lbs)
            if index == 0 then
                return false
            endif
            set prev.next               = index.next
             
            set trigIndexStack[index]   = trigIndexStack[0]
            set trigIndexStack[0]       = index
         
            if reset then
                set index.configured    = false
                set index               = index*FILTER_MAX
                call index.toggleAllFilters(false)
            endif
            return true
        endmethod
        static method damageUnit takes unit u, real life returns nothing
            call SetWidgetLife(u, RMaxBJ(life, 0.41))
            if life <= 0.405 then
                if udg_DamageEventType < 0 then
                    call SetUnitExploded(u, true)
                endif
                //Kill the unit
                set DamageTrigger.enabled = false
                call UnitDamageTarget(udg_DamageEventSource, u, -999, false, false, null, DAMAGE_TYPE_UNIVERSAL, null)
                set DamageTrigger.enabled = true
            endif
        endmethod
        static method checkLife takes nothing returns boolean
            if protectUnit != null then
                if Damage.lifeTrigger != null then
                    call DestroyTrigger(Damage.lifeTrigger)
                    set Damage.lifeTrigger = null
                endif
                if GetUnitAbilityLevel(protectUnit, udg_DamageBlockingAbility) > 0 then
                    call UnitRemoveAbility(protectUnit, udg_DamageBlockingAbility)
                    call SetWidgetLife(protectUnit, protectLife)
                elseif udg_IsDamageSpell or blocked then
                    call DamageTrigger.damageUnit(protectUnit, protectLife)
                endif
                if blocked then
                    set blocked = false
                endif
                set protectUnit = null
                return true
            endif
            return false
        endmethod
        method run takes nothing returns nothing
            local integer cat = this
            local Damage d = Damage.index
            if cat == MOD or not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
                set dreaming = true
                //call BJDebugMsg("Start of event running")
                loop                                  
                    set this = this.next
                    exitwhen this == 0
                    if cat == MOD then
                        exitwhen d.override or udg_DamageEventOverride
                        exitwhen this.weight >= 4.00 and udg_DamageEventAmount <= 0.00
                    endif
                    set eventIndex = this
                    if not this.trigFrozen and filters[this*FILTER_MAX + eventFilter] and IsTriggerEnabled(this.rootTrig) then
                        //! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_PDD()
                        //! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_01()
                        //! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_02()
                        //! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_03()
                        //! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_04()
                        //! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_05()
                     
                        if TriggerEvaluate(this.rootTrig) then
                            call TriggerExecute(this.rootTrig)
                        endif
                        if cat == MOD then
                            //! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_PDD()
                            //! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_01()
                            //! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_02()
                            //! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_03()
                            //! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_04()
                            //! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_05()
                            if this.usingGUI then
                                set d.damage        = udg_DamageEventAmount
                                set d.userType      = udg_DamageEventType
                                set d.override      = udg_DamageEventOverride
                            elseif this.next == 0 or this.next.usingGUI then //Might offer a slight performance improvement
                                call setGUIFromStruct(false)
                            endif
                        endif
                        call checkLife()
                    endif
                endloop
                //call BJDebugMsg("End of event running")
                set dreaming                                = false
            endif
        endmethod
       
        static method finish takes nothing returns nothing
            if checkLife() and not blocked and udg_DamageEventAmount != 0.00 then
                call DamageTrigger.AFTER.run()
            endif
        endmethod
       
        static trigger array    autoTriggers
        static boolexpr array   autoFuncs
        static integer          autoN = 0
        static method operator [] takes code c returns trigger
            local integer i             = 0
            local boolexpr b            = Filter(c)
            loop
                if i == autoN then
                    set autoTriggers[i] = CreateTrigger()
                    set autoFuncs[i]    = b
                    call TriggerAddCondition(autoTriggers[i], b)
                    exitwhen true
                endif
                set i = i + 1
                exitwhen b == autoFuncs[i]
            endloop
            return autoTriggers[i]
        endmethod
    endstruct
    struct Damage extends array
        readonly unit           sourceUnit    //stores udg_DamageEventSource
        readonly unit           targetUnit    //stores udg_DamageEventTarget
        real                    damage        //stores udg_DamageEventAmount
        readonly real           prevAmt       //stores udg_DamageEventPrevAmt
        integer                 userType      //stores udg_DamageEventType
        readonly boolean        isCode
        readonly boolean        isSpell       //stores udg_IsDamageSpell
        boolean                 override
        readonly static unit    aoeSource   = null
        readonly static Damage  index       = 0
        private static Damage   damageStack = 0
        private static integer  count = 0 //The number of currently-running queued or sequential damage instances
        private Damage          stackRef
        private DamageTrigger   recursiveTrig
       
        static trigger lifeTrigger = null   //private
        static method operator source takes nothing returns unit
            return udg_DamageEventSource
        endmethod
        static method operator target takes nothing returns unit
            return udg_DamageEventTarget
        endmethod
        static method operator amount takes nothing returns real
            return Damage.index.damage
        endmethod
        static method operator amount= takes real r returns nothing
            set Damage.index.damage = r
        endmethod
         
        private static method onAOEEnd takes nothing returns nothing
            if udg_DamageEventAOE > 1 then
                call DamageTrigger.AOE.run()
            endif
            set udg_DamageEventAOE = 0
            set udg_DamageEventLevel = 0
            set udg_EnhancedDamageTarget = null
            set aoeSource = null
            call GroupClear(udg_DamageEventAOEGroup)
        endmethod
        static method finish takes nothing returns nothing
            local Damage i                                  = 0
            local integer exit                              
            if canKick then
                set canKick = false
                set kicking = true
                call DamageTrigger.finish()
                if damageStack != 0 then
                    loop
                        set exit                            = damageStack
                        set sleepLevel                      = sleepLevel + 1
                        loop
                            set eventFilter                 = FILTER_CODE
                            set Damage.index                = i.stackRef
                            call DamageTrigger.setGUIFromStruct(true)
                            call DamageTrigger.MOD.run()
                            call DamageTrigger.DAMAGE.run()
                            if udg_DamageEventAmount != 0.00 then
                                call DamageTrigger.damageUnit(udg_DamageEventTarget, GetWidgetLife(udg_DamageEventTarget) - udg_DamageEventAmount)
                                call DamageTrigger.AFTER.run()
                            endif
                            set i                           = i + 1
                            exitwhen i == exit
                        endloop
                        exitwhen i == damageStack
                    endloop
                    loop
                        set i                               = i - 1
                        set i.stackRef.recursiveTrig.trigFrozen  = false
                        set i.stackRef.recursiveTrig.levelsDeep  = 0
                        exitwhen i == 0                    
                    endloop                                
                    set damageStack                         = 0
                endif
                set dreaming                                = false
                set sleepLevel                              = 0
                call GroupClear(proclusGlobal)
                call GroupClear(fischerMorrow)
                set kicking = false
                //call BJDebugMsg("Cleared up the groups")
            endif
        endmethod
       
        private static method wakeUp takes nothing returns nothing
            set alarmSet = false
            set dreaming = false
            set DamageTrigger.enabled = true
           
            call finish()
            call onAOEEnd()
           
            set Damage.count    = 0
            set Damage.index    = 0
            set udg_DamageEventTarget = null
            set udg_DamageEventSource = null
        endmethod
        private static method createLifeTrigger takes unit u, limitop op, real amount returns nothing
            if not blocked then
                set lifeTrigger = CreateTrigger()
                call TriggerAddCondition(lifeTrigger, Filter(function DamageTrigger.finish))
                call TriggerRegisterUnitStateEvent(lifeTrigger, u, UNIT_STATE_LIFE, op, amount)
            endif
            set protectUnit = u
        endmethod
        private method mitigate takes real newAmount, boolean recursive returns nothing
            local real prevLife
            local real life
            local unit u = targetUnit
            local real prevAmount = prevAmt
            set life = GetWidgetLife(u)
            if not isSpell then
                if newAmount != prevAmount then
                    set life = life + prevAmount - newAmount
                    if GetUnitState(u, UNIT_STATE_MAX_LIFE) < life then
                        set protectLife = life - prevAmount
                        call UnitAddAbility(u, udg_DamageBlockingAbility)
                    endif
                    call SetWidgetLife(u, RMaxBJ(life, 0.42))
                endif
                call createLifeTrigger(u, LESS_THAN, RMaxBJ(0.41, life - prevAmount/2.00))
            else
                set protectLife = GetUnitState(u, UNIT_STATE_MAX_LIFE)
                set prevLife = life
                if life + prevAmount*0.75 > protectLife then
                    set life = RMaxBJ(protectLife - prevAmount/2.00, 1.00)
                    call SetWidgetLife(u, life)
                    set life = (life + protectLife)/2.00
                else
                    set life = life + prevAmount*0.50
                endif
                set protectLife = prevLife - (prevAmount - (prevAmount - newAmount))
                call createLifeTrigger(u, GREATER_THAN, life)
            endif
            set u = null
        endmethod
        private method getSpellAmount takes real amt returns real
            local integer i = 6
            local real mult = 1.00
            set isSpell = amt < 0.00
            if isSpell then
                set amt = -amt
                if IsUnitType(target, UNIT_TYPE_ETHEREAL) and not IsUnitType(target, UNIT_TYPE_HERO) then
                    set mult = mult*udg_DAMAGE_FACTOR_ETHEREAL //1.67
                endif
                if GetUnitAbilityLevel(target, 'Aegr') > 0 then
                    set mult = mult*udg_DAMAGE_FACTOR_ELUNES //0.80
                endif
                if udg_DmgEvBracers != 0 and IsUnitType(target, UNIT_TYPE_HERO) then
                    //Inline of UnitHasItemOfTypeBJ without the potential handle ID leak.
                    loop
                        set i = i - 1
                        if GetItemTypeId(UnitItemInSlot(target, i)) == udg_DmgEvBracers then
                            set mult = mult*udg_DAMAGE_FACTOR_BRACERS //0.67
                            exitwhen true
                        endif
                        exitwhen i == 0
                    endloop
                endif
                return amt*mult
            endif
            return amt
        endmethod
        private method addRecursive takes nothing returns boolean
            if this.damage != 0.00 then
                set this.recursiveTrig = DamageTrigger.eventIndex
                if not this.isCode then
                    set this.isCode = true
                endif
                set inception = inception or DamageTrigger.eventIndex.inceptionTrig
                if kicking and IsUnitInGroup(this.sourceUnit, proclusGlobal) and IsUnitInGroup(this.targetUnit, fischerMorrow) then
                    if inception and not DamageTrigger.eventIndex.trigFrozen then
                        set DamageTrigger.eventIndex.inceptionTrig = true
                        if DamageTrigger.eventIndex.levelsDeep < sleepLevel then
                            set DamageTrigger.eventIndex.levelsDeep = DamageTrigger.eventIndex.levelsDeep + 1
                            if DamageTrigger.eventIndex.levelsDeep >= LIMBO then
                                set DamageTrigger.eventIndex.trigFrozen = true
                            endif
                        endif
                    else
                        set DamageTrigger.eventIndex.trigFrozen = true
                    endif
                endif
                set damageStack.stackRef = this
                set damageStack = damageStack + 1
                //call BJDebugMsg("damageStack: " + I2S(damageStack) + " levelsDeep: " + I2S(DamageTrigger.eventIndex.levelsDeep) + " sleepLevel: " + I2S(sleepLevel))
                return true
            endif
            set inception = false
            return false
        endmethod
        private static method onDamageResponse takes nothing returns boolean
            local Damage d      = Damage.count + 1
            set Damage.count    = d
            set d.sourceUnit    = GetEventDamageSource()
            set d.targetUnit    = GetTriggerUnit()
            set d.damage        = d.getSpellAmount(GetEventDamage())
            set d.prevAmt       = d.damage
            set d.userType      = udg_NextDamageType
            set d.isCode        = udg_NextDamageType != 0 or udg_NextDamageOverride or dreaming
            set d.override      = udg_NextDamageOverride
           
            set udg_NextDamageOverride      = false
            set udg_NextDamageType          = 0
           
            call finish() //in case the unit state event failed and the 0.00 second timer hasn't yet expired
            if dreaming then
                if d.addRecursive() then
                    set blocked = true
                    call d.mitigate(0.00, true)
                else
                    set Damage.count = d - 1
                endif
                return false
            endif
           
            //Added 25 July 2017 to detect AOE damage or multiple single-target damage
            if alarmSet then
                if d.sourceUnit != aoeSource then
                    call onAOEEnd()
                    set aoeSource           = d.sourceUnit
                elseif d.targetUnit == udg_EnhancedDamageTarget then
                    set udg_DamageEventLevel= udg_DamageEventLevel + 1
                elseif not IsUnitInGroup(d.targetUnit, udg_DamageEventAOEGroup) then
                    set udg_DamageEventAOE  = udg_DamageEventAOE + 1
                endif
            else
                call TimerStart(alarm, 0.00, false, function Damage.wakeUp)
                set alarmSet                = true
                set aoeSource               = d.sourceUnit
                set udg_EnhancedDamageTarget= d.targetUnit
            endif
           
            set Damage.index = d
            call DamageTrigger.setGUIFromStruct(true)
            call GroupAddUnit(udg_DamageEventAOEGroup, udg_DamageEventTarget)
            call GroupAddUnit(proclusGlobal, udg_DamageEventSource)
            call GroupAddUnit(fischerMorrow, udg_DamageEventTarget)
           
            if udg_DamageEventAmount == 0.00 then
                call DamageTrigger.ZERO.run()
                set canKick = true
                call finish()
            else
                if d.isCode then
                    set eventFilter = FILTER_CODE
                elseif udg_IsDamageSpell then
                    set eventFilter = FILTER_SPELL
                else
                    set eventFilter = FILTER_ATTACK
                endif
                call DamageTrigger.MOD.run()
                call DamageTrigger.DAMAGE.run()
         
                //The damage amount is finalized.
                call d.mitigate(udg_DamageEventAmount, false)
                set canKick = true
            endif
            return false
        endmethod
        static method createDamageTrigger takes nothing returns nothing //private
            set udg_DamageEventTrigger = CreateTrigger()
            call TriggerAddCondition(udg_DamageEventTrigger, Filter(function thistype.onDamageResponse))
        endmethod
        static method setup takes nothing returns boolean //private
            local integer i = udg_UDex
            local unit u
            if udg_UnitIndexEvent == 1.00 then
                set u = udg_UDexUnits[i]
                if CheckAddUnitToEngine(u) then
                    set udg_UnitDamageRegistered[i] = true
                    call TriggerRegisterUnitEvent(udg_DamageEventTrigger, u, EVENT_UNIT_DAMAGED)
                    call UnitAddAbility(u, udg_SpellDamageAbility)
                    call UnitMakeAbilityPermanent(u, true, udg_SpellDamageAbility)
                endif
                set u = null
            else
                set udg_HideDamageFrom[i] = false
                if udg_UnitDamageRegistered[i] then
                    set udg_UnitDamageRegistered[i] = false
                    set udg_DamageEventsWasted = udg_DamageEventsWasted + 1
                    if udg_DamageEventsWasted == 32 then //After 32 registered units have been removed...
                        set udg_DamageEventsWasted = 0
               
                        //Rebuild the mass EVENT_UNIT_DAMAGED trigger:
                        call DestroyTrigger(udg_DamageEventTrigger)
                        call createDamageTrigger()
                        set i = udg_UDexNext[0]
                        loop
                            exitwhen i == 0
                            if udg_UnitDamageRegistered[i] then
                                call TriggerRegisterUnitEvent(udg_DamageEventTrigger, udg_UDexUnits[i], EVENT_UNIT_DAMAGED)
                            endif
                            set i = udg_UDexNext[i]
                        endloop
                    endif
                endif
            endif
            return false
        endmethod
       
        //! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_DMGPKG()
        //! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_01()
        //! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_02()
        //! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_03()
        //! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_04()
        //! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_05()
       
    endstruct
    public function RegisterFromHook takes trigger whichTrig, string var, limitop op, real value returns nothing
        call DamageTrigger.registerVerbose(whichTrig, var, value, true, GetHandleId(op))
    endfunction
    hook TriggerRegisterVariableEvent RegisterFromHook
    function TriggerRegisterDamageEngineEx takes trigger whichTrig, string eventName, real value, integer f returns DamageTrigger
        return DamageTrigger.registerVerbose(whichTrig, DamageTrigger.getVerboseStr(eventName), value, false, f)
    endfunction
    function TriggerRegisterDamageEngine takes trigger whichTrig, string eventName, real value returns DamageTrigger
        return DamageTrigger.registerTrigger(whichTrig, eventName, value)
    endfunction
    function RegisterDamageEngineEx takes code c, string eventName, real value, integer f returns DamageTrigger
        return TriggerRegisterDamageEngineEx(DamageTrigger[c], eventName, value, f)
    endfunction
    //Similar to TriggerRegisterDamageEvent, although takes code instead of trigger as the first argument.
    function RegisterDamageEngine takes code c, string eventName, real value returns DamageTrigger
        return RegisterDamageEngineEx(c, eventName, value, FILTER_OTHER)
    endfunction
    endlibrary
    function InitTrig_Damage_Engine takes nothing returns nothing
        local unit u = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), 'uloc', 0, 0, 0)
        local integer i = bj_MAX_PLAYERS //Fixed in 3.8
       
        //Create this trigger with UnitIndexEvents in order to add and remove units
        //as they are created or removed.
        local trigger t = CreateTrigger()
        call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
        call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 2.00)
        call TriggerAddCondition(t, Filter(function Damage.setup))
        set t = null
       
        //Run the configuration actions to set all configurables:
        call ExecuteFunc("Trig_Damage_Engine_Config_Actions")
       
        //Create trigger for storing all EVENT_UNIT_DAMAGED events.
        call Damage.createDamageTrigger()
     
        //Disable SpellDamageAbility for every player.
        loop
            set i = i - 1
            call SetPlayerAbilityAvailable(Player(i), udg_SpellDamageAbility, false)
            exitwhen i == 0
        endloop
     
        //Preload abilities.
        call UnitAddAbility(u, udg_DamageBlockingAbility)
        call UnitAddAbility(u, udg_SpellDamageAbility)
        call RemoveUnit(u)
        set u = null
    endfunction
     


    How to use:



    How to install:




    Necessary: Copy & paste the Unit Indexer trigger into your map.
    Necessary: Copy & paste the Damage Engine trigger into your map
    Necessary: Copy the Cheat Death Ability from Object Editor, and set it to the variable DamageBlockingAbility.
    Necessary: Copy the Spell Damage Detector Ability from Object Editor, and set it to the variable SpellDamageAbility.
    Recommended: Mana Shield abilities need their Damage Absorbed % reduced to 0.00. The test map has this implemented already.
    Recommended: Anti-Magic Shell (amount-based) has to be triggered. I included the dummy ability, as well as the trigger, in the test map.
    Recommended: Locust Swarm needs its Damage Return Factor changed from 0.75 to -0.75. The test map has this implemented already.
    Recommended: Unlike in PDD, do not remove the Spell Damage Reduction ability from your Runed Bracers. Instead, modify the spell damage ability (in Object Editor, go to spells tab, then to special from the left column, then under items change Spell Damage Reduction's Damage Reduction) to 1.67 instead of .33. The test map has this implemented already. If you are switching from PDD and already had that ability removed, just implement the cross-compatibility PDD script I included below.
    Recommended: Spell damage is converted into negative damage values without this system, so I recommend not using the event "Unit Takes Damage" and, instead, using Damage Engine to process those events (DamageEvent becomes Equal to 1.00).
    Recommended: Life Drain doesn't heal the caster. To fix this, I have created a mostly-perfect trigger which heals the caster to the correct amount at the correct time. You can find this trigger in the test map.
    Note: You can configure the script to detect certain conditions that may affect its spell resistance (ie. a different item than runed bracers or different variations of Elune's Grace). You can use a "DamageModifierEvent Equal to 1.00" trigger to adjust the damage amount manually, just by checking if DamageEventTarget has a certain item or ability.
    Note: There is no need for Get/SetUnitLife functions, nor UnitDamageTargetEx. The system uses a Unit State event to detect when the unit's life has been adjusted for damage, and then an "AfterDamageEvent Equal to 1.00" event is fired.

    • Damage Engine Config
      • Events
      • Conditions
      • Actions
        • -------- - --------
        • -------- This trigger's conditions let you filter out units you don't want detection for. --------
        • -------- NOTE: By default, units with Locust will not pass the check. --------
        • -------- TIP: The unit is called UDexUnits[UDex] and its custom value is UDex --------
        • -------- - --------
        • -------- Copy the Cheat Death Ability from Object Editor into your map and set the following variable respectively: --------
        • -------- - --------
        • Set DamageBlockingAbility = Cheat Death Ability (+500,000)
        • -------- - --------
        • -------- Copy the Detect Spell Damage Ability from Object Editor into your map and set the following variable respectively: --------
        • -------- - --------
        • Set SpellDamageAbility = Detect Spell Damage
        • -------- - --------
        • -------- 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 --------
        • -------- - --------
        • Set DamageTypeExplosive = -1
        • Set DamageTypeCriticalStrike = 1
        • Set DamageTypeHeal = 2
        • Set DamageTypeReduced = 3
        • Set DamageTypeBlocked = 4
        • -------- - --------
        • -------- Leave the next Set statement disabled if you modified the Spell Damage Reduction item ability to 1.67 reduction --------
        • -------- Otherwise, if you removed that ability from Runed Bracers, you'll need to enable this line: --------
        • -------- - --------
        • Set DmgEvBracers = Runed Bracers
        • -------- - --------
        • -------- Set the damage multiplication factor (1.00 being unmodified, increasing in damage over 1.00 and at 0 damage with 0.00) --------
        • -------- NOTE. With the default values, Runed Bracers is reduces 33%, Elune's Grace reduces 20% and Ethereal increases 67% --------
        • -------- - --------
        • Set DAMAGE_FACTOR_BRACERS = 0.67
        • Set DAMAGE_FACTOR_ELUNES = 0.80
        • Set DAMAGE_FACTOR_ETHEREAL = 1.67
        • -------- - --------
        • -------- Added 25 July 2017 to allow detection of things like Bash or Pulverize or AOE spread --------
        • -------- - --------
        • Set DamageEventAOE = 1
        • Set DamageEventLevel = 1


    Code (vJASS):

    //===========================================================================
    // Damage Engine lets you detect, amplify, block or nullify damage. It even
    // lets you detect if the damage was physical or from a spell. Just reference
    // DamageEventAmount/Source/Target or the boolean IsDamageSpell, to get the
    // necessary damage event data.
    //
    // - Detect damage: use the event "DamageEvent Equal to 1.00"
    // - To change damage before it's dealt: use the event "DamageModifierEvent Equal to 1.00"
    // - Detect damage after it was applied, use the event "AfterDamageEvent Equal to 1.00"
    // - Detect spell damage: use the condition "IsDamageSpell Equal to True"
    // - Detect zero-damage: use the event "DamageEvent Equal to 2.00" (an AfterDamageEvent will not fire for this)
    //
    // You can specify the DamageEventType before dealing triggered damage. To prevent an already-improbable error, I recommend running the trigger "ClearDamageEvent (Checking Conditions)" after dealing triggered damage from within a damage event:
    // - Set NextDamageType = DamageTypeWhatever
    // - Unit - Cause...
    // - Trigger - Run ClearDamageEvent (Checking Conditions)
    //
    // You can modify the DamageEventAmount and the DamageEventType from a "DamageModifierEvent Equal to 1.00" trigger.
    // - If the amount is modified to negative, it will count as a heal.
    // - If the amount is set to 0, no damage will be dealt.
    //
    // If you need to reference the original in-game damage, use the variable "DamageEventPrevAmt".
    //
    //===========================================================================
    // Programming note about "integer i" and "udg_DmgEvRecursionN": integer i
    // ranges from -1 upwards. "udg_DmgEvRecursionN" ranges from 0 upwards.
    // "integer i" is always 1 less than "udg_DmgEvRecursionN"
    //
    function DmgEvResetVars takes nothing returns nothing
        local integer i = udg_DmgEvRecursionN - 2
        set udg_DmgEvRecursionN = i + 1
        if i >= 0 then
            set udg_DamageEventPrevAmt  = udg_LastDmgPrevAmount[i]
            set udg_DamageEventAmount   = udg_LastDmgValue[i]
            set udg_DamageEventSource   = udg_LastDmgSource[i]
            set udg_DamageEventTarget   = udg_LastDmgTarget[i]
            set udg_IsDamageSpell       = udg_LastDmgWasSpell[i]
            set udg_DamageEventType     = udg_LastDmgPrevType[i]
        endif
    endfunction

    function CheckDamagedLifeEvent takes boolean clear returns nothing
        if clear then
            set udg_NextDamageOverride = false
            set udg_NextDamageType = 0
        endif
        if udg_DmgEvTrig != null then
            call DestroyTrigger(udg_DmgEvTrig)
            set udg_DmgEvTrig = null
            if udg_IsDamageSpell then
                call SetWidgetLife(udg_DamageEventTarget, RMaxBJ(udg_LastDamageHP, 0.41))
                if udg_LastDamageHP <= 0.405 then
                    if udg_DamageEventType < 0 then
                        call SetUnitExploded(udg_DamageEventTarget, true)
                    endif
                    //Kill the unit
                    call DisableTrigger(udg_DamageEventTrigger)
                    call UnitDamageTarget(udg_DamageEventSource, udg_DamageEventTarget, -999, false, false, null, DAMAGE_TYPE_UNIVERSAL, null)
                    call EnableTrigger(udg_DamageEventTrigger)
                endif
            elseif GetUnitAbilityLevel(udg_DamageEventTarget, udg_DamageBlockingAbility) > 0 then
                call UnitRemoveAbility(udg_DamageEventTarget, udg_DamageBlockingAbility)
                call SetWidgetLife(udg_DamageEventTarget, udg_LastDamageHP)
            endif
            if udg_DamageEventAmount != 0.00 and not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
                set udg_AfterDamageEvent = 0.00
                set udg_AfterDamageEvent = 1.00
                set udg_AfterDamageEvent = 0.00
            endif
            call DmgEvResetVars()
        endif
    endfunction
     
    function DmgEvOnAOEEnd takes nothing returns nothing
        if udg_DamageEventAOE > 1 then
            set udg_AOEDamageEvent = 0.00
            set udg_AOEDamageEvent = 1.00
            set udg_AOEDamageEvent = 0.00
            set udg_DamageEventAOE = 1
        endif
        set udg_DamageEventLevel = 1
        set udg_EnhancedDamageTarget = null
        call GroupClear(udg_DamageEventAOEGroup)
    endfunction
     
    function DmgEvOnExpire takes nothing returns nothing
        set udg_DmgEvStarted = false
        call CheckDamagedLifeEvent(true)
        //Reset things so they don't perpetuate for AoE/Level target detection
        call DmgEvOnAOEEnd()
        set udg_DamageEventTarget = null
        set udg_DamageEventSource = null
    endfunction

    function PreCheckDamagedLifeEvent takes nothing returns boolean
        call CheckDamagedLifeEvent(true)
        return false
    endfunction

    function OnUnitDamage takes nothing returns boolean
        local boolean override = udg_DamageEventOverride
        local integer i
        local integer e = udg_DamageEventLevel
        local integer a = udg_DamageEventAOE
        local string s
        local real prevAmount
        local real life
        local real prevLife
        local unit u
        local unit f
        call CheckDamagedLifeEvent(false) //in case the unit state event failed and the 0.00 second timer hasn't yet expired
        set i = udg_DmgEvRecursionN - 1 //Had to be moved here due to false recursion tracking
        if i < 0 then
            //Added 25 July 2017 to detect AOE damage or multiple single-target damage
            set u                       = udg_DamageEventTarget
            set f                       = udg_DamageEventSource
        elseif i < 16 then
            set udg_LastDmgPrevAmount[i]= udg_DamageEventPrevAmt
            set udg_LastDmgValue[i]     = udg_DamageEventAmount
            set udg_LastDmgSource[i]    = udg_DamageEventSource
            set udg_LastDmgTarget[i]    = udg_DamageEventTarget
            set udg_LastDmgWasSpell[i]  = udg_IsDamageSpell
            set udg_LastDmgPrevType[i]  = udg_DamageEventType
        else
            set s = "WARNING: Recursion error when dealing damage! Make sure when you deal damage from within a DamageEvent trigger, do it like this:\n\n"
            set s = s + "Trigger - Turn off (This Trigger)\n"
            set s = s + "Unit - Cause...\n"
            set s = s + "Trigger - Turn on (This Trigger)"
     
            //Delete the next couple of lines to disable the in-game recursion crash warnings
            call ClearTextMessages()
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.00, 0.00, 999.00, s)
            return false
        endif
        set udg_DmgEvRecursionN     = i + 2
        set prevAmount              = GetEventDamage()
        set udg_DamageEventTarget   = GetTriggerUnit()
        set udg_DamageEventSource   = GetEventDamageSource()
     
        set udg_DamageEventAmount   = prevAmount
     
        set udg_DamageEventType     = udg_NextDamageType
        set udg_NextDamageType      = 0
        set udg_DamageEventOverride = udg_NextDamageOverride
        set udg_NextDamageOverride  = false
     
        if i < 0 then
            //Added 25 July 2017 to detect AOE damage or multiple single-target damage
            if udg_DamageEventType == 0 then
                if f == udg_DamageEventSource then
                    //Source has damaged more than once
                    if IsUnitInGroup(udg_DamageEventTarget, udg_DamageEventAOEGroup) then
                        //Added 5 August 2017 to improve tracking of enhanced damage against, say, Pulverize
                        set udg_DamageEventLevel = udg_DamageEventLevel + 1
                        set udg_EnhancedDamageTarget = udg_DamageEventTarget
                    else
                        //Multiple targets hit by this source - flag as AOE
                        set udg_DamageEventAOE = udg_DamageEventAOE + 1
                    endif
                else
                    //New damage source - unflag everything
                    set u = udg_DamageEventSource
                    set udg_DamageEventSource = f
                    call DmgEvOnAOEEnd()
                    set udg_DamageEventSource = u
                endif
                call GroupAddUnit(udg_DamageEventAOEGroup, udg_DamageEventTarget)
            endif
            if not udg_DmgEvStarted then
                set udg_DmgEvStarted = true
                call TimerStart(udg_DmgEvTimer, 0.00, false, function DmgEvOnExpire)
            endif
        endif
     
        if prevAmount == 0.00 then
            if not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
                set udg_DamageEventPrevAmt = 0.00
                set udg_DamageEvent = 0.00
                set udg_DamageEvent = 2.00
                set udg_DamageEvent = 0.00
            endif
            call DmgEvResetVars()
        else
            set u = udg_DamageEventTarget
            set udg_IsDamageSpell = prevAmount < 0.00
            if udg_IsDamageSpell then
                set prevAmount = -udg_DamageEventAmount
                set life = 1.00
                if IsUnitType(u, UNIT_TYPE_ETHEREAL) and not IsUnitType(u, UNIT_TYPE_HERO) then
                    set life = life*udg_DAMAGE_FACTOR_ETHEREAL //1.67
                endif
                if GetUnitAbilityLevel(u, 'Aegr') > 0 then
                    set life = life*udg_DAMAGE_FACTOR_ELUNES //0.80
                endif
                if udg_DmgEvBracers != 0 and IsUnitType(u, UNIT_TYPE_HERO) then
                    //Inline of UnitHasItemOfTypeBJ without the potential handle ID leak.
                    set i = 6
                    loop
                        set i = i - 1
                        if GetItemTypeId(UnitItemInSlot(u, i)) == udg_DmgEvBracers then
                            set life = life*udg_DAMAGE_FACTOR_BRACERS //0.67
                            exitwhen true
                        endif
                        exitwhen i == 0
                    endloop
                endif
                set udg_DamageEventAmount = prevAmount*life
            endif
            set udg_DamageEventPrevAmt = prevAmount
            set udg_DamageModifierEvent = 0.00
            if not udg_DamageEventOverride then
                set udg_DamageModifierEvent = 1.00
                if not udg_DamageEventOverride then
                    set udg_DamageModifierEvent = 2.00
                    set udg_DamageModifierEvent = 3.00
                endif
            endif
            set udg_DamageEventOverride = override
            if udg_DamageEventAmount > 0.00 then
                set udg_DamageModifierEvent = 4.00
            endif
            set udg_DamageModifierEvent = 0.00
            if not udg_HideDamageFrom[GetUnitUserData(udg_DamageEventSource)] then
                set udg_DamageEvent = 0.00
                set udg_DamageEvent = 1.00
                set udg_DamageEvent = 0.00
            endif
            call CheckDamagedLifeEvent(true) //in case the unit state event failed from a recursive damage event
     
            //All events have run and the damage amount is finalized.
            set life = GetWidgetLife(u)
            set udg_DmgEvTrig = CreateTrigger()
            call TriggerAddCondition(udg_DmgEvTrig, Filter(function PreCheckDamagedLifeEvent))
            if not udg_IsDamageSpell then
                if udg_DamageEventAmount != prevAmount then
                    set life = life + prevAmount - udg_DamageEventAmount
                    if GetUnitState(u, UNIT_STATE_MAX_LIFE) < life then
                        set udg_LastDamageHP = life - prevAmount
                        call UnitAddAbility(u, udg_DamageBlockingAbility)
                    endif
                    call SetWidgetLife(u, RMaxBJ(life, 0.42))
                endif
                call TriggerRegisterUnitStateEvent(udg_DmgEvTrig, u, UNIT_STATE_LIFE, LESS_THAN, RMaxBJ(0.41, life - prevAmount/2.00))
            else
                set udg_LastDamageHP = GetUnitState(u, UNIT_STATE_MAX_LIFE)
                set prevLife = life
                if life + prevAmount*0.75 > udg_LastDamageHP then
                    set life = RMaxBJ(udg_LastDamageHP - prevAmount/2.00, 1.00)
                    call SetWidgetLife(u, life)
                    set life = (life + udg_LastDamageHP)/2.00
                else
                    set life = life + prevAmount*0.50
                endif
                set udg_LastDamageHP = prevLife - (prevAmount - (prevAmount - udg_DamageEventAmount))
                call TriggerRegisterUnitStateEvent(udg_DmgEvTrig, u, UNIT_STATE_LIFE, GREATER_THAN, life)
            endif
        endif
        set u = null
        set f = null
        return false
    endfunction

    function CreateDmgEvTrg takes nothing returns nothing
        set udg_DamageEventTrigger = CreateTrigger()
        call TriggerAddCondition(udg_DamageEventTrigger, Filter(function OnUnitDamage))
    endfunction

    function SetupDmgEv takes nothing returns boolean
        local integer i = udg_UDex
        local unit u
        if udg_UnitIndexEvent == 1.00 then
            set u = udg_UDexUnits[i]
            if GetUnitAbilityLevel(u, 'Aloc') == 0 and TriggerEvaluate(gg_trg_Damage_Engine_Config) then
                set udg_UnitDamageRegistered[i] = true
                call TriggerRegisterUnitEvent(udg_DamageEventTrigger, u, EVENT_UNIT_DAMAGED)
                call UnitAddAbility(u, udg_SpellDamageAbility)
                call UnitMakeAbilityPermanent(u, true, udg_SpellDamageAbility)
            endif
            set u = null
        else
            set udg_HideDamageFrom[i] = false
            if udg_UnitDamageRegistered[i] then
                set udg_UnitDamageRegistered[i] = false
                set udg_DamageEventsWasted = udg_DamageEventsWasted + 1
                if udg_DamageEventsWasted == 32 then //After 32 registered units have been removed...
                    set udg_DamageEventsWasted = 0
         
                    //Rebuild the mass EVENT_UNIT_DAMAGED trigger:
                    call DestroyTrigger(udg_DamageEventTrigger)
                    call CreateDmgEvTrg()
                    set i = udg_UDexNext[0]
                    loop
                        exitwhen i == 0
                        if udg_UnitDamageRegistered[i] then
                            call TriggerRegisterUnitEvent(udg_DamageEventTrigger, udg_UDexUnits[i], EVENT_UNIT_DAMAGED)
                        endif
                        set i = udg_UDexNext[i]
                    endloop
                endif
            endif
        endif
        return false
    endfunction
     
    //===========================================================================
    function InitTrig_Damage_Engine takes nothing returns nothing
        local unit u = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), 'uloc', 0, 0, 0)
        local integer i = bj_MAX_PLAYERS //Fixed in 3.8
     
        //Create this trigger with UnitIndexEvents in order add and remove units
        //as they are created or removed.
        local trigger t = CreateTrigger()
        call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
        call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 2.00)
        call TriggerAddCondition(t, Filter(function SetupDmgEv))
        set t = null
     
        //Run the configuration trigger to set all configurables:
        if gg_trg_Damage_Engine_Config == null then
            //It's possible this InitTrig_ function ran first, in which case use ExecuteFunc.
            call ExecuteFunc("Trig_Damage_Engine_Config_Actions")
        else
            call TriggerExecute(gg_trg_Damage_Engine_Config)
        endif
     
        //Create trigger for storing all EVENT_UNIT_DAMAGED events.
        call CreateDmgEvTrg()
     
        //Create GUI-friendly trigger for cleaning up after UnitDamageTarget.
        set udg_ClearDamageEvent = CreateTrigger()
        call TriggerAddCondition(udg_ClearDamageEvent, Filter(function PreCheckDamagedLifeEvent))
     
        //Disable SpellDamageAbility for every player.
        loop
            set i = i - 1
            call SetPlayerAbilityAvailable(Player(i), udg_SpellDamageAbility, false)
            exitwhen i == 0
        endloop
     
        //Preload abilities.
        call UnitAddAbility(u, udg_DamageBlockingAbility)
        call UnitAddAbility(u, udg_SpellDamageAbility)
        call RemoveUnit(u)
        set u = null
    endfunction
     


    Cross-Compatibility with PDD and GDD


    Code (vJASS):

    //Physical Damage Detection plugin for Damage Engine 5.0 and prior.
    //
    //A few important notes:
    //- Spell reduction is handled multiplicatively in DamageEngine, instead of additively like in PDD.
    //- Just like in PDD, make sure you've modified the Runed Bracers item to remove the Spell Damage Reduction ability
    //- Move any UnitDamageTargetEx calls to an "AfterDamageEvent Equal to 1.00" trigger
    //- You do not need custom Get/Set-Unit(Max)Life functions, but I included them for ease of import.
    //
    //I will add more disclaimers, tips or fixes if anyone runs into issues!

    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
    function UnitDamageTargetEx takes unit source, unit target, boolean b, real amount, boolean attack, boolean ranged, attacktype a, damagetype d, weapontype w returns boolean
        local integer ptype = udg_PDD_damageType
        //To keep this functioning EXACTLY the same as it does in PDD, you need to move it
        //to an AfterDamageEvent Equal to 1.00 trigger. It's up to you.
        if ptype != udg_PDD_CODE then
            set udg_PDD_damageType = udg_PDD_CODE
            call UnitDamageTarget(source, target, amount, attack, ranged, a, d, w)
            call TriggerEvaluate(udg_ClearDamageEvent)
            set udg_PDD_damageType = ptype
            return true
        endif
        return false
    endfunction
    function PDD_OnDamage takes nothing returns boolean
        //cache previously-stored values in the event of recursion.
        local unit source = udg_PDD_source
        local unit target = udg_PDD_target
        local real amount = udg_PDD_amount
        local integer typ = udg_PDD_damageType
        //set variables to their event values.
        set udg_PDD_source = udg_DamageEventSource
        set udg_PDD_target = udg_DamageEventTarget
        set udg_PDD_amount = udg_DamageEventAmount
        //Extract the damage type from what we already have to work with.
        if udg_IsDamageSpell then
            set udg_PDD_damageType = udg_PDD_SPELL
        elseif udg_PDD_damageType != udg_PDD_CODE then
            set udg_PDD_damageType = udg_PDD_PHYSICAL
        endif
        //Fire PDD event.
        set udg_PDD_damageEventTrigger = 0.00
        set udg_PDD_damageEventTrigger = 1.00
        //Hey, this works and I'm really happy about that!
        set udg_DamageEventAmount = udg_PDD_amount
        //reset things just in case of a recursive event.
        set udg_PDD_damageEventTrigger = 0.00
        set udg_PDD_source = source
        set udg_PDD_target = target
        set udg_PDD_amount = amount
        set udg_PDD_damageType = typ
        set source = null
        set target = null
        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


    Code (vJASS):
    //GDD Compatibility for Damage Engine 5.0 and prior
    //globals
    //    real udg_GDD_Event = 0
    //    real udg_GDD_Damage = 0
    //    unit udg_GDD_DamagedUnit = null
    //    unit udg_GDD_DamageSource = null
    //endglobals
    //===========================================================================
    function GDD_Event takes nothing returns boolean
        local unit s = udg_GDD_DamageSource
        local unit t = udg_GDD_DamagedUnit
        local real v = udg_GDD_Damage
        set udg_GDD_DamageSource = udg_DamageEventSource
        set udg_GDD_DamagedUnit = udg_DamageEventTarget
        set udg_GDD_Damage = udg_DamageEventAmount
        set udg_GDD_Event = 1
        set udg_GDD_Event = 0
        set udg_GDD_DamageSource = s
        set udg_GDD_DamagedUnit = t
        set udg_GDD_Damage = v
        set s = null
        set t = null
        return false
    endfunction
    //===========================================================================
    function InitTrig_GUI_Friendly_Damage_Detection takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 1)
        call TriggerRegisterVariableEvent(t, "udg_DamageEvent", EQUAL, 2)
        call TriggerAddCondition(t, Filter(function GDD_Event))
        set t = null
    endfunction



    Change log
    1.0.0.0 - Release of Damage Event
    2.0.0.0 - Release of Damage Engine which combines Damage Modifier
    2.1.0.0 - Circumvented a very lame wc3 bug that's making the timer expire twice. It's still expiring twice but there's nothing I can do to figure out why.
    2.2.0.0 - Fixed the double expire bug completely and fixed a bug that could give weird life/max life readings.
    2.2.1.0 - Fixed a really nasty bug with the engine corrupting Unit Indexer after recreating the Damage Event trigger.
    3.0.0.0 - Fixed everything and added Spell Damage Detection!
    3.0.1.0 - Fixed triggered damage bug when it was set very high, and fixed recursion so it stops at 16 recursive events instead of at a potentially-buggy time.
    3.0.2.0 - Fixed bug that ethereal units didn't have spell damage amplification and that Elune's Grace wasn't reducing spell damage.
    3.0.3.0 - Added a get unit life/max life function and fixed a bug where heroes were getting their damage amped too much from banish.
    3.1.0.0 - no longer uses a timer. Also, got rid of the override/explodes booleans. Use DamageEventType instead.
    3.1.1.0 - uses a timer in addition to the event in case the event didn't go off.
    3.1.2.0 - has improved unit state event handling for better results.
    3.1.3.0 - fixed a potential recursion issue introduced when I switched to unit state events.
    3.1.3.1 - improved the accuracy of spell damage affecting a unit state change.
    3.2.0.0 - added a bunch of optional textmacros for an upcoming optional vJass plugin.
    3.3.0.0 - got rid of the textmacros. Now requires you to use the variable "NextDamageType" before dealing triggered damage. Code now uses as little custom script as possible.
    3.4.0.0 - integrated Mana Shield processing. Requires the user to set Damage Absorbed % to 0.00 for both Mana Shield abilities and for all 3 level
    3.5.0.0 - decoupled Mana Shield from DamageEngine and split DamageModification into 4 parts. Added a custom Anti-Magic Shell to the test map which can be used as a template for any kind of shield. Fixed a recursion issue. Re-applied backwards-compatibility with scripts that used DamageEventOverride.
    3.5.1.0 - Fixed a bug introduced with a previous damage processing update. Thanks to kruzerg and Spellbound for finding this bug with the system!
    3.5.2.0 - Fixed a bug with tornado slow aura and made a couple minor improvements.
    3.5.3.0 - In the previous update I disabled the zero-damage event from working normally. This update reverts that, as I found there was a bug with recursion with that event which I have now fixed. I also made several other minor improvements.
    3.5.3.1 - Fixed an extremely-rare issue with recursive damage modifier events not guaranteed to fire if they were greater than 1.
    3.5.4.0 - Fixed a bug with recursive events not firing 100% of the time. Added a boolean called NextDamageOverride that will let you skip the main 3 damage modifier events and built-in spell damage modifier, allowing you to deal "true" damage (which can still be reduced by shields). Added a feature to ClearDamageEvent to set NextDamageOverride to false and NextDamageType to 0 in case the damage engine didn't run due to spell immunity or invulnerability.
    3.5.4.1 - Fixed a bug introduced with the ClearDamageEvent change preventing both NextDamageX from working together. So as to not introduce potentially-new bugs I will only do bug fixes in the near future.
    3.6.0.0 - Converted everything to JASS, removed numerous extra variables, added a HideDamageFrom boolean array so users can opt to not run DamageEvents and AfterDamageEvents from those units. DamageModifierEvents will still run.
    3.6.0.1 - Fixed a potential handle ID issue if the user was using the compatibility setting with looking_for_help's PDD. Changed the compatibility script for PDD to compensate for it being integrated into Damage Engine. Updated the demo map behavior for the Knight so he now deals 4x instead of takes it.
    3.6.1.0 - Fixed a major recursion misflag caused by storing RecursionN to a local before potentially modifying RecursionN in the CheckForDamage function. Thanks to ssbbssc2 and Jampion for pointing this out!
    3.7.0.0 - Added some new variables to detect when a unit has been damaged by the same source multiple times in one instance (udg_DamageEventLevel tracks how many times this has happened in this moment). The other variables detect AOE damage and provide an event after all the AOE damage has been fully applied.
    3.8.0.0 - Fixed issues plaguing users of patch 1.29 by updating Damage Engine and Unit Indexer to compensate for the player slot state increase.
     

    Attached Files: