This is a compilation of libraries intended to replace the damage detection and prevention engine ADamage. Originally, this was intended to be merely the next version of ADamage, but some conceptual changes such as moving from damage prevention to more general damage modification made it difficult to maintain backwards compatibility, so I decided to make it a new resource instead which could provide a more general, robust and modular damage detection and modification framework than ADamage. Also, I know some people will like that fact that I gave it a new name.

The resource is split into two libraries:

DamageEvent, a damage detection engine.

If you are already using another damage detection system in your map, DamageEvent will interface with it instead of creating its own damage detection triggers to avoid wasting performance. Currently supported damage detection systems are IDDS and LLDD.

DamageEvent:

libraryDamageEventinitializerInitrequiresoptionalDamageModifiers, optionalLightLeaklessDamageDetect, optionalIntuitiveDamageSystem, optionalxedamage//*****************************************************************//* DAMAGE EVENT LIBRARY//*//* written by: Anitarf//* supports: -DamageModifiers//*//* This is a damage detection library designed to compensate for//* the lack of a generic "unit takes damage" event in JASS.//*//* All functions following the Response function interface that//* is defined at the end of the calibration section of this//* library can be used to respond to damage events. Simply add//* such functions to the system's call list with the//* RegisterDamageResponse function.//*//* function RegisterDamageResponse takes Response r returns nothing//*//* DamageEvent fully supports the use of DamageModifiers. As//* long as you have the DamageModifiers library in your map//* DamageEvent will use it to modify the damage before calling//* the response functions.//*//* If the map contains another damage detection library,//* DamageEvent will interface with it instead of creating//* it's own damage event triggers to improve performance.//* Currently supported libraries are LightLeaklessDamageDetect//* and IntuitiveDamageSystem.//*//* DamageEvent is also set up to automatically ignore dummy//* damage events sometimes caused by xedamage when validating//* targets (only works with xedamage 0.7 or higher).//*****************************************************************globals// In wc3, damage events sometimes occur when no real damage is dealt,// for example when some spells are cast that don't really deal damage,// so this system will only consider damage events where damage is// higher than this threshold value.privateconstantrealDAMAGE_THRESHOLD = 0.0// The following calibration options are only used if the system uses// it's own damage detection triggers instead of interfacing with other// damage event engines:// If this boolean is true, the damage detection trigger used by this// system will be periodically destroyed and remade, thus getting rid// of damage detection events for units that have decayed/been removed.privateconstantbooleanREFRESH_TRIGGER = true// Each how many seconds should the trigger be refreshed?privateconstantrealTRIGGER_REFRESH_PERIOD = 300.0endglobalsprivatefunctionCanTakeDamagetakesunitureturnsboolean// You can filter out which units need damage detection events with this function.// For example, dummy casters will never take damage so the system doesn't need to register events for them,// by filtering them out you are reducing the number of handles the game will create, thus increasing performance.//return GetUnitTypeId(u)!='e000' // This is a sample return statement that lets you ignore a specific unit type.returntrueendfunction// This function interface is included in the calibration section// for user reference only and should not be changed in any way.publicfunctioninterfaceResponsetakesunitdamagedUnit, unitdamageSource, realdamagereturnsnothing// END OF CALIBRATION SECTION // ================================================================globalsprivateResponsearrayresponsesprivateintegerresponsesCount = 0endglobalsfunctionRegisterDamageResponsetakesResponserreturnsnothingsetresponses[responsesCount]=rsetresponsesCount=responsesCount+1endfunctionprivatefunctionDamagetakesnothingreturnsnothing// Main damage event function.localunitdamaged=GetTriggerUnit()
localunitdamager=GetEventDamageSource()
localrealdamage=GetEventDamage()
localintegeri = 0loopexitwhennot (damage>DAMAGE_THRESHOLD)
staticifLIBRARY_xedamagethenexitwhenxedamage.isDummyDamageendifstaticifLIBRARY_DamageModifiersthensetdamage=RunDamageModifiers()
endifloopexitwheni>=responsesCountcallresponses[i].execute(damaged, damager, damage)
seti=i+1endloopexitwhentrueendloopsetdamaged=nullsetdamager=nullendfunctionprivatefunctionDamageCtakesnothingreturnsbooleancallDamage() // Used to interface with LLDD.returnfalseendfunction// ================================================================globalsprivategroupgprivateboolexprbprivatebooleanclearprivatetriggercurrentTrgprivatetriggeractioncurrentTrgAprivatetriggeroldTrg = nullprivatetriggeractionoldTrgA = nullendglobalsprivatefunctionTriggerRefreshEnumtakesnothingreturnsnothing// Code "borrowed" from Captain Griffen's GroupRefresh function.// This clears the group of any "shadows" left by removed units.ifclearthencallGroupClear(g)
setclear = falseendifcallGroupAddUnit(g, GetEnumUnit())
// For units that are still in the game, add the event to the new trigger.callTriggerRegisterUnitEvent( currentTrg, GetEnumUnit(), EVENT_UNIT_DAMAGED )
endfunctionprivatefunctionTriggerRefreshtakesnothingreturnsnothing// The old trigger is destroyed with a delay for extra safety.// If you get bugs despite this then turn off trigger refreshing.ifoldTrg!=nullthencallTriggerRemoveAction(oldTrg, oldTrgA)
callDestroyTrigger(oldTrg)
endif// The current trigger is prepared for delayed destruction.callDisableTrigger(currentTrg)
setoldTrg=currentTrgsetoldTrgA=currentTrgA// The current trigger is then replaced with a new trigger.setcurrentTrg = CreateTrigger()
setcurrentTrgA = TriggerAddAction(currentTrg, functionDamage)
setclear = truecallForGroup(g, functionTriggerRefreshEnum)
ifclearthencallGroupClear(g)
endifendfunction// ================================================================privatefunctionDamageRegistertakesnothingreturnsbooleanlocalunitu = GetFilterUnit()
ifCanTakeDamage(u) thencallTriggerRegisterUnitEvent( currentTrg, u, EVENT_UNIT_DAMAGED )
callGroupAddUnit(g, u)
endifsetu = nullreturnfalseendfunctionprivatefunctionInittakesnothingreturnsnothinglocalrectreclocalregionreglocaltriggertstaticifLIBRARY_IntuitiveDamageSystemthen// IDDS initialization codesett=CreateTrigger()
callTriggerAddAction(t, functionDamage)
callTriggerRegisterDamageEvent(t, 0)
elsestaticifLIBRARY_LightLeaklessDamageDetectthen// LLDD initialization codecallAddOnDamageFunc(Condition(functionDamageC))
else// DamageEvent initialization codesetrec = GetWorldBounds()
setreg = CreateRegion()
sett = CreateTrigger()
callRegionAddRect(reg, rec)
callTriggerRegisterEnterRegion(t, reg, Condition(functionDamageRegister))
setcurrentTrg = CreateTrigger()
setcurrentTrgA = TriggerAddAction(currentTrg, functionDamage)
setg = CreateGroup()
callGroupEnumUnitsInRect(g, rec, Condition(functionDamageRegister))
ifREFRESH_TRIGGERthencallTimerStart(CreateTimer(), TRIGGER_REFRESH_PERIOD, true, functionTriggerRefresh)
endifcallRemoveRect(rec)
setrec = nullsetb = nullendifendifendfunctionendlibrary

DamageModifiers, an optional addon that allows you to modify damage.

Two versions of DamageModifiers are provided, one that uses Table and one that uses AutoIndex.
DamageModifiers can use either xepreload or AbilityPreload to preload the ability it uses.

DamageModifiers (Table version):

libraryDamageModifiersinitializerInitrequiresTable, optionalAbilityPreload, optionalxepreload//*****************************************************************//* DAMAGE MODIFIERS LIBRARY (Table version) 1.1//*//* written by: Anitarf//* requires: -Table//* -a damage detection system (DamageEvent recommended)//* optional: -AbilityPreload or xepreload//*//* This is a library that allows you to modify the damage taken//* by units. It is the most robust system of this kind available,//* it can both reduce and increase damage, when increasing//* damage it still awards bounty for kills correctly, it can//* tolerate users dealing additional damage on the damage event//* as well as allow them to get the correct life of the unit on//* this event rather than a value inflated by damage prevention.//*//* IMPLEMENTATION://*//* It is important to note that different damage modifying//* systems can not operate in the same map, so you can not use//* this system if you are already using a different one.//*//* DamageModifiers does not work on its own, but is designed to//* be used in combination with a damage detection system, which//* needs to call the RunDamageModifiers function once whenever a//* damage event occurs. The DamageEvent damage detection system//* is already configured to do this.//*//* function RunDamageModifiers takes nothing returns real//*//* The real value that the RunDamageModifiers function returns//* equals what the damage is after it has been affected by all//* modifiers.//*//* To be able to modify damage in situations where it exceeds//* the max hp of the damaged unit, this system must temporarily//* add a bonus hp ability to the unit. An external Objectmerger//* call is provided in the calibration section that generates//* this ability in a map automatically when the map is saved.//* The ability will also get automatically preloaded to prevent//* first-use lag as long as either the xepreload library or the//* AbilityPreload library is present in the map.//*//* USAGE://*//* Damage modifiers are implemented as structs that extend the//* system's DamageModifier struct. A damage modifier is created//* for a specific unit and can modify both the damage that the//* unit deals and receives (using the onDamageDealt and//* onDamageTaken methods respectively, see the interface defined//* at the end of the calibration section).//*//* Keep in mind that when a struct extends DamageModifier, the//* allocate method of that struct must match the create method of//* DamageModifier://*//* static method create takes unit u, integer priority returns DamageModifier//*//* The unit argument determines on which unit will the modifier//* be applied and the priority argument determines the order in//* which the modifiers will be evaluated. The system always//* evaluates the modifiers on the unit dealing the damage and on//* the unit taking the damage simultaneously, starting with the//* modifiers with the highest priority. If two modifiers share//* the same priority, the one on the unit dealing the damage//* is evaluated first, if the two modifiers are on the same unit//* then the NEW_MODIFIER_ON_TOP calibration constant determines//* which gets evaluated first.//*//* The library also provides the functions GetUnitLife and//* SetUnitLife. When used outside damage detection, these//* functions work the same as the natives GetWidgetLife and//* SetWidgetLife, however when called on a unit that has damage//* stacked on it these functions will return/modify the life the//* unit will have after the damage resolves, rather than its//* current life which will be overwritten when damage modifiers//* finish processing the unit. Again, the functions are://*//* function GetUnitLife takes unit u returns real//* function SetUnitLife takes unit u, real newLife returns nothing//*//* EXAMPLE:/*
// This damage modifier will simply reduce all damage a unit takes by a constant value.
struct armour extends DamageModifier
// All additional struct members are optional:
static integer PRIORITY=0 // Default priority
real power // This lets us give different units differently strong armour.
// create method is optional, if you don't declare one then you must use
// the .allocate parameters (unit, integer) when creating a modifier of this kind.
static method create takes unit u, real power returns armour
// Note the parameters for .allocate, this is because this struct extends
// DamageModifier which asks for these parameters in its create method:
local armour this = armour.allocate(u, armour.PRIORITY)
set this.power = power
return this
endmethod
// This is the method that runs when damage is dealt to the unit with the modifier.
// The damage parameter tells how much damage got to this modifier past any modifiers
// with a higher priority that the unit may have.
// The value that the method returns tells the system by how much to modify the damage,
// a positive return value increases damage while a negative value reduces it.
method onDamageTaken takes unit damageSource, real damage returns real
// This is a simple modifier that just gives a flat damage reduction.
if this.power>damage then
return -damage
endif
return -this.power
endmethod
// There is no onDamageDealt method so this modifier does not affect the damage that the unit deals.
// onDestroy method is optional, in this case we don't need it.
endstruct
*///* VERSION HISTORY://* 1.1 -Fixed a bug that would cause an incorrect total damage to//* be reported back to DamageEvent in some circumstances.//* -GetUnitLife returns 0.0 instead of negative values for//* for units that are about to die from the stacked damage.//* -Narrowed the criteria for applying the survival ability.//* it should now only be applied when it is really needed.//* -Improved the documentation.//*****************************************************************globals// If two modifiers have the same priority, which one should// modify the damage first? If true, the newer modifier will.privateconstantbooleanNEW_MODIFIER_ON_TOP = true// If more damage is dealt to the unit than the unit's max hp, then this// system must temporarily add a bonus hp ability to the unit in order to// facilitate damage prevention.// An objectmerger call is provided below that automatically generates// the ability when you save the map in the NewGen world editor.privateconstantintegerSURVIVAL_ABILITY = 'DMsa'endglobals// This objectmerger call only needs to run once to generate the ability in a map,// just save the map to run it, then close the map and re-open it and the ability// should be there, after that you can disable the objectmerget call to speed up// the map saving process in the future.// (To disable the call, delete the "!" so it turns from a command into a comment.)//! external ObjectMerger w3a AIl1 DMsa anam "LifeBonus" ansf "(DamageModifiers)" Ilif 1 100000 aite 0// This interface is included in the calibration section// for user reference only and should not be changed in any way.// It is private because your modifiers shouldn't extend it directly,// but should extend the DamageModifer struct instead.privateinterfaceDamageModiferTemplate// Returned real determines by how much the damage the unit deals should be modified.methodonDamageDealttakesunitdamagedUnit, realdamagereturnsrealdefaults0.0// Returned real determines by how much the damage the unit is dealt should be modified.methodonDamageTakentakesunitdamageSource, realdamagereturnsrealdefaults0.0endinterface// END OF CALIBRATION SECTION // ================================================================privatekeywordEvaluate// DAMAGE MODIFIER STRUCT// To create your own damage modifiers, extend this struct.structDamageModifierextendsDamageModiferTemplateprivateunittargetunitintegerpriorityprivateDamageModifiernext=0privatestaticHandleTablefirstprivatemethodlistInserttakesnothingreturnsnothinglocalDamageModifierdm=DamageModifier(DamageModifier.first[.targetunit])
ifdm==0ordm.priority<.priorityor (NEW_MODIFIER_ON_TOPanddm.priority==.priority) thenset.next=DamageModifier(DamageModifier.first[.targetunit])
setDamageModifier.first[.targetunit]=integer(this)
elseloopexitwhendm.next == 0ordm.next.priority<.priorityor (NEW_MODIFIER_ON_TOPanddm.next.priority==.priority)
setdm=dm.nextendloopset.next=dm.nextsetdm.next=thisendifendmethodprivatemethodlistRemovetakesnothingreturnsnothinglocalDamageModifierdm=DamageModifier(DamageModifier.first[.targetunit])
ifdm==thisthensetDamageModifier.first[.targetunit]=integer(.next)
elseloopexitwhendm.next==thissetdm=dm.nextendloopsetdm.next=.nextendifendmethod// ================================================================staticmethodcreatetakesunitu, integerpriorityreturnsDamageModifierlocalDamageModifierthisifu==nullthenreturn0endifsetthis=DamageModifier.allocate()
set.targetunit=uset.priority = prioritycall.listInsert()
returnthisendmethodmethodonDestroytakesnothingreturnsnothingcall.listRemove()
ifDamageModifier.first[.targetunit]==0thencallDamageModifier.first.flush(.targetunit)
endifendmethod// ================================================================staticmethodonInittakesnothingreturnsnothingsetDamageModifier.first=HandleTable.create()
endmethodstaticmethodEvaluatetakesunitdamaged, unitdamager, realdamagereturnsreal// Loops through all the modifiers involved in a damage event and// returns the total amount by which the damage must be modified.// Positive values meaning a damage increase and negative a damage decrease.localrealmodifier=0.0localDamageModifierthis=DamageModifier(DamageModifier.first[damager])
localDamageModifierthat=DamageModifier(DamageModifier.first[damaged])
loopexitwhenthis==0andthat==0ifthis!=0and (that==0orthis.priority>=that.priority) thensetmodifier=modifier+this.onDamageDealt(damaged, damage+modifier)
setthis=this.nextelseifthat !=0thensetmodifier=modifier+that.onDamageTaken(damager, damage+modifier)
setthat=that.nextendifendloopreturnmodifierendmethodendstruct// ================================================================// DAMAGE STRUCT// This is an internal struct created whenever a unit takes damage and// destroyed after a 0.0 second timer to allow the damage to be dealt.// If multiple damage events occur on that unit in that time, a single// Damage struct will handle all of them.privatestructDamageuniturealhp=0.0// Stores the correct hp of the unit. The actual hp may be higher// if not all damage that was stacked on the unit has resolved yet,// however once the damage resolves the correct hp and actual hp// values should match again.// There are two exceptions when this does not happen:// - Actual hp could not be set correctly because that would kill the unit.// (in case of damage modifiers that increase damage)// - Actual hp could not be set correctly because of unit's max life limit.// (in case of damage modifiers that reduce damage)realtemp=0.0// In the case of the two exceptions mentioned above, this stores by how// much the life of the unit could not be increased or reduced, so this// is essentially the difference between the unit's actual and correct hp// (after the damage stacked on the unit is resolved, of course).// If another damage event should occur before the first one resolves,// this value will be added to the new damage modifier, ensuring that any// life modifications that couldn't be done previously are not forgotten.// This way, if one damage event has a positive modifier and an event that// occurs in response has a negative modifier or vice versa, the two will// properly cancel each other even if the first one of them couldn't be// fully applied. Likewise, if the unit is given the survival ability, any// damage reduction that couldn't be aplied earlier due to max hp limit// will get applied properly once the unit's max hp is increased.privateDamagenextprivatestaticDamagefirst=0// A list of all active Damage structs.privatestatictimerdelay=CreateTimer()
// The timer used to wait for the damage to be dealt.privatestaticmethodgettakesunitureturnsDamage// In most cases, only one or very few units will be damaged// at the same time, so a linear O(n) search is acceptable.localDamagethis=Damage.firstloopexitwhenthis==0or.u==usetthis=.nextendloopreturnthisendmethodprivatestaticmethodcreatetakesunitureturnsDamage// The only time a Damage is created is when it doesn't yet exist for the unit.localDamagethis=Damage.allocate()
set.u = uset.next = Damage.firstsetDamage.first=thisreturnthisendmethodmethodonDestroytakesnothingreturnsnothing// The only time a Damage is destroyed is when it is first, so this works.setDamage.first=.nextset.u = nullendmethod// ================================================================privatestaticmethodendtakesnothingreturnsnothing// This method is called with a very slight delay, which allows it to run// after the life of the damaged unit has already been reduced by the damage.loopexitwhenDamage.first==0callUnitRemoveAbility(Damage.first.u, SURVIVAL_ABILITY)
callSetWidgetLife(Damage.first.u, Damage.first.hp)
callDamage.first.destroy()
endloopendmethodstaticmethodRunModifierstakesnothingreturnsreallocalunitdamaged = GetTriggerUnit()
localunitdamager = GetEventDamageSource()
localrealdamage = GetEventDamage()
localrealmodifier = 0.0localreallife = GetWidgetLife(damaged)
localrealmaxlife = GetUnitState(damaged,UNIT_STATE_MAX_LIFE)
localrealstackeddamagelocalDamagedifdamaged==nullthen// Method not called from a damage event, terminate.setdamager=nullsetdamaged=nullreturn0.0endif// Calculate by how much to modify the damage.setmodifier=DamageModifier.Evaluate(damaged, damager, damage)
// Get the unit's Damage struct.setd=Damage.get(damaged)
ifd==0then// First damage, create a new damage struct.setd=Damage.create(damaged)
setd.hp=lifecallTimerStart(Damage.delay, 0.0, false, functionDamage.end)
endif// Calculate the unresolved real damage stacked on the unit.setstackeddamage=((life-d.temp)-d.hp)+damage// Calculate the modified damage.setdamage=damage+modifier// Calculate the correct hp of the unit.setd.hp=d.hp-damage// Check if we need to add the survival ability.ifd.hp>0.405andmaxlife-stackeddamage<=0.405thencallUnitAddAbility(damaged, SURVIVAL_ABILITY)
setmaxlife = GetUnitState(damaged,UNIT_STATE_MAX_LIFE)
endif// If the unit's life couldn't be reduced or increased as much as we wanted// on a previous damage event, it's time we compensate for that now.setmodifier=modifier+d.tempsetd.temp=0.0// Modify unit life to make it appear as if damage was modified.iflife-modifier>0.405andlife-modifier<=maxlifethen// Set the unit's life so that after the damage is dealt, it will be correct.callSetWidgetLife(damaged, life-modifier)
elseiflife-modifier>maxlifethen// The unit's maxlife is preventing us from increasing life as needed,// we need to remember that in the case the survival ability is ever added// to the unit on another damage event before this damage resolves.setd.temp=modifier-(life-maxlife)
callSetWidgetLife(damaged, maxlife)
else// We have a damage modifier value that would kill the unit, so we// let the actual damage kill it instead, thus awarding proper bounty.// We need to remember that we failed to reduce the life as needed in case// the unit is healed in response to this damage.setd.temp=modifier-(life-0.406)
callSetWidgetLife(damaged, 0.406)
endifsetdamaged=nullsetdamager=nullreturndamageendmethod// ================================================================staticmethodgetLifetakesunitureturnsreallocalDamaged=Damage.get(u)
ifd==0thenreturnGetWidgetLife(u)
endififd.hp<=0.405thenreturn0.0endifreturnd.hpendmethodstaticmethodsetLifetakesunitu, realnewLifereturnsnothinglocalDamaged=Damage.get(u)
localrealmodifierlocalreallifelocalrealmaxlifelocalrealstackeddamageifd==0then// Unit not taking damage, proceed with setting the unit's life.callSetWidgetLife(u, newLife)
else// Calculate by how much the unit's life will change.setmodifier=newLife-d.hpifmodifier>0andd.hp<=0.405then// Increasing life in a situation where the damage is about to kill the unit,// so we must intervene and increase the unit's actual life as well.setlife=GetWidgetLife(u)
setmaxlife = GetUnitState(u,UNIT_STATE_MAX_LIFE)
// Calculate the unresolved damage stacked on the unit.setstackeddamage=(life-d.temp)-d.hp// Check if we need to add the survival ability.ifd.hp>0.405andmaxlife-stackeddamage<=0.405thencallUnitAddAbility(u, SURVIVAL_ABILITY)
setmaxlife = GetUnitState(u,UNIT_STATE_MAX_LIFE)
endifsetd.hp=newLifeifmaxlife-life>modifierthencallSetWidgetLife(u, life+modifier)
elsecallSetWidgetLife(u, maxlife)
setd.temp=d.temp-modifier+maxlife-lifeendifelse// Nothing to worry about, the rest of the code will handle everything.setd.hp=newLifesetd.temp=d.temp-modifierendifendifendmethodendstruct// ================================================================privatefunctionInittakesnothingreturnsnothingstaticifLIBRARY_xepreloadthencallXE_PreloadAbility(SURVIVAL_ABILITY)
elsestaticifLIBRARY_AbilityPreloadthencallAbilityPreload(SURVIVAL_ABILITY)
endifendifendfunction// ================================================================// To make this system work, call this function once whenever a damage event occurs.// The DamageEvent library is already designed to do this.functionRunDamageModifierstakesnothingreturnsrealreturnDamage.RunModifiers()
endfunction// These functions allow you to get/set the proper life of a unit even when it's taking damage.functionGetUnitLifetakesunitureturnsrealreturnDamage.getLife(u)
endfunctionfunctionSetUnitLifetakesunitu, realnewLifereturnsnothingcallDamage.setLife(u, newLife)
endfunctionendlibrary

DamageModifiers (AutoIndex version):

libraryDamageModifiersinitializerInitrequiresAutoIndex, optionalAbilityPreload, optionalxepreload//*****************************************************************//* DAMAGE MODIFIERS LIBRARY (AutoIndex version) 1.1//*//* written by: Anitarf//* requires: -AutoIndex//* -a damage detection system (DamageEvent recommended)//* optional: -AbilityPreload or xepreload//*//* This is a library that allows you to modify the damage taken//* by units. It is the most robust system of this kind available,//* it can both reduce and increase damage, when increasing//* damage it still awards bounty for kills correctly, it can//* tolerate users dealing additional damage on the damage event//* as well as allow them to get the correct life of the unit on//* this event rather than a value inflated by damage prevention.//*//* IMPLEMENTATION://*//* It is important to note that different damage modifying//* systems can not operate in the same map, so you can not use//* this system if you are already using a different one.//*//* DamageModifiers does not work on its own, but is designed to//* be used in combination with a damage detection system, which//* needs to call the RunDamageModifiers function once whenever a//* damage event occurs. The DamageEvent damage detection system//* is already configured to do this.//*//* function RunDamageModifiers takes nothing returns real//*//* The real value that the RunDamageModifiers function returns//* equals what the damage is after it has been affected by all//* modifiers.//*//* To be able to modify damage in situations where it exceeds//* the max hp of the damaged unit, this system must temporarily//* add a bonus hp ability to the unit. An external Objectmerger//* call is provided in the calibration section that generates//* this ability in a map automatically when the map is saved.//* The ability will also get automatically preloaded to prevent//* first-use lag as long as either the xepreload library or the//* AbilityPreload library is present in the map.//*//* In this AutoIndex version of this library, damage modifiers//* can not be added to units that are not indexed, also, if such//* units take damage, damage modifiers will not be evaluated.//*//* USAGE://*//* Damage modifiers are implemented as structs that extend the//* system's DamageModifier struct. A damage modifier is created//* for a specific unit and can modify both the damage that the//* unit deals and receives (using the onDamageDealt and//* onDamageTaken methods respectively, see the interface defined//* at the end of the calibration section).//*//* Keep in mind that when a struct extends DamageModifier, the//* allocate method of that struct must match the create method of//* DamageModifier://*//* static method create takes unit u, integer priority returns DamageModifier//*//* The unit argument determines on which unit will the modifier//* be applied and the priority argument determines the order in//* which the modifiers will be evaluated. The system always//* evaluates the modifiers on the unit dealing the damage and on//* the unit taking the damage simultaneously, starting with the//* modifiers with the highest priority. If two modifiers share//* the same priority, the one on the unit dealing the damage//* is evaluated first, if the two modifiers are on the same unit//* then the NEW_MODIFIER_ON_TOP calibration constant determines//* which gets evaluated first.//*//* The library also provides the functions GetUnitLife and//* SetUnitLife. When used outside damage detection, these//* functions work the same as the natives GetWidgetLife and//* SetWidgetLife, however when called on a unit that has damage//* stacked on it these functions will return/modify the life the//* unit will have after the damage resolves, rather than its//* current life which will be overwritten when damage modifiers//* finish processing the unit. Again, the functions are://*//* function GetUnitLife takes unit u returns real//* function SetUnitLife takes unit u, real newLife returns nothing//*//* EXAMPLE:/*
// This damage modifier will simply reduce all damage a unit takes by a constant value.
struct armour extends DamageModifier
// All additional struct members are optional:
static integer PRIORITY=0 // Default priority
real power // This lets us give different units differently strong armour.
// create method is optional, if you don't declare one then you must use
// the .allocate parameters (unit, integer) when creating a modifier of this kind.
static method create takes unit u, real power returns armour
// Note the parameters for .allocate, this is because this struct extends
// DamageModifier which asks for these parameters in its create method:
local armour this = armour.allocate(u, armour.PRIORITY)
set this.power = power
return this
endmethod
// This is the method that runs when damage is dealt to the unit with the modifier.
// The damage parameter tells how much damage got to this modifier past any modifiers
// with a higher priority that the unit may have.
// The value that the method returns tells the system by how much to modify the damage,
// a positive return value increases damage while a negative value reduces it.
method onDamageTaken takes unit damageSource, real damage returns real
// This is a simple modifier that just gives a flat damage reduction.
if this.power>damage then
return -damage
endif
return -this.power
endmethod
// There is no onDamageDealt method so this modifier does not affect the damage that the unit deals.
// onDestroy method is optional, in this case we don't need it.
endstruct
*///* VERSION HISTORY://* 1.1 -Fixed a bug that would cause an incorrect total damage to//* be reported back to DamageEvent in some circumstances.//* -GetUnitLife returns 0.0 instead of negative values for//* for units that are about to die from the stacked damage.//* -Narrowed the criteria for applying the survival ability,//* it should now only be applied when it is really needed.//* -Improved the documentation.//*****************************************************************globals// If two modifiers have the same priority, which one should// modify the damage first? If true, the newer modifier will.privateconstantbooleanNEW_MODIFIER_ON_TOP = true// If more damage is dealt to the unit than the unit's max hp, then this// system must temporarily add a bonus hp ability to the unit in order to// facilitate damage prevention.// An objectmerger call is provided below that automatically generates// the ability when you save the map in the NewGen world editor.privateconstantintegerSURVIVAL_ABILITY = 'DMsa'endglobals// This objectmerger call only needs to run once to generate the ability in a map,// just save the map to run it, then close the map and re-open it and the ability// should be there, after that you can disable the objectmerget call to speed up// the map saving process in the future.// (To disable the call, delete the "!" so it turns from a command into a comment.)//! external ObjectMerger w3a AIl1 DMsa anam "LifeBonus" ansf "(DamageModifiers)" Ilif 1 100000 aite 0// This interface is included in the calibration section// for user reference only and should not be changed in any way.// It is private because your modifiers shouldn't extend it directly,// but should extend the DamageModifer struct instead.privateinterfaceDamageModiferTemplate// Returned real determines by how much the damage the unit deals should be modified.methodonDamageDealttakesunitdamagedUnit, realdamagereturnsrealdefaults0.0// Returned real determines by how much the damage the unit is dealt should be modified.methodonDamageTakentakesunitdamageSource, realdamagereturnsrealdefaults0.0endinterface// END OF CALIBRATION SECTION // ================================================================privatekeywordEvaluate// DAMAGE MODIFIER STRUCT// To create your own damage modifiers, extend this struct.structDamageModifierextendsDamageModiferTemplateprivateunittargetunitprivateintegerpriorityprivateDamageModifiernext=0privatestaticDamageModifierarrayfirstprivatemethodlistInserttakesnothingreturnsnothinglocalDamageModifierdm=DamageModifier.first[GetUnitId(.targetunit)]
ifdm==0ordm.priority<.priorityor (NEW_MODIFIER_ON_TOPanddm.priority==.priority) thenset.next=DamageModifier.first[GetUnitId(.targetunit)]
setDamageModifier.first[GetUnitId(.targetunit)]=thiselseloopexitwhendm.next == 0ordm.next.priority<.priorityor (NEW_MODIFIER_ON_TOPanddm.next.priority==.priority)
setdm=dm.nextendloopset.next=dm.nextsetdm.next=thisendifendmethodprivatemethodlistRemovetakesnothingreturnsnothinglocalDamageModifierdm=DamageModifier.first[GetUnitId(.targetunit)]
ifdm==thisthensetDamageModifier.first[GetUnitId(.targetunit)]=.nextelseloopexitwhendm.next==thissetdm=dm.nextendloopsetdm.next=.nextendifendmethod// ================================================================staticmethodcreatetakesunitu, integerpriorityreturnsDamageModifierlocalDamageModifierthisifu==nullornot(IsUnitIndexed(u)) thenreturn0endifsetthis=DamageModifier.allocate()
set.targetunit=uset.priority = prioritycall.listInsert()
returnthisendmethodmethodonDestroytakesnothingreturnsnothingcall.listRemove()
endmethod// ================================================================staticmethodEvaluatetakesunitdamaged, unitdamager, realdamagereturnsreal// Loops through all the modifiers involved in a damage event and// returns the total amount by which the damage must be modified.// Positive values meaning a damage increase and negative a damage decrease.localrealmodifier=0.0localDamageModifierthis=0localDamageModifierthat=DamageModifier(DamageModifier.first[GetUnitId(damaged)])
ifIsUnitIndexed(damager) thensetthis=DamageModifier(DamageModifier.first[GetUnitId(damager)])
endifloopexitwhenthis==0andthat==0ifthis!=0and (that==0orthis.priority>=that.priority) thensetmodifier=modifier+this.onDamageDealt(damaged, damage+modifier)
setthis=this.nextelseifthat !=0thensetmodifier=modifier+that.onDamageTaken(damager, damage+modifier)
setthat=that.nextendifendloopreturnmodifierendmethodendstruct// ================================================================// DAMAGE STRUCT// This is an internal struct created whenever a unit takes damage and// destroyed after a 0.0 second timer to allow the damage to be dealt.// If multiple damage events occur on that unit in that time, a single// Damage struct will handle all of them.privatestructDamageuniturealhp=0.0// Stores the correct hp of the unit. The actual hp may be higher// if not all damage that was stacked on the unit has resolved yet,// however once the damage resolves the correct hp and actual hp// values should match again.// There are two exceptions when this does not happen:// - Actual hp could not be set correctly because that would kill the unit.// (in case of damage modifiers that increase damage)// - Actual hp could not be set correctly because of unit's max life limit.// (in case of damage modifiers that reduce damage)realtemp=0.0// In the case of the two exceptions mentioned above, this stores by how// much the life of the unit could not be increased or reduced, so this// is essentially the difference between the unit's actual and correct hp// (after the damage stacked on the unit is resolved, of course).// If another damage event should occur before the first one resolves,// this value will be added to the new damage modifier, ensuring that any// life modifications that couldn't be done previously are not forgotten.// This way, if one damage event has a positive modifier and an event that// occurs in response has a negative modifier or vice versa, the two will// properly cancel each other even if the first one of them couldn't be// fully applied. Likewise, if the unit is given the survival ability, any// damage reduction that couldn't be aplied earlier due to max hp limit// will get applied properly once the unit's max hp is increased.privateDamagenextprivatestaticDamagefirst=0// A list of all active Damage structs.privatestatictimerdelay=CreateTimer()
// The timer used to wait for the damage to be dealt.privatestaticDamagearrayunitlist// Each struct is associated with a specific unit.privatestaticmethodgettakesunitureturnsDamagereturnDamage.unitlist[GetUnitId(u)]
endmethodprivatestaticmethodcreatetakesunitureturnsDamage// The only time a Damage is created is when it doesn't yet exist for the unit.localDamagethis=Damage.allocate()
set.u = uset.next = Damage.firstsetDamage.first=thissetDamage.unitlist[GetUnitId(.u)]=thisreturnthisendmethodmethodonDestroytakesnothingreturnsnothing// The only time a Damage is destroyed is when it is first, so this works.setDamage.first=.nextsetDamage.unitlist[GetUnitId(.u)]=0set.u = nullendmethod// ================================================================privatestaticmethodendtakesnothingreturnsnothing// This method is called with a very slight delay, which allows it to run// after the life of the damaged unit has already been reduced by the damage.loopexitwhenDamage.first==0callUnitRemoveAbility(Damage.first.u, SURVIVAL_ABILITY)
callSetWidgetLife(Damage.first.u, Damage.first.hp)
callDamage.first.destroy()
endloopendmethodstaticmethodRunModifierstakesnothingreturnsreallocalunitdamaged = GetTriggerUnit()
localunitdamager = GetEventDamageSource()
localrealdamage = GetEventDamage()
localrealmodifier = 0.0localreallife = GetWidgetLife(damaged)
localrealmaxlife = GetUnitState(damaged,UNIT_STATE_MAX_LIFE)
localrealstackeddamagelocalDamagedifdamaged==nullornot(IsUnitIndexed(damaged)) then// Method not called from a damage event or damage event on an unindexed unit, terminate.setdamager=nullsetdamaged=nullreturn0.0endif// Calculate by how much to modify the damage.setmodifier=DamageModifier.Evaluate(damaged, damager, damage)
// Get the unit's Damage struct.setd=Damage.get(damaged)
ifd==0then// First damage, create a new damage struct.setd=Damage.create(damaged)
setd.hp=lifecallTimerStart(Damage.delay, 0.0, false, functionDamage.end)
endif// Calculate the unresolved real damage stacked on the unit.setstackeddamage=((life-d.temp)-d.hp)+damage// Calculate the modified damage.setdamage=damage+modifier// Calculate the correct hp of the unit.setd.hp=d.hp-damage// Check if we need to add the survival ability.ifd.hp>0.405andmaxlife-stackeddamage<=0.405thencallUnitAddAbility(damaged, SURVIVAL_ABILITY)
setmaxlife = GetUnitState(damaged,UNIT_STATE_MAX_LIFE)
endif// If the unit's life couldn't be reduced or increased as much as we wanted// on a previous damage event, it's time we compensate for that now.setmodifier=modifier+d.tempsetd.temp=0.0// Modify unit life to make it appear as if damage was modified.iflife-modifier>0.405andlife-modifier<=maxlifethen// Set the unit's life so that after the damage is dealt, it will be correct.callSetWidgetLife(damaged, life-modifier)
elseiflife-modifier>maxlifethen// The unit's maxlife is preventing us from increasing life as needed,// we need to remember that in the case the survival ability is ever added// to the unit on another damage event before this damage resolves.setd.temp=modifier-(life-maxlife)
callSetWidgetLife(damaged, maxlife)
else// We have a damage modifier value that would kill the unit, so we// let the actual damage kill it instead, thus awarding proper bounty.// We need to remember that we failed to reduce the life as needed in case// the unit is healed in response to this damage.setd.temp=modifier-(life-0.406)
callSetWidgetLife(damaged, 0.406)
endifsetdamaged=nullsetdamager=nullreturndamageendmethod// ================================================================staticmethodgetLifetakesunitureturnsreallocalDamaged=0ifIsUnitIndexed(u) thensetd=Damage.get(u)
endififd==0thenreturnGetWidgetLife(u)
endififd.hp<=0.405thenreturn0.0endifreturnd.hpendmethodstaticmethodsetLifetakesunitu, realnewLifereturnsnothinglocalDamaged=0localrealmodifierlocalreallifelocalrealmaxlifelocalrealstackeddamageifIsUnitIndexed(u) thensetd=Damage.get(u)
endififd==0then// Unit not taking damage, proceed with setting the unit's life.callSetWidgetLife(u, newLife)
else// Calculate by how much the unit's life will change.setmodifier=newLife-d.hpifmodifier>0andd.hp<=0.405then// Increasing life in a situation where the damage is about to kill the unit,// so we must intervene and increase the unit's actual life as well.setlife=GetWidgetLife(u)
setmaxlife = GetUnitState(u,UNIT_STATE_MAX_LIFE)
// Calculate the unresolved damage stacked on the unit.setstackeddamage=(life-d.temp)-d.hp// Check if we need to add the survival ability.ifd.hp>0.405andmaxlife-stackeddamage<=0.405thencallUnitAddAbility(u, SURVIVAL_ABILITY)
setmaxlife = GetUnitState(u,UNIT_STATE_MAX_LIFE)
endifsetd.hp=newLifeifmaxlife-life>modifierthencallSetWidgetLife(u, life+modifier)
elsecallSetWidgetLife(u, maxlife)
setd.temp=d.temp-modifier+maxlife-lifeendifelse// Nothing to worry about, the rest of the code will handle everything.setd.hp=newLifesetd.temp=d.temp-modifierendifendifendmethodendstruct// ================================================================privatefunctionInittakesnothingreturnsnothingstaticifLIBRARY_xepreloadthencallXE_PreloadAbility(SURVIVAL_ABILITY)
elsestaticifLIBRARY_AbilityPreloadthencallAbilityPreload(SURVIVAL_ABILITY)
endifendifendfunction// ================================================================// To make this system work, call this function once whenever a damage event occurs.// The DamageEvent library is already designed to do this.functionRunDamageModifierstakesnothingreturnsrealreturnDamage.RunModifiers()
endfunction// These functions allow you to get/set the proper life of a unit even when it's taking damage.functionGetUnitLifetakesunitureturnsrealreturnDamage.getLife(u)
endfunctionfunctionSetUnitLifetakesunitu, realnewLifereturnsnothingcallDamage.setLife(u, newLife)
endfunctionendlibrary

now that there are static ifs and optional library requirements:
why don't you merge the Table and the AutoIndex version into one library - that would make later switching much easier (and they don't seem to be that different from each other anyway)

Well, optional requirements are just that, optional. In this case, however, the requirement isn't optional: either Table or AutoIndex, one of those requirements must be there. Furthermore, I need to declare globals depending on which library is used and I'm not sure I can do that in static ifs.

I suppose I could use static ifs for alternate versions of DamageEvent, though.

//* Damage modifiers are implemented as structs that extend the//* system's DamageModifier struct. A damage modifier is created//* for a specific unit and can modify both the damage that the//* unit deals and receives (using the damage and damaged methods//* respectively, see the interface defined at the end of the//* calibration section).

Shouldn't it be UNIT_TYPE specific?

Further on, there is no example code.
No matter how good your documentation is, an example code is always on top when it comes to user friendly.

// In wc3, damage events sometimes occur when no real damage is dealt,// for example when some spells are cast that don't really deal damage,// also some systems use very small damages to test for valid targets,// so this system will only consider damage events where damage is// higher than this threashold value.privateconstantrealDAMAGE_THRESHOLD = 0.05

Damage event bigger then DAMAGE_THRESHOLD AND smaller then 0 should be considered.

I would rather, as a user, have control at the unit level than at the unittype level.

You got me wrong. You should be able to handle it on unit level too, but maybe the user doesn't want to just set all the data for every single unit, so you can add a small (optional) addon (module) to store stuff in tables and just set the unit data on entering the playable map area to the data you saved to be basic for any unit of that type?

Quote:

No DDS supports negative damage, nor should they. If you want to heal a unit, negative damage is not the way to do it.

Is there any other way as doing an triggered heal that has a damagetype/weapontype? I for example use it (native UnitDamageTarget) to heal units too. That allows me to even block heals from units.

to Anitarf:

Quote:

I wouldn't. How am I supposed to make a buff that modifies incoming damage if I can't apply it to a specific unit?

Have you tested that? When I tried that with a negative value, I got a damage event with a total of 0 damage. Unless you can actually produce a negative damage event in a test map, shut up.

Quote:

If you don't care about users to get to know to your system who need help in your stuff, how can you submit it then? Its a insolence to provide such advanced stuff without further help.

Again, read the documentation. It clearly states that that is a compatibility library. I don't need to provide support for that, because noone is supposed to keep using it, it is just meant to provide backwards compatibility to old resources. If you really care so much about it, go read the ADamage documentation.