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

Damage Interface [vJASS][LUA]

Damage Interface v2.4 [Custom Evasion][Critical Strike][Spell Power][Life Steal][Spell Vamp]

Intro
Damage Interface provides an easy way to register, detect, and modify custom on damage events, like Critical Strikes and Evasion, as well as introduce new functionalities like Spell Power and Spell Vamp and simulate functionalities like Life Steal.
You will also have access to quick specific damage types registration, like On Attack Damage or On Spell Damage events.
How it Works?
By using the new damage natives that for now are only accessible to Jassers and Lunatics (see what i did there), I created custom events for Critical Strike and Evasion and provided a way to register functions to be executed when these custom events fire. Of course, given the circumstances these custom events will not fire for normal critical strikes and evasion in the game, so if you want to detect a critical strike for example, you will have to give a chance and a multiplier to the desired unit using the public API of each system.
Apart from Damage Interface library, all other libraries are modular and do not depend on each other.
Warning* For the Custom Evasion System, please be aware that a few hard coded functionalities in the game like bash, cleave, critical strikes, orb effects will still trigger damage events, so in order to make a full simulation of an Evasion event all of those functionalities would have to be custom made as well. All that being said, this system by itself offers you the ability to create all of those custom system the way you want.
How To Import?
Simply copy the Damage Interface folder from the test map into your map. You also will need a Unit Indexer. In the test map I used the Unit Indexer created by @Bribe so credits to him, but any unit indexer will do the required job.
Requirements
Damage Interface requires patch 1.31+ and a Unit Indexer.

vJASS:
/* -------------------------------------------------------------------------- */
/*                              Damage Interface                              */
/* -------------------------------------------------------------------------- */
function RegisterAttackDamageEvent takes code c returns nothing
    -> Code will run when a unit attacks another.

function RegisterSpellDamageEvent takes code c returns nothing
    -> Code will run when a unit takes spell damage.

function RegisterDamageEvent takes attacktype attack, damagetype damage, code c returns nothing
    -> Code will run when the attacktype and damagetype of the
    -> damage event match the parameters.

function RegisterAnyDamageEvent takes code c returns nothing
    -> Code will run when any damage is dealt.

function RegisterAttackDamagingEvent takes code c returns nothing
    -> Code will run when a unit attacks another
    -> Before the mitigation.

function RegisterSpellDamagingEvent takes code c returns nothing
    -> Code will run when a unit takes spell damage
    -> Before the mitigation.

function RegisterDamagingEvent takes attacktype attack, damagetype damage, code c returns nothing
     -> Code will run when the attacktype and damagetype of the
     -> damage event match the parameters. Before the mitigation.

function RegisterAnyDamagingEvent takes code c returns nothing
    -> Code will run when any damage is dealt
    -> Before the mitigation.

/* -------------------------------------------------------------------------- */
/*                                   Evasion                                  */
/* -------------------------------------------------------------------------- */
function RegisterEvasionEvent takes code c returns nothing
    -> YourFunction will run when a unit evades an attack.

function GetMissingUnit takes nothing returns unit
    -> Returns the unit missing the attack

function GetEvadingUnit takes nothing returns unit
    -> Returns the unit evading the attack

function GetEvadedDamage takes nothing returns real
    -> Returns the amount of evaded damage

function GetUnitEvasionChance takes unit u returns real
    -> Returns this system amount of evasion chance given to a unit

function GetUnitMissChance takes unit u returns real
    -> Returns this system amount of miss chance given to a unit

function SetUnitEvasionChance takes unit u, real chance returns nothing
    -> Sets unit evasion chance to specified amount

function SetUnitMissChance takes unit u, real chance returns nothing
    -> Sets unit miss chance to specified amount

function UnitAddEvasionChance takes unit u, real chance returns nothing
    -> Add to a unit Evasion chance the specified amount

function UnitAddMissChance takes unit u, real chance returns nothing
    -> Add to a unit Miss chance the specified amount

function MakeUnitNeverMiss takes unit u, boolean flag returns nothing
    -> Will make a unit never miss attacks no matter the evasion chance of the attacked unit

function DoUnitNeverMiss takes unit u returns boolean
    -> Returns true if the unit will never miss an attack

/* -------------------------------------------------------------------------- */
/*                                  Critical                                  */
/* -------------------------------------------------------------------------- */
function RegisterCriticalStrikeEvent takes code c returns nothing
    -> YourFunction will run when a unit hits a critical strike.

function GetCriticalSource takes nothing returns unit
    -> Returns the unit hitting a critical strike.

function GetCriticalTarget takes nothing returns unit
    -> Returns the unit being hit by a critical strike.

function GetCriticalDamage takes nothing returns real
    -> Returns the critical strike damage amount.

function GetUnitCriticalChance takes unit u returns real
    -> Returns the chance to hit a critical strike to specified unit.

function GetUnitCriticalMultiplier takes unit u returns real
    -> Returns the chance to hit a critical strike to specified unit.

function SetUnitCriticalChance takes unit u, real value returns nothing
    -> Set's the unit chance to hit a critical strike to specified value.
    -> 15.0 = 15%

function SetUnitCriticalMultiplier takes unit u, real value returns nothing
    -> Set's the unit multiplier of damage when hitting a critical to value
    -> 1.0 = increases the multiplier by 1. all units have a multiplier of 1.0
    -> by default, so by adding 1.0, for example, the critical damage will be
    -> 2x the normal damage

function SetCriticalEventDamage takes real newValue returns nothing
    -> Modify the critical damage dealt to the specified value.

function UnitAddCriticalStrike takes unit u, real chance, real multiplier returns nothing
    -> Adds the specified values of chance and multiplier to a unit

/* -------------------------------------------------------------------------- */
/*                                 Spell Power                                */
/* -------------------------------------------------------------------------- */
function GetUnitSpellPowerFlat takes unit u returns real
    -> Returns the Flat bonus of spell power of a unit

function GetUnitSpellPowerPercent takes unit u returns real
    -> Returns the Percent bonus of spell power of a unit

function SetUnitSpellPowerFlat takes unit u, real value returns nothing
    -> Set's the Flat amount of Spell Power of a unit

function SetUnitSpellPowerPercent takes unit u, real value returns nothing
    -> Set's the Flat amount of Spell Power of a unit

function UnitAddSpellPowerFlat takes unit u, real amount returns nothing
    -> Add to the Flat amount of Spell Power of a unit

function UnitAddSpellPowerPercent takes unit u, real amount returns nothing
    -> Add to the Percent amount of Spell Power of a unit

function GetSpellDamage takes real amount, unit u returns real
    -> Returns the amount of spell damage that would be dealt given an initial damage

/* -------------------------------------------------------------------------- */
/*                                 Life Steal                                 */
/* -------------------------------------------------------------------------- */
function SetUnitLifeSteal takes unit u, real amount returns nothing
    -> Set the Life Steal amount for a unit

function GetUnitLifeSteal takes unit u returns real
    -> Returns the Life Steal amount of a unit

function UnitAddLifeSteal takes unit u, real amount returns nothing
    -> Add to the Life Steal amount of a unit the given amount

/* -------------------------------------------------------------------------- */
/*                                 Spell Vamp                                 */
/* -------------------------------------------------------------------------- */
function SetUnitSpellVamp takes unit u, real amount returns nothing
    -> Set the Spell Vamp amount for a unit

function GetUnitSpellVamp takes unit u returns real
    -> Returns the Spell Vamp amount of a unit

function UnitAddSpellVamp takes unit u, real amount returns nothing
    -> Add to the Spell Vamp amount of a unit the given amount

/* -------------------------------------------------------------------------- */
/*                           Damage Interface Utils                           */
/* -------------------------------------------------------------------------- */
function UnitAddEvasionChanceTimed takes unit u, real amount, real duration returns nothing
    -> Add to a unit Evasion chance the specified amount for a given period

function UnitAddMissChanceTimed takes unit u, real amount, real duration returns nothing
    -> Add to a unit Miss chance the specified amount for a given period

function UnitAddCriticalStrikeTimed takes unit u, real chance, real multiplier, real duration returns nothing
    -> Adds the specified values of chance and multiplier to a unit for a given period

function UnitAddCriticalChanceTimed takes unit u, real chance, real duration returns nothing
    -> Adds the specified values of critical chance to a unit for a given period

function UnitAddCriticalMultiplierTimed takes unit u, real multiplier, real duration returns nothing
    -> Adds the specified values of critical multiplier to a unit for a given period

function UnitAddSpellPowerFlatTimed takes unit u, real amount, real duration returns nothing
    -> Add to the Flat amount of Spell Power of a unit for a given period

function UnitAddSpellPowerPercentTimed takes unit u, real amount, real duration returns nothing
    -> Add to the Percent amount of Spell Power of a unit for a given period

function AbilitySpellDamage takes unit u, integer abilityId, abilityreallevelfield field returns string
    -> Given an ability field, will return a string that represents the damage that would be dealt
    -> taking into consideration the spell power bonusses of a unit.

function AbilitySpellDamageEx takes real amount, unit u returns string
    -> Similar to GetSpellDamage will return the damage that would be dealt but as a string

function UnitAddLifeStealTimed takes unit u, real amount, real duration returns nothing
    -> Add to the Life Steal amount of a unit the given amount for a given period

function UnitAddSpellVampTimed takes unit u, real amount, real duration returns nothing
    -> Add to the Spell Vamp amount of a unit the given amount for a given period
Lua:
/* -------------------------------------------------------------------------- */
/*                              Damage Interface                              */
/* -------------------------------------------------------------------------- */
function RegisterDamageEvent(attacktype, damagetype, code)
    -> Code will run when attacktype and damagetype match.

function RegisterAttackDamageEvent(code)
    -> Code will run when a unit is attacked.

function RegisterSpellDamageEvent(code)
    -> Code will run when Spell damage is dealt.

function RegisterAnyDamageEvent(code)
    -> Code will run when any damage is dealt.

function RegisterDamagingEvent(attacktype, damagetype, code)
    -> Code will run when attacktype and damagetype match.
    -> Before Mitigation.

function RegisterAttackDamagingEvent(code)
    -> Code will run when a unit is attacked.
    -> Before Mitigation.

function RegisterSpellDamagingEvent(code)
    -> Code will run when Spell damage is dealt.
    -> Before Mitigation.

function RegisterAnyDamagingEvent(code)
    -> Code will run when any damage is dealt.
    -> Before Mitigation.

/* -------------------------------------------------------------------------- */
/*                                   Evasion                                  */
/* -------------------------------------------------------------------------- */
function RegisterEvasionEvent(code)
    -> YourFunction will run when a unit evades an attack.

function GetMissingUnit()
    -> Returns the unit missing the attack

function GetEvadingUnit()
    -> Returns the unit evading the attack

function GetEvadedDamage()
    -> Returns the amount of evaded damage

function GetUnitEvasionChance(unit)
    -> Returns this system amount of evasion chance given to a unit

function GetUnitMissChance(unit)
    -> Returns this system amount of miss chance given to a unit

function SetUnitEvasionChance(unit, real)
    -> Sets unit evasion chance to specified amount

function SetUnitMissChance(unit, real)
    -> Sets unit miss chance to specified amount

function UnitAddEvasionChance(unit, real)
    -> Add to a unit Evasion chance the specified amount

function UnitAddMissChance(unit, real)
    -> Add to a unit Miss chance the specified amount

function MakeUnitNeverMiss(unit, flag)
    -> Will make a unit never miss attacks no matter the evasion chance of the attacked unit

function DoUnitNeverMiss(unit)
    -> Returns true if the unit will never miss an attack

/* -------------------------------------------------------------------------- */
/*                                  Critical                                  */
/* -------------------------------------------------------------------------- */
function RegisterCriticalStrikeEvent(code)
    -> YourFunction will run when a unit hits a critical strike.

function GetCriticalSource()
    -> Returns the unit hitting a critical strike.

function GetCriticalTarget()
    -> Returns the unit being hit by a critical strike.

function GetCriticalDamage()
    -> Returns the critical strike damage amount.

function GetUnitCriticalChance(unit)
    -> Returns the chance to hit a critical strike to specified unit.

function GetUnitCriticalMultiplier(unit)
    -> Returns the chance to hit a critical strike to specified unit.

function SetUnitCriticalChance(unit, real)
    -> Set's the unit chance to hit a critical strike to specified value.
    -> 15.0 = 15%

function SetUnitCriticalMultiplier(unit, real)
    -> Set's the unit multiplier of damage when hitting a critical to value
    -> 1.0 = increases the multiplier by 1. all units have a multiplier of 1.0
    -> by default, so by adding 1.0, for example, the critical damage will be
    -> 2x the normal damage

function SetCriticalEventDamage(real)
    -> Modify the critical damage dealt to the specified value.

function UnitAddCriticalStrike(unit, chance, multiplier)
    -> Adds the specified values of chance and multiplier to a unit

/* -------------------------------------------------------------------------- */
/*                                 Spell Power                                */
/* -------------------------------------------------------------------------- */
function GetUnitSpellPowerFlat(unit)
    -> Returns the Flat bonus of spell power of a unit

function GetUnitSpellPowerPercent(unit)
    -> Returns the Percent bonus of spell power of a unit

function SetUnitSpellPowerFlat(unit, real)
    -> Set the Flat amount of Spell Power of a unit

function SetUnitSpellPowerPercent(unit, real)
    -> Set the Flat amount of Spell Power of a unit

function UnitAddSpellPowerFlat(unit, real)
    -> Add to the Flat amount of Spell Power of a unit

function UnitAddSpellPowerPercent(unit, real)
    -> Add to the Percent amount of Spell Power of a unit

function GetSpellDamage(unit, real)
    -> Returns the amount of spell damage that would be dealt given an initial damage

/* -------------------------------------------------------------------------- */
/*                                 Life Steal                                 */
/* -------------------------------------------------------------------------- */
function SetUnitLifeSteal(unit, real)
    -> Set the Life Steal amount for a unit

function GetUnitLifeSteal(unit)
    -> Returns the Life Steal amount of a unit

function UnitAddLifeSteal(unit, real)
    -> Add to the Life Steal amount of a unit the given amount

/* -------------------------------------------------------------------------- */
/*                                 Spell Vamp                                 */
/* -------------------------------------------------------------------------- */
function SetUnitSpellVamp(unit, real)
    -> Set the Spell Vamp amount for a unit

function GetUnitSpellVamp(unit)
    -> Returns the Spell Vamp amount of a unit

function UnitAddSpellVamp(unit, real)
    -> Add to the Spell Vamp amount of a unit the given amount

/* -------------------------------------------------------------------------- */
/*                           Damage Interface Utils                           */
/* -------------------------------------------------------------------------- */
function UnitAddEvasionChanceTimed(unit, amount, duration)
    -> Add to a unit Evasion chance the specified amount for a given period

function UnitAddMissChanceTimed(unit, amount, duration)
    -> Add to a unit Miss chance the specified amount for a given period

function UnitAddCriticalStrikeTimed(unit, chance, multiplier, duration)
    -> Adds the specified values of chance and multiplier to a unit for a given period

function UnitAddCriticalChanceTimed(unit, chance, duration)
    -> Adds the specified values of critical chance to a unit for a given period

function UnitAddCriticalMultiplierTimed(unit, multiplier, duration)
    -> Adds the specified values of critical multiplier to a unit for a given period

function UnitAddSpellPowerFlatTimed(unit, amount, duration)
    -> Add to the Flat amount of Spell Power of a unit for a given period

function UnitAddSpellPowerPercentTimed(unit, amount, duration)
    -> Add to the Percent amount of Spell Power of a unit for a given period

function AbilitySpellDamage(unit, ability, field)
    -> Given an ability field, will return a string that represents the damage
    -> that would be dealt taking into consideration the spell power bonusses of a unit.

function AbilitySpellDamageEx(real, unit)
    -> Similar to GetSpellDamage will return the damage that would be dealt but as a string

function UnitAddLifeStealTimed(unit, amount, duration)
    -> Add to the Life Steal amount of a unit the given amount for a given period

function UnitAddSpellVampTimed(unit, amount, duration)
    -> Add to the Spell Vamp amount of a unit the given amount for a given period
vJASS:
Damage.source.unit          -> The source unit of the damage event
Damage.source.player        -> The owning player of the source unit
Damage.source.handle        -> The handle id of the source unit
Damage.source.id            -> The unit user data (UnitIndexer) of the source unit
Damage.source.x             -> The x coordinate of the source unit
Damage.source.y             -> The y coordinate of the source unit
Damage.source.z             -> The z coordinate of the source unit
Damage.source.isHero        -> True if the source unit is a Hero unit
Damage.source.isMelee       -> True if the source unit is a Melee unit
Damage.source.isRanged      -> True if the source unit is a Ranged unit
Damage.source.isStructure   -> True if the source unit is a Structure unit
Damage.source.isMagicImmune -> True if the source unit is a Magic Immune unit

Damage.target.unit          -> The target unit of the damage event
Damage.target.player        -> The owning player of the target unit
Damage.target.handle        -> The handle id of the target unit
Damage.target.id            -> The unit user data (UnitIndexer) of the target unit
Damage.target.x             -> The x coordinate of the target unit
Damage.target.y             -> The y coordinate of the target unit
Damage.target.z             -> The z coordinate of the target unit
Damage.target.isHero        -> True if the target unit is a Hero unit
Damage.target.isMelee       -> True if the target unit is a Melee unit
Damage.target.isRanged      -> True if the target unit is a Ranged unit
Damage.target.isStructure   -> True if the target unit is a Structure unit
Damage.target.isMagicImmune -> True if the target unit is a Magic Immune unit

Damage.damagetype           -> The damagetype of the damage event
Damage.attacktype           -> The attacktype of the damage event
Damage.isSpell              -> True if the damage is Spell type damage
Damage.isAttack             -> True if the damage is Attack type damage
Damage.isEnemy              -> True if the source unit is an enemy of the target unit
Damage.isAlly               -> True if the source unit is an ally of the target unit

(v1.0)
  • Submission
(v1.1)
  • Core System renamed to Damage Interface
  • Included the ability to register damage before mitigation
(v1.2)
  • Add a configuration constant for the text size of Critical Strike and Evasion. Also increased the default text size from 0.016 to 0.019.
  • Attack Projectlies that do not play sound on death (hit) and melee attack will no longer play a sound when a unit evades an attack.
(v1.3)
  • Added a few global variables to assist reducing the amount of code when using this system

    • src -> the source of damage
    • tgt -> the target of damage
    • isEnemy -> true if the source is an enemy of the target
    • isAlly -> same as isEnemy but for allied units
    • isMelee -> indicates if the source of damage is a melee unit
    • structure -> indicates if the target of damage is a structure
    • magicImmune -> indicates if the target of damage is magic immune
    • sIdx -> represents the custom index of the source of damage
    • tIdx -> represents the custom index of the target of damage
    • sId -> represents the unit handle of the source of damage
    • tId -> represents the unit handle of the target of damage
    • p -> the owning player of the source
  • Renamed the Cleave event to Enhanced, as well observed by @Bribe
    • Added a condition to the evaluation of an Attack Event to not run when an attack is evaded (Evasion System)
  • Changed the logic for the NeverMiss functionality to avoid a Condition run (Evasion System)
  • Corrected a spelling error on the RegisterCriticalStrikeEvent() function call (Critical System)
v(1.4)
  • Redone the globlas to use struct members and to be more readable as suggested
    • DamageI.source -> the source of damage
    • DamageI.target -> the target of damage
    • DamageI.sourcePlayer -> the player who owns the source unit
    • DamageI.targetPlayer -> the player who owns the target unit
    • DamageI.isEnemy -> true if the source is an enemy of the target
    • DamageI.isAlly -> same as isEnemy but for allied units
    • DamageI.isMelee -> indicates if the source of damage is a melee unit
    • DamageI.isRanged -> indicates if the source of damage is a ranged unit
    • DamageI.isAttack -> true if the damage type is an attack damage
    • DamageI.isSpell -> true if the damage type is an spell damage
    • DamageI.isPure -> true if the damage type is pure damage
    • DamageI.isEnhanced -> true if the damage type is enhanced
    • DamageI.sourceIsHero -> indicates if the source of damage is a hero unit
    • DamageI.targetIsHero -> indicates if the target of damage is a hero unit
    • DamageI.structure -> indicates if the target of damage is a structure
    • DamageI.magicImmune -> indicates if the target of damage is a magic immune unit
    • DamageI.sourceX -> the X coordinate of the source unit
    • DamageI.sourceY -> the Y coordinate of the source unit
    • DamageI.targetX -> the X coordinate of the target unit
    • DamageI.targetY -> the Y coordinate of the target unit
    • DamageI.sIdx -> represents the custom index of the source of damage
    • DamageI.tIdx -> represents the custom index of the target of damage
    • DamageI.sId -> represents the unit handle of the source of damage
    • DamageI.tId -> represents the unit handle of the target of damage
    • DamageI.damageType -> the damage type of the damage event
    • DamageI.attackType -> the attack type of the damage event
  • Hopefully all those members will help the user to create code with less function calls and less amount of code created overall.
(v1.5)
  • New Configurable Constant CACHE_EXTRA: if true struct members will be set for every event.
  • Code Cleaning
(v1.6)
  • Fixed a minor bug on a global initialization
(v1.7)
  • System Overhauled for more flexibility and opportunities.
  • function RegisterDamageEvent takes attacktype attack, damagetype damage, code c returns nothing and function RegisterDamagingEvent takes attacktype attack, damagetype damage, code c returns nothing now takes the attack type and damage type as parameters for specialization. Example:
    • call RegisterDamageEvent(ATTACK_TYPE_HERO, DAMAGE_TYPE_FIRE, function OnDamage).
      • function OnDamage will execute whenever the Attack Type of the damage event is equal to ATTACK_TYPE_HERO and the Damage Type is equal to DAMAGE_TYPE_FIRE
    • call RegisterDamageEvent(ATTACK_TYPE_HERO, null, function OnDamage).
      • function OnDamage will run whenever the Attack Type of the damage event is equal to ATTACK_TYPE_HERO (null for the damage type parameter means that it doesn't matter)
    • call RegisterDamageEvent(null, DAMAGE_TYPE_COLD, function OnDamage.
      • function OnDamage will run whenever the Damage Type of the damage event is equal to DAMAGE_TYPE_COLD (null for the attack type parameter means that it doesn't matter)
  • Use function RegisterAnyDamageEvent takes code c returns nothing to register functions to any damage event or the equivalent call RegisterDamageEvent(null, null, function OnDamage)
  • The following functions were kept to keep backward compatibility:
    • function RegisterAttackDamageEvent takes code c returns nothing
    • function RegisterSpellDamageEvent takes code c returns nothing
    • function RegisterPureDamageEvent takes code c returns nothing
    • function RegisterEnhancedDamageEvent takes code c returns nothing
    • function RegisterEnhancedDamageEvent takes code c returns nothing
    • function RegisterSpellDamagingEvent takes code c returns nothing
    • function RegisterPureDamagingEvent takes code c returns nothing
    • function RegisterEnhancedDamagingEvent takes code c returns nothing
  • New library DamageInterfaceUtils containing all the extra functionalities of every system that were previously implemented on each system core implementation.
  • Some Code cleaning and optimization.
  • library DamageInterface uses optionally Bribe's Table Library. If not found it will use hashtables.
(v1.8)
  • Fixed a bug for the RegisterSpellDamageEvent not respecting correct flow of the system
  • Removed functions due to alternative registration (Attack and Spell kept because they are the most commonly used):
    • Removed: function RegisterPureDamageEvent takes code c returns nothing Equivalent: RegisterDamageEvent(null, DAMAGE_TYPE_UNIVERSAL, function OnDamage)
    • Removed: function RegisterEnhancedDamageEvent takes code c returns nothing Equivalent: RegisterDamageEvent(null, DAMAGE_TYPE_ENHANCED, function OnDamage)
    • Removed: function RegisterPureDamagingEvent takes code c returns nothing Equivalent: RegisterDamagingEvent(null, DAMAGE_TYPE_UNIVERSAL, function OnDamage)
    • Removed: function RegisterEnhancedDamagingEvent takes code c returns nothing Equivalent: RegisterDamagingEvent(null, DAMAGE_TYPE_ENHANCED, function OnDamage)
  • Removed an global variable used by the Evasion system, It's now a member
  • Code cleaning and formatting.
(v1.9)
  • Removed the members isPure and isEnhanced
(v2.0)
  • System ported to LUA.
  • Damage members redesigned for more intuitive usage (See API).
(v2.1)
  • Small adjustment for the z coordinate of the units in the members.
(V2.2)
  • Had to remove the Damage.damage member due to a bug when calling UnitDamageTarget inside a damage event. So back to using the natives GetEventDamage and BlzSetEventDamage
  • Updated some of the Utilities libraries.
(v2.3)
  • Fixed a small bug for the Spell Power in lua version
(v2.4)
  • Removed the use of GetUnitUserData from the LUA version, now it uses the unit it self as key.
Contents

Damage Interface (Map)

Damage Interface (Map)

Reviews
MyPad
Due to a number of resources already using this being approved, this will retroactively be approved. Status: Damage Interface is Approved
Level 20
Joined
May 16, 2012
Messages
635
I would consider this to be more of a damage interface system than just a plain damage event system, due to the additional functionalities that this system offers that is not exclusively related to damage. That being said, what happened to EVENT_PLAYER_UNIT_DAMAGING?

Agree on the interface point, will change its name if required. About the damaging event, during my experiments with maps development I found myself using most of the time the damaged event, since for the most functionalities it makes more sense, For Example, Life Steal, Spell Vamp only make sense after damage mitigation, for critical strikes I wanted to change the approach to make critical represent the actual amount of damage dealt. It's pretty common to see in custom maps a unit hit a 100k critical strike and dealing only 10 damage, it feels wrong. For Evasion it's irrelevant which one to use, since damage is always set to 0 if given chance to evade occurs. The only applicable scenarios would be on Spell Power Lib and in the core structure of detecting damage, but only as an option. thinking on "A Unit Takes Damage" event will more likely be translated to a "A Unit is Damaged" than "A Unit Is Damaging". After this opinions, if its required to implement Before and After events, I will do, the down side would be continent size function names hehe.
 
Last edited:
About the damaging event, during my experiments with maps development I found myself using most of the time the damaged event, since for the most functionalities it makes more sense, For Example, Life Steal, Spell Vamp only make sense after damage mitigation, for critical strikes I wanted to change the approach to make critical represent the actual amount of damage dealt. It's pretty common to see in custom maps a unit hit a 100k critical strike and dealing only 10 damage, it feels wrong. For Evasion it's irrelevant which one to use, since damage is always set to 0 if given chance to evade occurs.

Understood. Still, being able to access the event EVENT_PLAYER_UNIT_DAMAGING with this system would be useful to some folks who would like to have more control over damage instances.

It doesn't even need to be extensively long. Probably just cnp DamageEvents.OnDamage callback function or alter function directly to process different event ids.
 
Level 20
Joined
May 16, 2012
Messages
635
Understood. Still, being able to access the event EVENT_PLAYER_UNIT_DAMAGING with this system would be useful to some folks who would like to have more control over damage instances.

It doesn't even need to be extensively long. Probably just cnp DamageEvents.OnDamage callback function or alter function directly to process different event ids.

Ok, will make the required changes. thx for the feedback.

Edit: System updated as required.
 
Last edited:
Level 20
Joined
May 16, 2012
Messages
635
Miss display looks too small to catch with the naked eye. When evading an attack, the weapon sound still plays.

Ok, I'll add a configuration constant that represents the text size for Evasion and Critical Strike. As for the weapon sound, I'll try to find a solution for that, not sure how now though.

Oh, and NewBonus appears to not handle extremely large values properly. (possible overflow leading to negative damage).

Well, that's the ability integer field limitation. What is possible to do is limit the max bonus amount. For ability integer field values, what's the maximum value? 2147483647?

I'll do all that tomorrow, 2 pm here right now, but thx anyway.
 
Ok, I'll add a configuration constant that represents the text size for Evasion and Critical Strike. As for the weapon sound, I'll try to find a solution for that, not sure how now though.

Try BlzSetEventWeaponType in a damaging event.

ability integer field values, what's the maximum value? 2147483647?

Iirc, by extensive testing, it was capable of going up to 2^32 - 1 before underflowing. Needs more testing, though. Stick with 2147483647.
 
Level 1
Joined
Apr 29, 2020
Messages
1
All your 3 packages is awesome !!

Can you add example how to use it with GUI ? I would like to use it in my map :D
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
The name Evasion is misleading. Maybe name it damage block or something similar, because that's what it really does. Right now, the supposed evaded attack still triggers attack modifiers (crit, bash, etc) and orb effects and even fires a damage event, which is not really evasion.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
The name Evasion is misleading. Maybe name it damage block or something similar, because that's what it really does. Right now, the supposed evaded attack still triggers attack modifiers (crit, bash, etc) and orb effects and even fires a damage event, which is not really evasion.
Also the form of nullifying the attack sound isn't as sophisticated as what I use for the Peasant in the Damage Engine demo map. The armor type of the target unit can also be nullified.
 
Level 20
Joined
May 16, 2012
Messages
635
The name Evasion is misleading. Maybe name it damage block or something similar, because that's what it really does. Right now, the supposed evaded attack still triggers attack modifiers (crit, bash, etc) and orb effects and even fires a damage event, which is not really evasion.

That's why it's a custom event, and that's why this is for jassers, who can control these situations better. There's no way of blocking attack modifiers as far as i know, unless it's triggered, and that's where this system can come in handy.

Also the form of nullifying the attack sound isn't as sophisticated as what I use for the Peasant in the Damage Engine demo map. The armor type of the target unit can also be nullified.

Simpler is better imo. Projectiles with death sounds will still play it in your system even if both are nullified. Melee attacks will play no sound as intended. Someone playing will not be able to say that both are null or only one, so what's your point, what other advantages nullifying both fields offer?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
That's why it's a custom event, and that's why this is for jassers, who can control these situations better. There's no way of blocking attack modifiers as far as i know, unless it's triggered, and that's where this system can come in handy.



Simpler is better imo. Projectiles with death sounds will still play it in your system even if both are nullified. Melee attacks will play no sound as intended. Someone playing will not be able to say that both are null or only one, so what's your point, what other advantages nullifying both fields offer?
There's a subtle impact sound which plays when anything hits a unit's armor, even if the impacting weapon or missile has no sound of its own.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
That's why it's a custom event, and that's why this is for jassers, who can control these situations better. There's no way of blocking attack modifiers as far as i know, unless it's triggered, and that's where this system can come in handy.
I'm not asking you to replicate evasion actually cause afaik, it's currently undoable. But the point still stands that the name/description given by the system should correctly describe what it does, which is the concern. You can't make a custom event for example that uses EVENT_PLAYER_UNIT_ATTACKED behind the scenes and disguise it as a damage detection system, and say that the users can deal with the issues due to having jass at their disposal.
 
Level 20
Joined
May 16, 2012
Messages
635
I'm not asking you to replicate evasion actually cause afaik, it's currently undoable. But the point still stands that the name/description given by the system should correctly describe what it does, which is the concern. You can't make a custom event for example that uses EVENT_PLAYER_UNIT_ATTACKED behind the scenes and disguise it as a damage detection system, and say that the users can deal with the issues due to having jass at their disposal.

Understood. Will rename it to something else.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Really? In my tests I couldn't hear a thing. Can you tell an example so I can go and test it?
I had just been testing on my demo map with the peasant attacking friendly units. I would recommend having your headphones on though, as again it's quite subtle.

That is, unless that got changed in a recent patch. It was some months ago when I was last playing around with it.
 
Level 20
Joined
May 16, 2012
Messages
635
I had just been testing on my demo map with the peasant attacking friendly units. I would recommend having your headphones on though, as again it's quite subtle.

That is, unless that got changed in a recent patch. It was some months ago when I was last playing around with it.

I tested your map and there are absolutely no difference. I have a jbl tune500bt, which is quite descent headphone, with music turned off and sound effect at 100% and couldn't notice anything different about your method and the one I implemented.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I tested your map and there are absolutely no difference. I have a jbl tune500bt, which is quite descent headphone, with music turned off and sound effect at 100% and couldn't notice anything different about your method and the one I implemented.
Interesting - thanks for testing. I stand corrected. Maybe something changed in a recent patch, or maybe my testing wasn't thorough enough.
 
Found a possible data race condition in the Evasion Library, if used repeatedly.

JASS:
function MakeUnitNeverMiss takes unit u, boolean flag returns nothing
    set Evasion.NeverMiss[GetUnitUserData(u)] = flag
endfunction

The function above is supposedly only going to be used once. However, if the function is used more than once, the following data race condition arises:

JASS:
    call MakeUnitNeverMiss(u, true)    // This will set the NeverMiss flag to true
    call MakeUnitNeverMiss(u, true)    // At the current setup, this will do nothing of importance.

    call MakeUnitNeverMiss(u, false)    // This will set the NeverMiss flag to false
    set newFlag = DoUnitNeverMiss(u) // newFlag will be false instead of true.
    call MakeUnitNeverMiss(u, false)
    set newFlag = DoUnitNeverMiss(u) // newFlag will be false as expected.

The race condition in this case is the unexpected value 'false' in the first read to DoUnitNeverMiss after MakeUnitNeverMiss, where a 'true' value would be expected. (Since MakeUnitNeverMiss is called twice, making the unit not miss an attack, calling MakeUnitNeverMiss twice to make the unit miss attacks again should be the intended outcome.)
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Found a possible data race condition in the Evasion Library, if used repeatedly.

JASS:
function MakeUnitNeverMiss takes unit u, boolean flag returns nothing
    set Evasion.NeverMiss[GetUnitUserData(u)] = flag
endfunction

The function above is supposedly only going to be used once. However, if the function is used more than once, the following data race condition arises:

JASS:
    call MakeUnitNeverMiss(u, true)    // This will set the NeverMiss flag to true
    call MakeUnitNeverMiss(u, true)    // At the current setup, this will do nothing of importance.

    call MakeUnitNeverMiss(u, false)    // This will set the NeverMiss flag to false
    set newFlag = DoUnitNeverMiss(u) // newFlag will be false instead of true.
    call MakeUnitNeverMiss(u, false)
    set newFlag = DoUnitNeverMiss(u) // newFlag will be false as expected.

The race condition in this case is the unexpected value 'false' in the first read to DoUnitNeverMiss after MakeUnitNeverMiss, where a 'true' value would be expected. (Since MakeUnitNeverMiss is called twice, making the unit not miss an attack, calling MakeUnitNeverMiss twice to make the unit miss attacks again should be the intended outcome.)

Yeah that would have to be mitigaged by a system which tracks the number of allocates on a unit and only deactivates once all allocations are cleared out.
 
Level 20
Joined
May 16, 2012
Messages
635
I see the problem here, and it's pretty simple to solve, although I'm with @MyPad on the usage of the functionality. I'll make NeverMiss an integer instead of a Boolean, that way is simpler to keep track of this problem instead of checking for instances. If NeverMiss > 0 than the unit never misses other wise it can miss attacks, and when calling the functions to manipulate it, if true, Never miss is increased and if false, decreased.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I'd like to mention that the Enhanced damage type does not only pertain to Cleave. See the link below:

REPO in progress - mapping damage types to their abilities

Cleaving Attack (subsequent targets only)
Pulverize
Fan of Knives
Bladestorm
Burning Oil


I therefore recommend a more general approach. Based on the needs, requests and implementations I've seen, I've found the most common things people want are:

Physical damage detection
Spell damage detection
Code/triggered damage filtering or detection
Ranged/melee differentiation
Catch-all damage (such as applying shields)

Additionally, I've noticed that your scoping of your structs are not following protocol. Make sure you are privatizing everything that you don't intend for the user to interact with (such as the onDamage event response method).
 
Level 20
Joined
May 16, 2012
Messages
635
I'd like to mention that the Enhanced damage type does not only pertain to Cleave. See the link below:

REPO in progress - mapping damage types to their abilities

Cleaving Attack (subsequent targets only)
Pulverize
Fan of Knives
Bladestorm
Burning Oil


I therefore recommend a more general approach. Based on the needs, requests and implementations I've seen, I've found the most common things people want are:

Physical damage detection
Spell damage detection
Code/triggered damage filtering or detection
Ranged/melee differentiation
Catch-all damage (such as applying shields)

Additionally, I've noticed that your scoping of your structs are not following protocol. Make sure you are privatizing everything that you don't intend for the user to interact with (such as the onDamage event response method).

Thx for the feedback and suggestions @Bribe . I'm cooking some ideas to make it more useful and fixed a few things pointed out.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Thanks for fixing the issue with the globals. However you should definitely avoid so much duplicate code with:

JASS:
            set damageType    = BlzGetEventDamageType()
            set attackType    = BlzGetEventAttackType()
            set source        = GetEventDamageSource()
            set target        = BlzGetEventDamageTarget()
            set sourcePlayer  = GetOwningPlayer(source)
            set targetPlayer  = GetOwningPlayer(target)
            set isEnemy       = IsUnitEnemy(target, sourcePlayer)
            set isAlly        = IsUnitAlly(target, sourcePlayer)
            set isMelee       = IsUnitType(source, UNIT_TYPE_MELEE_ATTACKER)
            set isRanged      = IsUnitType(source, UNIT_TYPE_RANGED_ATTACKER)
            set isAttack      = damageType == DAMAGE_TYPE_NORMAL
            set isSpell       = attackType == ATTACK_TYPE_NORMAL
            set isPure        = damageType == DAMAGE_TYPE_UNIVERSAL
            set isEnhanced    = damageType == DAMAGE_TYPE_ENHANCED
            set sourceIsHero  = IsUnitType(source, UNIT_TYPE_HERO)
            set targetIsHero  = IsUnitType(target, UNIT_TYPE_HERO)
            set structure     = IsUnitType(target, UNIT_TYPE_STRUCTURE)
            set magicImmune   = IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
            set sourceX       = GetUnitX(source)
            set sourceY       = GetUnitY(source)
            set targetX       = GetUnitX(target)
            set targetY       = GetUnitY(target)
            set sIdx          = GetUnitUserData(source)
            set tIdx          = GetUnitUserData(target)
            set sId           = GetHandleId(source)
            set tId           = GetHandleId(target)

There should just be the static method "GetEventResponses" or such that is called by both the Damage and Damaging responses to get those values out.

I also would recommend avoiding caching unnecessary things the user can get themselves, like getting rid of these:

JASS:
            set isEnemy       = IsUnitEnemy(target, sourcePlayer)
            set isAlly        = IsUnitAlly(target, sourcePlayer)
            set sourceIsHero  = IsUnitType(source, UNIT_TYPE_HERO)
            set targetIsHero  = IsUnitType(target, UNIT_TYPE_HERO)
            set structure     = IsUnitType(target, UNIT_TYPE_STRUCTURE)
            set magicImmune   = IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
            set sourceX       = GetUnitX(source)
            set sourceY       = GetUnitY(source)
            set targetX       = GetUnitX(target)
            set targetY       = GetUnitY(target)
            set sIdx          = GetUnitUserData(source)
            set tIdx          = GetUnitUserData(target)
            set sId           = GetHandleId(source)
            set tId           = GetHandleId(target)
 
Level 20
Joined
May 16, 2012
Messages
635
There should just be the static method "GetEventResponses" or such that is called by both the Damage and Damaging responses to get those values out.

Good Idea, will do!

I also would recommend avoiding caching unnecessary things the user can get themselves, like getting rid of these:

Hmm, the purpose behind that is to reduce the amount of calls per trigger that is listening to a damage event. When i created Damage Interface my idea was to provide access to stuff that i was repeating again and again in so many triggers like getting the custom index of a unit or its handle or multiple conditions to evaluate, and that's a lot of calls. I think I`ll add a configuration global for the user to set, so if they want those values cached the system will do it for them and if not then it will ignore it. I think this way it can maintain the utilities.

Thx Bribe, you are of great help.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Good Idea, will do!



Hmm, the purpose behind that is to reduce the amount of calls per trigger that is listening to a damage event. When i created Damage Interface my idea was to provide access to stuff that i was repeating again and again in so many triggers like getting the custom index of a unit or its handle or multiple conditions to evaluate, and that's a lot of calls. I think I`ll add a configuration global for the user to set, so if they want those values cached the system will do it for them and if not then it will ignore it. I think this way it can maintain the utilities.

Thx Bribe, you are of great help.
Sounds then like your ideal solution is to have that variable-caching function be towards the top of your library and then ask the user to just comment out any variable sets they'll never need.
 
Level 3
Joined
Oct 10, 2017
Messages
32
Is there an easy to understand guide? For a newbie that wants to implement this system into his map the description is kind of unfitting :/ not everyone is a professional coder.
 
Level 12
Joined
May 16, 2020
Messages
660
Hi Chopinski, how do I make use of LinkBonusToItem?

Can I add the code into a map initialization trigger, and then "design" all the items within the same trigger?

For example:
  • ItemshopGUI Init
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Custom script: call LinkBonusToItem(GetManipulatingUnit(), BONUS_ARMOR, 10, GetManipulatedItem(RAM1))

Or do I need to create a specific trigger, like "unit picks up item" or "drops item"?
 
Level 18
Joined
Oct 17, 2012
Messages
818
The bonus linked to item is only temporary. It will disappear once item is dropped. So, you will have to reapply bonuses upon pickup.

I once suggest to chopinski something similar to Custom Stat System but for item handles. He never came around to it. On the other hand, such a suggestion would probably be better off as a separate system that uses NewBonus as its base.
 
Last edited:
Level 12
Joined
May 16, 2020
Messages
660
Hi Chopinski, I do not really understand JASS, but I assume your Critical Strike works in the way that it just doubles / tripples etc the damage done (like for example you would SetDamage = XXX in Bribe's system).

How did you make the triggered Critical Strike damage work correctly with the default Cleave Attack ability?
Or did you trigger Cleave as well?
 
Level 20
Joined
May 16, 2012
Messages
635
Hi Chopinski, I do not really understand JASS, but I assume your Critical Strike works in the way that it just doubles / tripples etc the damage done (like for example you would SetDamage = XXX in Bribe's system).

How did you make the triggered Critical Strike damage work correctly with the default Cleave Attack ability?
Or did you trigger Cleave as well?
It depends on how you want critical and cleave to work. The standard way the game handles both is when you hit a critical strike and have cleave, the cleave damage will also be increased by the total critical damage . Now you have a few ways to implement it, if you want this behavior then you should try to use the ONDAMAGING event because it happens before damage armor mitigation like the native critical strike. If do not want this behavior then you should use the ONDAMAGE event, which happens after armor mitigation, resulting in a cleave damage that is not altered by the total damage after critical. There is still a chance that you might not be able to use the first if I recall due to the way Cleave is natively implemented in the game, in that case then you should implement a custom cleave, but you must test it. You can do this with Bribe's system easily, you just need to use the right values for the DamageEvent variable to execute before or after armor mitigation.
 
Level 18
Joined
Oct 17, 2012
Messages
818
You could ask someone or the author to post code here. As an alternative, check out Preview Triggers. Another way would be to keep two different versions of Warcraft, so you will not have such trouble using these patch dependent resources.
 
Level 2
Joined
Apr 26, 2020
Messages
2
Hey @chopinski, thanks for this resource; I found your Lua code to be a pleasure to read and I've learned a lot from it. I've been crawling through your code and made a number of changes to my preference (possibly minor performance increases, typos in comments, silly things).

Once I got to the DamageInterfaceUtil code though, I wasn't able to follow it. I felt that I understood what it was doing, just not how it was doing it. Plus I noticed you were using a number of timers where you could just use one. So...I kinda rewrote it. :grin: I assume it performs better too, but I haven't done any benchmarking or anything.

Anyway, I just wanted to say thanks and share my revision rewrite with you. I don't expect you to use it (I rewrote it in my own style), but I thought maybe you might be interested to see it done differently.

Lua:
do
    -- -------------------------------------------------------------------------- --
    --                                Configuration                               --
    -- -------------------------------------------------------------------------- --
    local systemTickRate = 0.03125000
    local systemTimer = CreateTimer()
    local utilInstances = {}
    local utilInstancesCount = 0 -- A counter to track number of data in the utilInstances array. This is faster than using #utilInstances.
    -- -------------------------------------------------------------------------- --
    --                                   System                                   --
    -- -------------------------------------------------------------------------- --
    local function TimerTick()
        -- Loop over each system instance and manually tick them, once an instance is out of ticks we remove it from the instance array.
        local index = 1
        local newTick = 0 -- Cache the new tick calculation to reduce code repetition.
                
        while index <= utilInstancesCount do
            newTick = utilInstances[index].remainingTicks - 1 or 0
            if newTick <= 0 then
                utilInstances[index]:destroy() -- Tell the instance to perform its final logic.
                utilInstances[index] = utilInstances[utilInstancesCount] -- Move the last element in the array to this index.
                utilInstances[utilInstancesCount] = nil -- Set the last element in the array to nil (remove it).
                utilInstancesCount = utilInstancesCount - 1
            else -- If we didn't move any instances around in the array, we move on to the next by incrementing.
                utilInstances[index].remainingTicks = newTick
                index = index + 1
            end
        end
        -- If there aren't any more instances we stop the system timer from needlessly ticking.
        if utilInstancesCount <= 0 then PauseTimer(systemTimer) end
    end
    -- -------------------------------------------------------------------------- --
    --                                Timed Evasion                               --
    -- -------------------------------------------------------------------------- --
    do
        TimedEvasion = {}
        TimedEvasion.__index = TimedEvasion
        
        function TimedEvasion:destroy()
            if self.isMissChance then
                UnitAddMissChance(self.unit, -self.amount)
            else
                UnitAddEvasionChance(self.unit, -self.amount)
            end
        end
        
        function TimedEvasion.new(unit, amount, duration, isMissChance)
            local newInstance = {
                unit = unit,
                amount = amount,
                remainingTicks = math.ceil(duration / systemTickRate),
                isMissChance = isMissChance or false
            }
            setmetatable(newInstance, TimedEvasion)
            -- Initialize the instance's effects.
            if newInstance.isMissChance then
                UnitAddMissChance(unit, amount)
            else
                UnitAddEvasionChance(unit, amount)
            end
            
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
            
        end
    end
    
    -- -------------------------------------------------------------------------- --
    --                            Timed Critical Strike                           --
    -- -------------------------------------------------------------------------- --
    do
        TimedCritical = {}
        TimedCritical.__index = TimedCritical
        
        function TimedCritical:destroy()
            UnitAddCriticalStrike(self.unit, -self.chance, -self.multiplier)
        end
        
        function TimedCritical.new(unit, chance, multiplier, duration)
            local newInstance = {
                unit = unit,
                chance = chance,
                multiplier = multiplier,
                remainingTicks = math.ceil(duration / systemTickRate)
            }
            setmetatable(newInstance, TimedCritical)
            -- Initialize the instance's effects.
            UnitAddCriticalStrike(unit, chance, multiplier)
            
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
            
        end
    end
    
    -- -------------------------------------------------------------------------- --
    --                              Timed Spell Power                             --
    -- -------------------------------------------------------------------------- --
    do
        TimedSpellPower = {}
        TimedSpellPower.__index = TimedSpellPower
        
        function TimedSpellPower:destroy()
            if self.isFlat then
                UnitAddSpellPowerFlat(self.unit, -self.amount)
            else
                UnitAddSpellPowerPercent(self.unit, -self.amount)
            end
        end
        
        function TimedSpellPower.new(unit, amount, duration, isFlat)
            local newInstance = {
                unit = unit,
                amount = amount,
                remainingTicks = math.ceil(duration / systemTickRate),
                isFlat = isFlat or false
            }
            setmetatable(newInstance, TimedSpellPower)
            -- Initialize the instance's effects.
            if newInstance.isFlat then
                UnitAddSpellPowerFlat(unit, amount)
            else
                UnitAddSpellPowerPercent(unit, amount)
            end
            
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
            
        end
    end
    
    -- -------------------------------------------------------------------------- --
    --                              Timed Life Steal                              --
    -- -------------------------------------------------------------------------- --
    do
        TimedLifeSteal = {}
        TimedLifeSteal.__index = TimedLifeSteal
        
        function TimedLifeSteal:destroy()
            UnitAddLifeSteal(self.unit, -self.amount)
        end
        
        function TimedLifeSteal.new(unit, amount, duration)
            local newInstance = {
                unit = unit,
                amount = amount,
                remainingTicks = math.ceil(duration / systemTickRate)
            }
            setmetatable(newInstance, TimedLifeSteal)
            -- Initialize the instance's effects.
            UnitAddLifeSteal(unit, amount)
            
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
            
        end
    end
    -- -------------------------------------------------------------------------- --
    --                              Timed Spell Vamp                              --
    -- -------------------------------------------------------------------------- --
    do
        TimedSpellVamp = {}
        TimedSpellVamp.__index = TimedSpellVamp
        
        function TimedSpellVamp:destroy()
            UnitAddSpellVamp(self.unit, -self.amount)
        end
        
        function TimedSpellVamp.new(unit, amount, duration)
            local newInstance = {
                unit = unit,
                amount = amount,
                remainingTicks = math.ceil(duration / systemTickRate)
            }
            setmetatable(newInstance, TimedSpellVamp)
            -- Initialize the instance's effects.
            UnitAddSpellVamp(unit, amount)
            
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
            
        end
    end
    
    -- -------------------------------------------------------------------------- --
    --                                   LUA API                                  --
    -- -------------------------------------------------------------------------- --
    function UnitAddEvasionChanceTimed(unit, amount, duration)
        TimedEvasion.new(unit, amount, duration, false)
    end
    function UnitAddMissChanceTimed(unit, amount, duration)
        TimedEvasion.new(unit, amount, duration, true)
    end
    
    function UnitAddCriticalStrikeTimed(unit, chance, multiplier, duration)
        TimedCritical.new(unit, chance, multiplier, duration)
    end
    function UnitAddCriticalChanceTimed(unit, chance, duration)
        TimedCritical.new(unit, chance, 0, duration)
    end
    function UnitAddCriticalMultiplierTimed(unit, multiplier, duration)
        TimedCritical.new(unit, 0, multiplier, duration)
    end
    
    function UnitAddSpellPowerFlatTimed(unit, amount, duration)
        TimedSpellPower.new(unit, amount, duration, true)
    end
    function UnitAddSpellPowerPercentTimed(unit, amount, duration)
        TimedSpellPower.new(unit, amount, duration, false)
    end
    function AbilitySpellDamage(unit, ability, field)
        local i = GetUnitUserData(unit)
        
        return I2S(R2I((BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, GetUnitAbilityLevel(unit, ability) - 1) + (SpellPower.flat[i] or 0)) * (1 + (SpellPower.percent[i] or 0))))
    end
    function AbilitySpellDamageEx(real, unit)
        local i = GetUnitUserData(unit)
        
        return I2S(R2I((real + (SpellPower.flat[i] or 0)) * (1 + (SpellPower.percent[i] or 0))))
    end
    
    function UnitAddLifeStealTimed(unit, amount, duration)
        TimedLifeSteal.new(unit, amount, duration)
    end
    function UnitAddSpellVampTimed(unit, amount, duration)
        TimedSpellVamp.new(unit, amount, duration)
    end
end
 
Level 20
Joined
May 16, 2012
Messages
635
Hey @chopinski, thanks for this resource; I found your Lua code to be a pleasure to read and I've learned a lot from it. I've been crawling through your code and made a number of changes to my preference (possibly minor performance increases, typos in comments, silly things).

Once I got to the DamageInterfaceUtil code though, I wasn't able to follow it. I felt that I understood what it was doing, just not how it was doing it. Plus I noticed you were using a number of timers where you could just use one. So...I kinda rewrote it. :grin: I assume it performs better too, but I haven't done any benchmarking or anything.

Anyway, I just wanted to say thanks and share my revision rewrite with you. I don't expect you to use it (I rewrote it in my own style), but I thought maybe you might be interested to see it done differently.

Lua:
do
    -- -------------------------------------------------------------------------- --
    --                                Configuration                               --
    -- -------------------------------------------------------------------------- --
    local systemTickRate = 0.03125000
    local systemTimer = CreateTimer()
    local utilInstances = {}
    local utilInstancesCount = 0 -- A counter to track number of data in the utilInstances array. This is faster than using #utilInstances.
    -- -------------------------------------------------------------------------- --
    --                                   System                                   --
    -- -------------------------------------------------------------------------- --
    local function TimerTick()
        -- Loop over each system instance and manually tick them, once an instance is out of ticks we remove it from the instance array.
        local index = 1
        local newTick = 0 -- Cache the new tick calculation to reduce code repetition.
               
        while index <= utilInstancesCount do
            newTick = utilInstances[index].remainingTicks - 1 or 0
            if newTick <= 0 then
                utilInstances[index]:destroy() -- Tell the instance to perform its final logic.
                utilInstances[index] = utilInstances[utilInstancesCount] -- Move the last element in the array to this index.
                utilInstances[utilInstancesCount] = nil -- Set the last element in the array to nil (remove it).
                utilInstancesCount = utilInstancesCount - 1
            else -- If we didn't move any instances around in the array, we move on to the next by incrementing.
                utilInstances[index].remainingTicks = newTick
                index = index + 1
            end
        end
        -- If there aren't any more instances we stop the system timer from needlessly ticking.
        if utilInstancesCount <= 0 then PauseTimer(systemTimer) end
    end
    -- -------------------------------------------------------------------------- --
    --                                Timed Evasion                               --
    -- -------------------------------------------------------------------------- --
    do
        TimedEvasion = {}
        TimedEvasion.__index = TimedEvasion
       
        function TimedEvasion:destroy()
            if self.isMissChance then
                UnitAddMissChance(self.unit, -self.amount)
            else
                UnitAddEvasionChance(self.unit, -self.amount)
            end
        end
       
        function TimedEvasion.new(unit, amount, duration, isMissChance)
            local newInstance = {
                unit = unit,
                amount = amount,
                remainingTicks = math.ceil(duration / systemTickRate),
                isMissChance = isMissChance or false
            }
            setmetatable(newInstance, TimedEvasion)
            -- Initialize the instance's effects.
            if newInstance.isMissChance then
                UnitAddMissChance(unit, amount)
            else
                UnitAddEvasionChance(unit, amount)
            end
           
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
           
        end
    end
   
    -- -------------------------------------------------------------------------- --
    --                            Timed Critical Strike                           --
    -- -------------------------------------------------------------------------- --
    do
        TimedCritical = {}
        TimedCritical.__index = TimedCritical
       
        function TimedCritical:destroy()
            UnitAddCriticalStrike(self.unit, -self.chance, -self.multiplier)
        end
       
        function TimedCritical.new(unit, chance, multiplier, duration)
            local newInstance = {
                unit = unit,
                chance = chance,
                multiplier = multiplier,
                remainingTicks = math.ceil(duration / systemTickRate)
            }
            setmetatable(newInstance, TimedCritical)
            -- Initialize the instance's effects.
            UnitAddCriticalStrike(unit, chance, multiplier)
           
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
           
        end
    end
   
    -- -------------------------------------------------------------------------- --
    --                              Timed Spell Power                             --
    -- -------------------------------------------------------------------------- --
    do
        TimedSpellPower = {}
        TimedSpellPower.__index = TimedSpellPower
       
        function TimedSpellPower:destroy()
            if self.isFlat then
                UnitAddSpellPowerFlat(self.unit, -self.amount)
            else
                UnitAddSpellPowerPercent(self.unit, -self.amount)
            end
        end
       
        function TimedSpellPower.new(unit, amount, duration, isFlat)
            local newInstance = {
                unit = unit,
                amount = amount,
                remainingTicks = math.ceil(duration / systemTickRate),
                isFlat = isFlat or false
            }
            setmetatable(newInstance, TimedSpellPower)
            -- Initialize the instance's effects.
            if newInstance.isFlat then
                UnitAddSpellPowerFlat(unit, amount)
            else
                UnitAddSpellPowerPercent(unit, amount)
            end
           
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
           
        end
    end
   
    -- -------------------------------------------------------------------------- --
    --                              Timed Life Steal                              --
    -- -------------------------------------------------------------------------- --
    do
        TimedLifeSteal = {}
        TimedLifeSteal.__index = TimedLifeSteal
       
        function TimedLifeSteal:destroy()
            UnitAddLifeSteal(self.unit, -self.amount)
        end
       
        function TimedLifeSteal.new(unit, amount, duration)
            local newInstance = {
                unit = unit,
                amount = amount,
                remainingTicks = math.ceil(duration / systemTickRate)
            }
            setmetatable(newInstance, TimedLifeSteal)
            -- Initialize the instance's effects.
            UnitAddLifeSteal(unit, amount)
           
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
           
        end
    end
    -- -------------------------------------------------------------------------- --
    --                              Timed Spell Vamp                              --
    -- -------------------------------------------------------------------------- --
    do
        TimedSpellVamp = {}
        TimedSpellVamp.__index = TimedSpellVamp
       
        function TimedSpellVamp:destroy()
            UnitAddSpellVamp(self.unit, -self.amount)
        end
       
        function TimedSpellVamp.new(unit, amount, duration)
            local newInstance = {
                unit = unit,
                amount = amount,
                remainingTicks = math.ceil(duration / systemTickRate)
            }
            setmetatable(newInstance, TimedSpellVamp)
            -- Initialize the instance's effects.
            UnitAddSpellVamp(unit, amount)
           
            -- Update our global util instance count.
            utilInstancesCount = utilInstancesCount + 1
            utilInstances[utilInstancesCount] = newInstance
            -- If this is the first instance in this system then the timer shouldn't be running, so we start it.
            if utilInstancesCount == 1 then
                TimerStart(systemTimer, systemTickRate, true, TimerTick)
            end
           
        end
    end
   
    -- -------------------------------------------------------------------------- --
    --                                   LUA API                                  --
    -- -------------------------------------------------------------------------- --
    function UnitAddEvasionChanceTimed(unit, amount, duration)
        TimedEvasion.new(unit, amount, duration, false)
    end
    function UnitAddMissChanceTimed(unit, amount, duration)
        TimedEvasion.new(unit, amount, duration, true)
    end
   
    function UnitAddCriticalStrikeTimed(unit, chance, multiplier, duration)
        TimedCritical.new(unit, chance, multiplier, duration)
    end
    function UnitAddCriticalChanceTimed(unit, chance, duration)
        TimedCritical.new(unit, chance, 0, duration)
    end
    function UnitAddCriticalMultiplierTimed(unit, multiplier, duration)
        TimedCritical.new(unit, 0, multiplier, duration)
    end
   
    function UnitAddSpellPowerFlatTimed(unit, amount, duration)
        TimedSpellPower.new(unit, amount, duration, true)
    end
    function UnitAddSpellPowerPercentTimed(unit, amount, duration)
        TimedSpellPower.new(unit, amount, duration, false)
    end
    function AbilitySpellDamage(unit, ability, field)
        local i = GetUnitUserData(unit)
       
        return I2S(R2I((BlzGetAbilityRealLevelField(BlzGetUnitAbility(unit, ability), field, GetUnitAbilityLevel(unit, ability) - 1) + (SpellPower.flat[i] or 0)) * (1 + (SpellPower.percent[i] or 0))))
    end
    function AbilitySpellDamageEx(real, unit)
        local i = GetUnitUserData(unit)
       
        return I2S(R2I((real + (SpellPower.flat[i] or 0)) * (1 + (SpellPower.percent[i] or 0))))
    end
   
    function UnitAddLifeStealTimed(unit, amount, duration)
        TimedLifeSteal.new(unit, amount, duration)
    end
    function UnitAddSpellVampTimed(unit, amount, duration)
        TimedSpellVamp.new(unit, amount, duration)
    end
end
Thx. I will look at your code when i have some time. I still a noob in lua myself so there will always be room for improvement.
 
Top