- Joined
- Sep 26, 2009
- Messages
- 9,529
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.
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.
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.
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
Last edited: