- Joined
- Feb 6, 2014
- Messages
- 2,466
A modular Damage Detection System.
Contains libraries for your damage detection, distinction and manipulation needs.
Changelog
v1.00 - [3 Aug 2016]
- Initial Release
v1.10 - [7 Aug 2016]
- Fixed unremoved BJDebug Messages.
- Fixed unremoved group in preplace.
- Fixed uncleaned Table/hashtable timer handle id.
- Fixed "Nonrecursive Damage bug".
- Fixed HP Bar flickering bug.
- Fixed a bug where revived units are not registered.
- Optimized the script, now only uses 1 static timer.
- Replaced UnitAlive by GetUnitTypeId as the condition for removal.
- Removed optional requirements TimerUtils and TimerUtilsEx.
- Implemented a periodic refresh mechanism.
- Implemented the bucket technique to limit the number of units per refresh.
v1.11 - [7 Aug 2016]
- Fixed a bug where it does not auto-register preplaced units when using Table.
- Fixed some functions compiling to trigger evaluation due to the order.
- Added a filter when using AUTO_REGISTER.
v1.12 - [8 Aug 2016]
- Fixed a bug when all DamageBuckets are removed.
- Fixed a bug where currentBucket points to a destroyed DamageBucket when it is destroyed.
- Fixed unremoved saved boolean in Table/hashtable.
- AutoRegisterFilter is now also applied to preplaced units.
v1.20 - [17 August 2016]
- Fixed recursion bug.
- Added Damage.enabled to control whether DamageEvent callbacks are ON/OFF.
- Added Damage.registerPermanent(code).
- Renamed Damage.registerFirst(code) to Damage.registerModifier(code).
- Damage.registerModifier(code) only comes within DamageModify.
- SET_MAX_LIFE of DamageModify is now preloaded.
v1.30 - [15 October 2016]
- Added more detailed documentation with examples.
- Now uses life change event instead of a timer avoiding several bugs.
- Fixed recursion damage workaround.
- Optimized and shortened the code.
v1.40 - [29 March 2017]
- Implemented a stack to make Damage.source, Damage.target, Damage.amount and Damage.type behave like local variables in the callback.
- Fixed Damage.source and Damage.target changing unit value withing callback due to recursion.
- Fixed Damage.amount and Damage.type changing value within callback due to recursion.
- Improved documentation on how it affects default Warcraft 3 abilities.
v1.41 - [24 May 2017]
- Added Damage.lockAmount() feature.
- Fixed bug occuring when units with very high hp takes damage.
- Changing Damage.amount will no longer work on non-modifier codes/triggers.
v1.42 - [25 May 2017]
- Fixed bug occuring when units with very high hp takes very small damage.
v1.43 - [29 May 2017]
- Fixed bug when magic damage amount is between 0.125 to 0.2.
v1.44 - [3 June 2017]
- Fixed bug when magic damage exceeds target's max health.
Contains libraries for your damage detection, distinction and manipulation needs.
JASS:
//! novjass
/*
DamagePackage v1.44
Documentation
by Flux
Contains libraries for your damage detection, distinction and manipulation needs.
-----------
DamageEvent
-----------
A lightweight damage detection system that
detects when a unit takes damage.
Can distinguish physical and magical damage.
------------
DamageModify
------------
An add-on to DamageEvent that allows modification
of damage taken before it is applied.
-------------
DamageObjects
-------------
Automatically generates required Objects by DamageEvent and
DamageModify.
CONTENTS:
- API
- How to use DamagePackage
- Important Notes
- Credits
- Changelog
//==================================================================//
API:
//==================================================================//
*/
DamageEvent:
-----------------------------
DAMAGE PROPERTIES
-----------------------------
Damage.source
// Unit that dealt the damage.
Damage.target
// Unit that took the damage.
Damage.amount
// Amount of damage taken.
Damage.type
// The type of damage taken.
// Values can be DAMAGE_TYPE_PHYSICAL or DAMAGE_TYPE_MAGICAL.
-----------------------------
DAMAGE CALLBACKS
-----------------------------
Damage.register(code)
// Registers a code that will permanently run when a registered
// unit takes damage.
Damage.registerTrigger(trigger)
// Registers a trigger that will run when a registered unit takes
// damage. The trigger can be disabled to avoid an infinite loop.
// Triggers will execute depending on the order they are registered.
Damage.unregisterTrigger(trigger)
// Removes the event in the trigger, causing the trigger to no longer
// run when a registered unit takes damage.
Damage.add(unit)
// For manual registration of units to DamageEvent. You
// won't use this if AUTO_REGISTER is set to true.
-----------------------------
MISCELLANEOUS
-----------------------------
Damage.enabled
//Turns on/off the entire DamageEvent.
Damage.lockAmount()
//Prevents further modification of damage on this damage instance.
DamageModify
set Damage.amount = <new amount>
// Modify the damage taken before it is applied.
Damage.registerModifier(code)
// Registers a code that will permanently run when a
// registered unit takes damage executing before any callbacks
// registered via Damage.register(code)/Damage.registerTrigger(trigger).
Damage.registerModifierTrigger(trigger)
// Registers a trigger that will run when a registered unit
// takes damage executing before any callbacks registered
// via Damage.register(code)/Damage.registerTrigger(trigger).
// The trigger can be disabled to avoid an infinite loop.
Damage.unregisterModifierTrigger(trigger)
// Removes the event in the trigger, it will no longer evaluate
// and execute when a registered unit takes damage.
/*
//==================================================================//
HOW TO USE DAMAGE PACKAGE:
//==================================================================//
1. Decide whether you need to use DamageEvent or DamageEvent with DamageModify.
If you only want to detect the damage and the type of damage, DamageEvent
will suffice, but if you want to modify the Damage taken, then you need
DamageModify. Note that DamageEvent without DamageModify is designed to be
lightweight, therefore it is better not to have DamageModify if you do not
need it. Using DamageModify is 90 to 100 microseconds slower.
2. Define Basic configuration of DamageEvent
*/
private constant integer DAMAGE_TYPE_DETECTOR
//An ability based on Runed Bracer that is utilized by DamageEvent to distinguish
//PHYSICAL and MAGICAL damage.
private constant real ETHEREAL_FACTOR
//Using DamageEvent disables the ethereal factor configured in Gameplay Constants as
//a side effect of Runed Bracer. However, the system simulates ethereal amplification
//and this is the new ethereal factor for magic damage. The configured ethereal factor
//in Gameplay Constants will be completely ignored.
.
private constant boolean AUTO_REGISTER
//Determines whether units entering the map are automatically registered
//to the DamageEvent system
private constant boolean PREPLACE_INIT
//Auto registers units initially placed in World Editor.
private constant integer COUNT_LIMIT
//When the number of registered individual unit in the current DamageBucket
//reaches COUNT_LIMIT, the system will find a new DamageBucket with units less
//than COUNT_LIMIT and use it as the new current DamageBucket. If none is found,
//the system will create a new DamageBucket.
private constant real REFRESH_TIMEOUT
//Periodic Timeout of Trigger Refresh.
//Every REFRESH_TIMEOUT, the system will refresh a signle DamageBucket.
private constant integer SET_MAX_LIFE
//An ability based on Item Life Bonus that is utilized by DamageModify to
//manipulate damage taken.
/*
3. Register a code or a trigger that will run when a registered unit takes damage. Example:
*/
//USING CODE PARAMETER
library L initializer Init
private function OnDamage takes nothing returns nothing
//This will run whenever a unit registered takes damage.
//Do you thing here
endfunction
private function Init takes nothing returns nothing
call Damage.register(function OnDamage)
endfunction
endlibrary
//USING TRIGGER PARAMETER
library L initializer Init
globals
private trigger trg = CreateTrigger()
endglobals
private function OnDamage takes nothing returns boolean
//This will run whenever a unit registered takes damage.
return false
endfunction
private function Init takes nothing returns nothing
call Damage.registerTrigger(trg)
call TriggerAddCondition(trg, Condition(function OnDamage))
endfunction
endlibrary
// You would want to use Damage.registerTrigger when avoiding recursion loop
// because the trigger can be disabled unlike code.
/*
4. If you want to modify the damage taken, you need the DamageModify library.
Simply change Damage.amount to whatever you want the new damage value to be.
Example:
*/
library L initializer Init
private function OnDamage takes nothing returns nothing
//All damage taken will be amplied by two
set Damage.amount = 2*Damage.amount
endfunction
private function Init takes nothing returns nothing
call Damage.registerModifier(function OnDamage)
endfunction
endlibrary
/*
DamageModify callbacks and triggers runs first before DamageEvent callbacks
and triggers. Example:
*/
library L initializer Init
private function OnDamageModifier takes nothing returns nothing
set Damage.amount = 0 //This will cause all damage taken to be zero
endfunction
private function OnDamage takes nothing returns nothing
call BJDebugMsg(GetUnitName(Damage.target) " takes " + R2S(Damage.amount) + " damage")
//Will print:
//"<Target Name> takes 0 damage"
endfunction
private function Init takes nothing returns nothing
call Damage.registerModifier(function OnDamageModifier)
call Damage.register(function OnDamageModifier)
endfunction
endlibrary
/*
5. If you want to deal damage inside onDamage callback without causing infinite loops,
you can do so using Damage.registerTrigger(trigger) and disabling the trigger before
the new damage is applied then enabling it again after damage is applied. Example:
*/
library L initializer Init
globals
private trigger trg = CreateTrigger()
endglobals
private function OnDamage takes nothing returns boolean
call BJDebugMsg(GetUnitName(Damage.target) " takes " + R2S(Damage.amount) + " damage")
call DisableTrigger(thistype.trg)
call UnitDamageTarget(Damage.source, Damage.target, 42.0, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
call EnableTrigger(thistype.trg)
call BJDebugMsg(GetUnitName(Damage.target) " takes an extra 42 damage.")
return false
endfunction
private function Init takes nothing returns nothing
call Damage.registerTrigger(trg)
call TriggerAddCondition(trg, Condition(function OnDamage))
endfunction
endlibrary
/*
//==================================================================//
IMPORTANT NOTES:
//==================================================================//
- Life Drain will not work with this system.
- Locust Swarm abilities will still work, but the "Data - Damage Return Factor"
defined in Object Editor must be multiplied to -1. Example, to fix the
default Locust Swarm ability, change the value from 0.75 to -0.75
- Runed Bracer items and abilities will not work with this system. But one
can easily make a trigger for that.
- Mana Shield works normally.
- Artillery attacks that causes unit to explode on death works normally.
- Finger of Death works normally.
- Spirit Link will not work with this system. However, it is possible to
recreate a triggered version of Spirit Link using this system.
- Magic Attacks are detected as DAMAGE_TYPE_PHYSICAL while Spells Attack
are detected as DAMAGE_TYPE_MAGICAL.
//==================================================================//
CREDITS:
//==================================================================//
looking_for_help
- for the Runed Bracer trick allowing this system to distinguish PHYSICAL
and MAGICAL damage.
- for Physical Damage Detection System which was used as a reference for
creating this system.
Bribe
- for the optional Table
Cokemonkey11 and PurplePoot
- for the bucket-based damage detection systems for less processes per refresh.
Aniki, Quilnez and Wietlol
- for finding bugs, giving feedbacks and suggestions.
//==================================================================//
CHANGELOG:
//==================================================================//
v1.00 - [3 Aug 2016]
- Initial Release
v1.10 - [7 Aug 2016]
- Fixed unremoved and unintentional BJDebug Messages.
- Fixed unremoved group in preplace.
- Fixed uncleaned Table/hashtable timer handle id.
- Fixed "Nonrecursive Damage bug".
- Fixed HP Bar flickering bug.
- Fixed a bug where revived units are not registered.
- Optimized the script, now only uses 1 static timer.
- Replaced UnitAlive by GetUnitTypeId as the condition for removal.
- Removed optional requirements TimerUtils and TimerUtilsEx.
- Implemented a periodic refresh mechanism.
- Implemented the bucket technique to limit the number of units per refresh.
v1.11 - [7 Aug 2016]
- Fixed a bug where it does not auto-register preplaced units when using Table.
- Fixed some functions compiling to trigger evaluation due to the order.
- Added a filter when using AUTO_REGISTER.
v1.12 - [8 August 2016]
- Fixed a bug when all DamageBuckets are removed.
- Fixed a bug where currentBucket points to a destroyed DamageBucket
when it is destroyed.
- Fixed unremoved saved boolean in Table/hashtable.
- AutoRegisterFilter is now also applied to preplaced units.
v1.20 - [17 August 2016]
- Fixed recursion bug.
- Added Damage.enabled to control whether DamageEvent callbacks are ON/OFF.
- Added Damage.registerPermanent(code).
- Renamed Damage.registerFirst(code) to Damage.registerModifier(code).
- Damage.registerModifier(code) only comes within DamageModify.
- SET_MAX_LIFE of DamageModify is now preloaded.
v1.30 - [15 October 2016]
- Added more detailed documentation with examples.
- Now uses life change event instead of a timer avoiding several bugs.
- Fixed recursion damage workaround.
- Optimized and shortened the code.
v1.40 - [29 March 2017]
- Implemented a stack to make Damage.source, Damage.target, Damage.amount and Damage.type behave like local variables in the callback.
- Fixed Damage.source and Damage.target changing unit value withing callback due to recursion.
- Fixed Damage.amount and Damage.type changing value within callback due to recursion.
- Improved documentation on how it affects default Warcraft 3 abilities.
v1.41 - [24 May 2017]
- Added Damage.lockAmount() feature.
- Fixed bug occuring when units with very high hp takes damage.
- Changing Damage.amount will no longer work on non-modifier codes/triggers.
v1.42 - [25 May 2017]
- Fixed bug occuring when units with very high hp takes very small damage.
v1.43 - [29 May 2017]
- Fixed bug when magic damage amount is between 0.125 to 0.2.
v1.44 - [3 June 2017]
- Fixed bug when magic damage exceeds target's max health.
*/
//! endnovjass
JASS:
library DamageEvent /*
----------------------------------
DamageEvent v1.44
by Flux
----------------------------------
A lightweight damage detection system that
detects when a unit takes damage.
Can distinguish physical and magical damage.
*/ requires /*
(nothing)
*/ optional Table /*
If not found, DamageEvent will create 2 hashtables. Hashtables are limited to 255 per map.
*/
//Basic Configuration
//See documentation for details
globals
private constant integer DAMAGE_TYPE_DETECTOR = 'ADMG'
private constant real ETHEREAL_FACTOR = 1.6666
endglobals
//Advanced Configuration
//Default values are recommended, edit only if you understand how the system works (See documentation).
globals
private constant boolean AUTO_REGISTER = true
private constant boolean PREPLACE_INIT = true
private constant integer COUNT_LIMIT = 50
private constant real REFRESH_TIMEOUT = 30.0
endglobals
static if not AUTO_REGISTER and not PREPLACE_INIT then
//Equivalent to AUTO_REGISTER or PREPLACE_INIT
else
//Autoregister Filter
//If it returns true, it will be registered automatically
private function AutoRegisterFilter takes unit u returns boolean
local integer id = GetUnitTypeId(u)
return id != 'dumi'
endfunction
endif
//Globals not meant to be edited.
globals
constant integer DAMAGE_TYPE_PHYSICAL = 1
constant integer DAMAGE_TYPE_MAGICAL = 2
private constant real MIN_LIFE = 0.406
private DamageBucket pickedBucket = 0
private DamageBucket currentBucket = 0
endglobals
struct DamageBucket
readonly integer count
readonly trigger trg
readonly group grp
readonly thistype next
readonly thistype prev
private static timer t = CreateTimer()
method destroy takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
if thistype(0).next == 0 then
call PauseTimer(thistype.t)
endif
if this == currentBucket then
set currentBucket = thistype(0).next
endif
call DestroyTrigger(this.trg)
call DestroyGroup(this.grp)
set this.trg = null
set this.grp = null
call this.deallocate()
endmethod
//Returns the DamageBucket where unit u belongs.
static method get takes unit u returns thistype
static if LIBRARY_Table then
return Damage.tb[GetHandleId(u)]
else
return LoadInteger(Damage.hash, GetHandleId(u), 0)
endif
endmethod
method remove takes unit u returns nothing
call GroupRemoveUnit(this.grp, u)
static if LIBRARY_Table then
call Damage.tb.remove(GetHandleId(u))
else
call RemoveSavedInteger(Damage.hash, GetHandleId(u), 0)
endif
endmethod
//Add unit u to this DamageBucket.
method add takes unit u returns nothing
call TriggerRegisterUnitEvent(this.trg, u, EVENT_UNIT_DAMAGED)
call GroupAddUnit(this.grp, u)
set this.count = this.count + 1
static if LIBRARY_Table then
set Damage.tb[GetHandleId(u)] = this
else
call SaveInteger(Damage.hash, GetHandleId(u), 0, this)
endif
endmethod
private static thistype temp
//Enumerate DamageBucket units, removing it if it is removed from the game
private static method cleanGroup takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this = temp
if GetUnitTypeId(u) != 0 then
call TriggerRegisterUnitEvent(this.trg, u, EVENT_UNIT_DAMAGED)
set this.count = this.count + 1
else
call GroupRemoveUnit(this.grp, u)
static if LIBRARY_Table then
call Damage.tb.remove(GetHandleId(u))
else
call RemoveSavedInteger(Damage.hash, GetHandleId(u), 0)
endif
endif
set u = null
endmethod
//Refreshes this DamageBucket
method refresh takes nothing returns nothing
local unit u
call DestroyTrigger(this.trg)
set this.trg = CreateTrigger()
call TriggerAddCondition(this.trg, Filter(function Damage.core))
set this.count = 0
set thistype.temp = this
call ForGroup(this.grp, function thistype.cleanGroup)
if this.count == 0 then
call this.destroy()
endif
endmethod
static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set this.count = 0
set this.trg = CreateTrigger()
set this.grp = CreateGroup()
call TriggerAddCondition(this.trg, Filter(function Damage.core))
set this.next = thistype(0)
set this.prev = thistype(0).prev
set this.next.prev = this
set this.prev.next = this
if this.prev == 0 then
call TimerStart(thistype.t, REFRESH_TIMEOUT, true, function Damage.refresh)
endif
return this
endmethod
endstruct
struct DamageTrigger
private trigger trg
private thistype next
private thistype prev
method destroy takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
static if LIBRARY_Table then
call Damage.tb.remove(GetHandleId(this.trg))
else
call RemoveSavedInteger(Damage.hash, GetHandleId(this.trg), 0)
endif
set this.trg = null
call this.deallocate()
endmethod
static method unregister takes trigger t returns nothing
local integer id = GetHandleId(t)
static if LIBRARY_Table then
if Damage.tb.has(id) then
call thistype(Damage.tb[id]).destroy()
endif
else
if HaveSavedInteger(Damage.hash, id, 0) then
call thistype(LoadInteger(Damage.hash, id, 0)).destroy()
endif
endif
endmethod
static method register takes trigger t returns nothing
local thistype this = thistype.allocate()
set this.trg = t
set this.next = thistype(0)
set this.prev = thistype(0).prev
set this.next.prev = this
set this.prev.next = this
static if LIBRARY_Table then
set Damage.tb[GetHandleId(t)] = this
else
call SaveInteger(Damage.hash, GetHandleId(t), 0, this)
endif
endmethod
static method executeAll takes nothing returns nothing
local thistype this = thistype(0).next
loop
exitwhen this == 0
if IsTriggerEnabled(this.trg) then
if TriggerEvaluate(this.trg) then
call TriggerExecute(this.trg)
endif
endif
set this = this.next
endloop
endmethod
endstruct
struct Damage extends array
private static thistype stackTop = 0
private static thistype global
private static integer array allocator
private unit stackSource
private unit stackTarget
private real stackAmount
private integer stackType
private thistype stackNext
private static real hp
static if LIBRARY_Table then
readonly static Table tb
else
readonly static hashtable hash = InitHashtable()
endif
//Allows the DamageModify module to access the configuration
static if LIBRARY_DamageModify then
private static constant real S_ETHEREAL_FACTOR = ETHEREAL_FACTOR
private static constant real S_MIN_LIFE = MIN_LIFE
endif
static method remove takes unit u returns nothing
call DamageBucket.get(u).remove(u)
endmethod
//Add unit u to the current DamageBucket
static method add takes unit u returns nothing
local DamageBucket temp
local DamageBucket b
//If unit does not belong to any DamageBucket yet
if DamageBucket.get(u) == 0 then
call UnitAddAbility(u, DAMAGE_TYPE_DETECTOR)
call UnitMakeAbilityPermanent(u, true, DAMAGE_TYPE_DETECTOR)
if currentBucket != 0 then
//When the current DamageBucket exceeds the limit
if currentBucket.count >= COUNT_LIMIT then
set temp = DamageBucket(0).next
loop
exitwhen temp == 0
//Find a DamageBucket with few units
if temp.count < COUNT_LIMIT then
exitwhen true
endif
set temp = temp.next
endloop
if temp == 0 then //If none is found
set currentBucket = DamageBucket.create()
else //If a DamageBucket is found, use it
set currentBucket = temp
endif
endif
else
set currentBucket = DamageBucket.create()
set pickedBucket = currentBucket
endif
call currentBucket.add(u)
endif
endmethod
//Periodic Refresh only refreshing one DamageBucket per REFRESH_TIMEOUT
//to avoid lag spike.
static method refresh takes nothing returns nothing
call pickedBucket.refresh()
loop
set pickedBucket = pickedBucket.next
exitwhen pickedBucket != 0
endloop
endmethod
static method operator amount takes nothing returns real
return thistype.stackTop.stackAmount
endmethod
static method operator type takes nothing returns integer
return thistype.stackTop.stackType
endmethod
static method operator target takes nothing returns unit
return thistype.stackTop.stackTarget
endmethod
static method operator source takes nothing returns unit
return thistype.stackTop.stackSource
endmethod
private static boolean prevEnable = true
static method operator enabled takes nothing returns boolean
return thistype.prevEnable
endmethod
static method operator enabled= takes boolean b returns nothing
local DamageBucket bucket = DamageBucket(0).next
if b != thistype.prevEnable then
loop
exitwhen bucket == 0
if b then
call EnableTrigger(bucket.trg)
else
call DisableTrigger(bucket.trg)
endif
set bucket = bucket.next
endloop
endif
set thistype.prevEnable = b
endmethod
//All registered codes will go to this trigger
private static trigger registered
static method register takes code c returns boolean
call TriggerAddCondition(thistype.registered, Condition(c))
return false //Prevents inlining
endmethod
static method registerTrigger takes trigger trig returns nothing
call DamageTrigger.register(trig)
endmethod
static method unregisterTrigger takes trigger trig returns nothing
call DamageTrigger.unregister(trig)
endmethod
implement optional DamageModify
static if not LIBRARY_DamageModify then
private static method afterDamage takes nothing returns boolean
call SetWidgetLife(thistype.stackTop.stackTarget, thistype.hp - thistype.stackTop.stackAmount)
call DestroyTrigger(GetTriggeringTrigger())
if thistype.global > 0 then
set thistype.allocator[thistype.global] = thistype.allocator[0]
set thistype.allocator[0] = thistype.global
set thistype.stackTop = thistype.stackTop.stackNext
endif
return false
endmethod
static method core takes nothing returns boolean
local real amount = GetEventDamage()
local thistype this
local real newHp
local trigger trg
if amount == 0.0 then
return false
endif
set this = thistype.allocator[0]
if (thistype.allocator[this] == 0) then
set thistype.allocator[0] = this + 1
else
set thistype.allocator[0] = thistype.allocator[this]
endif
set this.stackSource = GetEventDamageSource()
set this.stackTarget = GetTriggerUnit()
set this.stackNext = thistype.stackTop
set thistype.stackTop = this
if amount > 0.0 then
set this.stackType = DAMAGE_TYPE_PHYSICAL
set this.stackAmount = amount
call DamageTrigger.executeAll()
set thistype.allocator[this] = thistype.allocator[0]
set thistype.allocator[0] = this
set thistype.stackTop = thistype.stackTop.stackNext
elseif amount < 0.0 then
set this.stackType = DAMAGE_TYPE_MAGICAL
if IsUnitType(this.stackTarget, UNIT_TYPE_ETHEREAL) then
set amount = amount*ETHEREAL_FACTOR
endif
set this.stackAmount = -amount
call DamageTrigger.executeAll()
set thistype.hp = GetWidgetLife(this.stackTarget)
set newHp = thistype.hp + amount
if newHp < MIN_LIFE then
set newHp = MIN_LIFE
endif
call SetWidgetLife(this.stackTarget, newHp)
set trg = CreateTrigger()
if amount < -1.0 then
call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 1.0)
elseif amount < -0.125 then
call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.125)
else
call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.01)
endif
call TriggerAddCondition(trg, Condition(function thistype.afterDamage))
set trg = null
set thistype.global = this
endif
return false
endmethod
endif
static if PREPLACE_INIT then
private static method preplace takes nothing returns nothing
local group g = CreateGroup()
local unit u
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if AutoRegisterFilter(u) then
call thistype.add(u)
endif
endloop
call DestroyGroup(g)
call DestroyTimer(GetExpiredTimer())
set g = null
endmethod
endif
static if AUTO_REGISTER then
private static method entered takes nothing returns boolean
local unit u = GetTriggerUnit()
if AutoRegisterFilter(u) then
call thistype.add(u)
endif
set u = null
return false
endmethod
endif
implement DamageInit
endstruct
module DamageInit
private static method onInit takes nothing returns nothing
static if AUTO_REGISTER then
local trigger t = CreateTrigger()
local region reg = CreateRegion()
call RegionAddRect(reg, bj_mapInitialPlayableArea)
call TriggerRegisterEnterRegion(t, reg, null)
call TriggerAddCondition(t, function thistype.entered)
endif
static if LIBRARY_Table then
set thistype.tb = Table.create()
endif
static if PREPLACE_INIT then
call TimerStart(CreateTimer(), 0.0000, false, function thistype.preplace)
endif
set thistype.registered = CreateTrigger()
call DamageTrigger.register(thistype.registered)
set thistype.allocator[0] = 1
set thistype(0).stackSource = null
set thistype(0).stackTarget = null
set thistype(0).stackAmount = 0.0
set thistype(0).stackType = 0
endmethod
endmodule
endlibrary
JASS:
library DamageModify uses DamageEvent/*
---------------------------------
DamageModify v1.44
by Flux
---------------------------------
An add-on to DamageEvent that allows modification
of damage taken before it is applied.
*/
globals
private constant integer SET_MAX_LIFE = 'ASML'
endglobals
struct DamageTrigger2
private trigger trg
private thistype next
private thistype prev
method destroy takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
static if LIBRARY_Table then
call Damage.tb.remove(GetHandleId(this.trg))
else
call RemoveSavedInteger(Damage.hash, GetHandleId(this.trg), 0)
endif
set this.trg = null
call this.deallocate()
endmethod
static method unregister takes trigger t returns nothing
local integer id = GetHandleId(t)
static if LIBRARY_Table then
if Damage.tb.has(id) then
call thistype(Damage.tb[id]).destroy()
endif
else
if HaveSavedInteger(Damage.hash, id, 0) then
call thistype(LoadInteger(Damage.hash, id, 0)).destroy()
endif
endif
endmethod
static method register takes trigger t returns nothing
local thistype this = thistype.allocate()
set this.trg = t
set this.next = thistype(0)
set this.prev = thistype(0).prev
set this.next.prev = this
set this.prev.next = this
static if LIBRARY_Table then
set Damage.tb[GetHandleId(t)] = this
else
call SaveInteger(Damage.hash, GetHandleId(t), 0, this)
endif
endmethod
static method executeAll takes nothing returns nothing
local thistype this = thistype(0).next
loop
exitwhen this == 0
if IsTriggerEnabled(this.trg) then
if TriggerEvaluate(this.trg) then
call TriggerExecute(this.trg)
endif
endif
set this = this.next
endloop
endmethod
endstruct
module DamageModify
private static boolean changed = false
private static trigger registered2 = CreateTrigger()
private static boolean locked = false
static method registerModifier takes code c returns boolean
call TriggerAddCondition(thistype.registered2, Condition(c))
return false //Prevents inlining
endmethod
static method registerModifierTrigger takes trigger trg returns nothing
call DamageTrigger2.register(trg)
endmethod
static method unregisterModifierTrigger takes trigger trg returns nothing
call DamageTrigger2.unregister(trg)
endmethod
static method lockAmount takes nothing returns nothing
set thistype.locked = true
endmethod
private static method afterDamage takes nothing returns boolean
if GetUnitAbilityLevel(thistype.stackTop.stackTarget, SET_MAX_LIFE) > 0 then
call UnitRemoveAbility(thistype.stackTop.stackTarget, SET_MAX_LIFE)
endif
call SetWidgetLife(thistype.stackTop.stackTarget, thistype.hp - thistype.stackTop.stackAmount)
call DestroyTrigger(GetTriggeringTrigger())
if thistype.global > 0 then
set thistype.allocator[thistype.global] = thistype.allocator[0]
set thistype.allocator[0] = thistype.global
set thistype.stackTop = thistype.stackTop.stackNext
endif
return false
endmethod
static method core takes nothing returns boolean
local real amount = GetEventDamage()
local boolean changed = false
local thistype this
local trigger trg
local real newHp
if amount == 0.0 then
return false
endif
set this = thistype.allocator[0]
if (thistype.allocator[this] == 0) then
set thistype.allocator[0] = this + 1
else
set thistype.allocator[0] = thistype.allocator[this]
endif
set this.stackSource = GetEventDamageSource()
set this.stackTarget = GetTriggerUnit()
set this.stackNext = thistype.stackTop
set thistype.stackTop = this
if amount > 0.0 then
set this.stackType = DAMAGE_TYPE_PHYSICAL
set this.stackAmount = amount
call DamageTrigger2.executeAll()
set changed = thistype.changed
if changed then
set thistype.changed = false
endif
set thistype.locked = true
call DamageTrigger.executeAll()
set thistype.locked = false
elseif amount < 0.0 then
set this.stackType = DAMAGE_TYPE_MAGICAL
if IsUnitType(this.stackTarget, UNIT_TYPE_ETHEREAL) then
set amount = amount*S_ETHEREAL_FACTOR
endif
set this.stackAmount = -amount
call DamageTrigger2.executeAll()
set changed = thistype.changed
if changed then
set thistype.changed = false
endif
set thistype.locked = true
call DamageTrigger.executeAll()
set thistype.locked = false
endif
if amount < 0.0 or (changed and amount > 0.125) then
set thistype.hp = GetWidgetLife(this.stackTarget)
set trg = CreateTrigger()
if amount > 0.0 then
set newHp = thistype.hp + amount
if newHp > GetUnitState(this.stackTarget, UNIT_STATE_MAX_LIFE) then
call UnitAddAbility(this.stackTarget, SET_MAX_LIFE)
endif
call SetWidgetLife(this.stackTarget, newHp)
if amount > 1.0 then
call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, LESS_THAN, newHp - 1.0)
elseif amount > 0.125 then
call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, LESS_THAN, newHp - 0.125)
endif
else
set newHp = thistype.hp + amount
if newHp < S_MIN_LIFE then
set newHp = S_MIN_LIFE
endif
call SetWidgetLife(this.stackTarget, newHp)
if amount < -1.0 then
call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 1.0)
elseif amount < -0.125 then
call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.125)
else
call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.01)
endif
endif
call TriggerAddCondition(trg, Condition(function thistype.afterDamage))
set trg = null
set thistype.global = this
else
set thistype.allocator[this] = thistype.allocator[0]
set thistype.allocator[0] = this
set thistype.stackTop = thistype.stackTop.stackNext
endif
return false
endmethod
static method operator amount= takes real r returns nothing
if not thistype.locked then
set thistype.stackTop.stackAmount = r
set thistype.changed = true
endif
endmethod
private static method onInit takes nothing returns nothing
local unit u = CreateUnit(Player(14), 'hfoo', 0, 0, 0)
call UnitAddAbility(u, SET_MAX_LIFE)
call RemoveUnit(u)
set thistype.registered2 = CreateTrigger()
call DamageTrigger2.register(thistype.registered2)
set u = null
endmethod
endmodule
endlibrary
JASS:
library L initializer Init
private function OnDamage takes nothing returns nothing
//This will run whenever a unit registered takes damage.
//Do you thing here
endfunction
private function Init takes nothing returns nothing
call Damage.register(function OnDamage)
endfunction
endlibrary
Changelog
v1.00 - [3 Aug 2016]
- Initial Release
v1.10 - [7 Aug 2016]
- Fixed unremoved BJDebug Messages.
- Fixed unremoved group in preplace.
- Fixed uncleaned Table/hashtable timer handle id.
- Fixed "Nonrecursive Damage bug".
- Fixed HP Bar flickering bug.
- Fixed a bug where revived units are not registered.
- Optimized the script, now only uses 1 static timer.
- Replaced UnitAlive by GetUnitTypeId as the condition for removal.
- Removed optional requirements TimerUtils and TimerUtilsEx.
- Implemented a periodic refresh mechanism.
- Implemented the bucket technique to limit the number of units per refresh.
v1.11 - [7 Aug 2016]
- Fixed a bug where it does not auto-register preplaced units when using Table.
- Fixed some functions compiling to trigger evaluation due to the order.
- Added a filter when using AUTO_REGISTER.
v1.12 - [8 Aug 2016]
- Fixed a bug when all DamageBuckets are removed.
- Fixed a bug where currentBucket points to a destroyed DamageBucket when it is destroyed.
- Fixed unremoved saved boolean in Table/hashtable.
- AutoRegisterFilter is now also applied to preplaced units.
v1.20 - [17 August 2016]
- Fixed recursion bug.
- Added Damage.enabled to control whether DamageEvent callbacks are ON/OFF.
- Added Damage.registerPermanent(code).
- Renamed Damage.registerFirst(code) to Damage.registerModifier(code).
- Damage.registerModifier(code) only comes within DamageModify.
- SET_MAX_LIFE of DamageModify is now preloaded.
v1.30 - [15 October 2016]
- Added more detailed documentation with examples.
- Now uses life change event instead of a timer avoiding several bugs.
- Fixed recursion damage workaround.
- Optimized and shortened the code.
v1.40 - [29 March 2017]
- Implemented a stack to make Damage.source, Damage.target, Damage.amount and Damage.type behave like local variables in the callback.
- Fixed Damage.source and Damage.target changing unit value withing callback due to recursion.
- Fixed Damage.amount and Damage.type changing value within callback due to recursion.
- Improved documentation on how it affects default Warcraft 3 abilities.
v1.41 - [24 May 2017]
- Added Damage.lockAmount() feature.
- Fixed bug occuring when units with very high hp takes damage.
- Changing Damage.amount will no longer work on non-modifier codes/triggers.
v1.42 - [25 May 2017]
- Fixed bug occuring when units with very high hp takes very small damage.
v1.43 - [29 May 2017]
- Fixed bug when magic damage amount is between 0.125 to 0.2.
v1.44 - [3 June 2017]
- Fixed bug when magic damage exceeds target's max health.
Attachments
Last edited: