1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Music Contest #10 - Results are finally published! Drop by to check some retro songs and congratulate the winners!
    Dismiss Notice
  3. Join Terraining Contest #19 and witness the aftermath!
    Dismiss Notice
  4. The 3rd Melee Mapping Contest is ON! Join in on a ride of a 4v4 melee experience!
    Dismiss Notice
  5. The 30th edition of the Modeling Contest is finally up! The Portable Buildings need your attention, so come along and have a blast!
    Dismiss Notice

Damage Engine 3.8.0.0

Submitted by Bribe
This bundle is marked as approved. It works and satisfies the submission rules.
Damage Engine 3.8.0.0: the coolest, most user-friendly damage system among GUI and vJass alike.

Features:
  • Spell/physical damage differentiation;
  • Damage Blocking, reducing, amplifying and/or conversion to healing;
  • Does not require you to known nor use Jass NewGen Pack nor any custom script;
  • Included cross-compatibility with looking_for_help's PDD and Weep's GDD;
  • Custom DamageType association available to be set before and/or during damage event;
  • Detect enhanced effects like Bash, Frost Arrows, Black Arrow;
  • Detect AOE damage.
Thank you so much to looking_for_help for finding that spell damage detection method - it is 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 AfterDamageEvent. Thanks to Spellbound for several crucial bug reports.
Most of all, thank you to the users of this system who have helped me mold this project into the stable, powerful form it is in today!

Damage Engine Triggers
  • Damage Engine Config
    • Events
    • Conditions
      • (UDexUnits[UDex] is A structure) Equal to False
    • 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
 

Custom abilities in the test map

  • Life Drain Fix
    • Events
      • Game - AfterDamageEvent becomes Equal to 1.00
    • Conditions
      • IsDamageSpell Equal to True
      • (DamageEventSource has buff Drain Life (Caster)) Equal to True
      • (DamageEventTarget has buff Drain Life (Target)) Equal to True
    • Actions
      • Unit - Set life of DamageEventSource to ((Life of DamageEventSource) + DamageEventAmount)

  • Finger of Death Fix
    • Events
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
      • IsDamageSpell Equal to True
      • (Current order of DamageEventSource) Equal to (Order(fingerofdeath))
    • Actions
      • Set DamageEventType = DamageTypeExplosive

Code (vJASS):

constant function GetAMSBuffId takes nothing returns integer
    return 'Bams'
endfunction
constant function GetAMSAbilId takes nothing returns integer
    return 'A002'
endfunction
constant function GetAMSShieldVal takes nothing returns real
    return 300.00
endfunction
function Trig_Anti_Magic_Shield_Fix_Actions takes nothing returns nothing
    local integer id = GetUnitUserData(udg_DamageEventTarget)
    local real shield = udg_AMSAmount[id]- udg_DamageEventAmount
    if shield <= 0.00 then
        set udg_DamageEventAmount = -shield
        set shield = 0.00
        call UnitRemoveAbility(udg_DamageEventTarget, GetAMSBuffId())
    else
        set udg_DamageEventAmount = 0.00
        if udg_DamageEventType == 0 then
            set udg_DamageEventType = udg_DamageTypeBlocked
        endif
    endif
    set udg_AMSAmount[id] = shield
endfunction
function Trig_Anti_Magic_Shield_Fix_Conditions takes nothing returns boolean
    if udg_IsDamageSpell then
        if GetUnitAbilityLevel(udg_DamageEventTarget, GetAMSBuffId()) > 0 then
            call Trig_Anti_Magic_Shield_Fix_Actions()
        else
            set udg_AMSAmount[GetUnitUserData(udg_DamageEventTarget)] = 0.00
        endif
    endif
    return false
endfunction
function AMS_Refresh_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == GetAMSAbilId() then
        set udg_AMSAmount[GetUnitUserData(GetSpellTargetUnit())] = GetAMSShieldVal()
    endif
    return false
endfunction
function InitTrig_Anti_Magic_Shield_Fix takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_DamageModifierEvent", EQUAL, 4.00)
    call TriggerAddCondition(t, Condition(function Trig_Anti_Magic_Shield_Fix_Conditions))
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Filter(function AMS_Refresh_Conditions))
endfunction
 

  • Mana Shield Fix
    • Events
      • Game - DamageModifierEvent becomes Equal to 4.00
    • Conditions
      • (DamageEventTarget has buff Mana Shield) Equal to True
    • Actions
      • -------- Mana Shield handling. For it to work, you need to set the mana shield Damage Absorbed % --------
      • -------- to 0.00 for Mana Shield (Neutral Hostile) and for all 3 levels of Mana Shield for the Naga Sea Witch. --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
        • Then - Actions
          • Set DmgEvMana = (Mana of DamageEventTarget)
          • -------- Get the shielding mana multiplier from the target unit based on the unit or hero ability ("multiplier" mana per HP) --------
          • -------- If you have custom mana shield abilities or have modified the normal ones, add or configure those here. --------
          • Set DmgEvMSlvl = (Level of Mana Shield for DamageEventTarget)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • DmgEvMSlvl Equal to 0
            • Then - Actions
              • Set DmgEvManaMult = 2.00
            • Else - Actions
              • Set DmgEvManaMult = ((0.50 x (Real(DmgEvMSlvl))) + 0.50)
          • Set DmgEvMana = (DmgEvMana x DmgEvManaMult)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • DamageEventAmount Less than (DmgEvMana - 0.01)
            • Then - Actions
              • Set DmgEvMana = (DmgEvMana - DamageEventAmount)
              • Set DamageEventAmount = 0.00
              • Set DmgEvMana = (DmgEvMana / DmgEvManaMult)
              • -------- You can remove this damage type change if you want, it's mostly for the purpose of the test map. --------
              • Set DamageEventType = DamageTypeBlocked
            • Else - Actions
              • Set DamageEventAmount = (DamageEventAmount - DmgEvMana)
              • Set DmgEvMana = 0.00
              • Unit - Order DamageEventTarget to Neutral Naga Sea Witch - Deactivate Mana Shield
              • -------- You can remove this damage type change if you want, it's mostly for the purpose of the test map. --------
              • Set DamageEventType = DamageTypeReduced
          • Unit - Set mana of DamageEventTarget to DmgEvMana
        • Else - Actions



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.

As of September 2015, cross-compatibility for looking_for_help's Physical Damage Detection has also been added to the ranks of Weep's compatibility script (which had been added in 2012). Users do not have to change any of their custom triggers to switch from either system to Damage Engine. Simply delete the custom script in their systems and replace it with the following scripts:
Cross-Compatibility

For looking_for_help's system, you can delete the entire custom script code in the "DamageEvent" trigger and replace it with the following:
Code (vJASS):

//Physical Damage Detection plugin for Damage Engine 3.
//
//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

For Weep's system, you can delete the entire custom script code in the "GUI Friendly Damage Detection" trigger, and replace it with the following:
Code (vJASS):
//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

How to Use



Damage Engine lets you detect, amplify, block, nullify damage or even detect if a damage was done by a physical attack or by a spell. Just reference DamageEventAmount/Source/Target/IsDamageSpell, to get the 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"
To get the instant after the unit is damaged, "AfterDamageEvent Equal to 1.00"
Detect zero-damage: use the event "DamageEvent Equal to 2.00" (an AfterDamageEvent is not fired for this)
To set the DamageEventType before dealing triggered Damage, use:
- Set NextDamageType = DamageTypeWhatever
- Unit - Cause...
You can then reference and/or modify DamageEventAmount/Type from a "DamageModifierEvent Equal to 1.00" trigger.
If you need to reference the original in-game damage, use the variable "DamageEventPrevAmt".
If IsSpellDamage is equal to true, the damage was caused by a spell.
DamageEventType will be 0 if the damage was dealt in-game or if you didn't "Set NextDamageType = DamageTypeWhatever" before causing the damage.
You can also create your own special damage types and add them to the definitions. If you want the unit to explode on death by that damage, use a damage type integer less than 0.
If you fully block DamageEventAmount (setting it to 0) the AfterDamageEvent will not run.
DamageModifierEvent is split into four phases. Only 1 and 4 are important, you do not need phase 2 or 3. Your map will still work exactly the same without them. However, they are there to help you out if you want them.
Recommended use:
- Terminal Phase: Equal to 1.00. If your goal is to block damage or switch it to a heal, this is the best time. If you don't want the damage blocked any further, set DamageEventOverride to true. It will skip phases 2 and 3 and even skip phase 4 if the damage is equal to or less than 0.
- Abstract Phase: Equal to 2.00. If you don't need to know the damage event amount to modify the damage, do it here. Multiplication, division and logarithm calculations are ideal, here.
- Direct Phase: Equal to 3.00. Hardened skin reduces damage by 13 but not by less than 3. This phase is the perfect time to implement such a thing.
- Quantitative Shield (Final) Phase: Equal to 4.00. This is the most interesting one. I needed to create it in order to make an Anti-Magic Shell and Mana Shield respond to a modified DamageEventAmount instead of just the original.

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.

Keywords:
unit indexer, damage detection, damage event, weep, nestharus, looking_for_help, EVENT_PLAYER_UNIT_DAMAGED, damage engine, spell, physical
Contents

Damage Engine Testmap (Map)

Reviews
Moderator
23:20, 11th Jan 2015, BPower Criticism: On the one hand Damage Engine offers extremely high utility to every spell, system and map maker independent of the coding style ( GUI, JASS, vJass, .... ) on the other hand it's very easy to import and...
  1. 23:20, 11th Jan 2015, BPower

    Criticism:

    On the one hand Damage Engine offers extremely high utility to every spell, system and map maker
    independent of the coding style ( GUI, JASS, vJass, .... ) on the other hand it's very easy to import and use.

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

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

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

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


    Director's Cut
     
  2. Bannar

    Bannar

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

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

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

    Magtheridon96

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

    trick :p

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

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

    Jazztastic

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

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

    Adiktuz

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

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

    RiotApe

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

    Adiktuz

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

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

    RiotApe

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

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

    10/10
     
  9. Adiktuz

    Adiktuz

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

    Magtheridon96

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

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

    Bribe

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

    Magtheridon96

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

    Bribe

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

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

    EloTheMan

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

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

    Adiktuz

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

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

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

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

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

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

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

    "Game - UnitDamageEvent becomes Equal to 1.00"

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

    EloTheMan

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

    Thanks :D
     
  17. Adiktuz

    Adiktuz

    Joined:
    Oct 16, 2008
    Messages:
    9,683
    Resources:
    23
    Models:
    2
    Packs:
    1
    Maps:
    1
    Spells:
    16
    Tutorials:
    1
    JASS:
    2
    Resources:
    23
    ooh... Didn't know, were from diff time zones so it showed as 8:11am on my comp... ^_^
     
  18. sentrywiz

    sentrywiz

    Joined:
    Feb 10, 2009
    Messages:
    1,732
    Resources:
    16
    Maps:
    10
    Tutorials:
    6
    Resources:
    16
    I don't understand these damage systems. Or the indexing.

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

    Explanation?
     
  19. Bribe

    Bribe

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

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

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

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