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

Damage Engine 3A.0.0.0 and 3.8.0.0

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Click here to go back to the current version of Damage Engine

For my GitHub Legacy Repository, click here

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.


JASS:
//===========================================================================
// 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
JASS:
//===========================================================================
// Damage Engine lets you detect, amplify, block or nullify damage. It even
// lets you detect if the damage was physical or from a spell. Just reference
// DamageEventAmount/Source/Target or the boolean IsDamageSpell, to get the
// necessary damage event data.
//
// - Detect damage: use the event "DamageEvent Equal to 1.00"
// - To change damage before it's dealt: use the event "DamageModifierEvent Equal to 1.00"
// - Detect damage after it was applied, use the event "AfterDamageEvent Equal to 1.00"
// - Detect spell damage: use the condition "IsDamageSpell Equal to True"
// - Detect zero-damage: use the event "DamageEvent Equal to 2.00" (an AfterDamageEvent will not fire for this)
//
// You can specify the DamageEventType before dealing triggered damage. 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



JASS:
//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

JASS:
//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


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.

Attachments

  • Damage Engine v3.8.w3x
    48.5 KB · Views: 1,003
Last edited:
Top