Damage Tracker v1.2.3

================
Damage Tracker v1.2.3
================
Damage Tracker is a small addon to Damage Engine. It allows you to easily retrieve a total amount of damage that a unit has dealt by using Damage Engine to detect when a unit takes damage then store the damage information into a hashtable. When the unit taking damage dies, the system clean up the information. This system's purpose is mainly for tracking individual units damage info but surely, it can also track each player's damage info as well. Total Damage Info is divided into three category: Total Overall Damage, Total Spell Damage, and Total Physical Damage.

Features

Code & Requirements

Credits


  • Damage Tracker has custom events that can help you retrieve damage info. They are:
    • DTR_TrackerEvent Equal to 1.00, detects when a unit takes damage
    • DTR_TrackerEvent Equal to 2.00, detects when a unit dies
  • Allows you to retrieve Total Overall/Spell/Physical Damage dealt by a unit.
    You can simply access these variables to retrieve the values:
    • DTR_TotalUnitDamage
    • DTR_TotalSpellDamage
    • DTR_TotalPhysicalDamage
    These work with both DTR_TrackerEvent Equal to 1.00 and DTR_TrackerEvent Equal to 2.00 but does not work with normal event.
  • Allows you to retrieve Total Overall/Spell/Physical Damage taken by a unit.
    You can simply access these variables to retrieve the values:
    • DTR_TotalDamageTaken
    • DTR_TotalSpellDamageTaken
    • DTR_TotalPhysicalDamageTaken
    These work with both DTR_TrackerEvent Equal to 1.00 and DTR_TrackerEvent Equal to 2.00 but does not work with normal event.
  • Allows you to retrieve Damage Contribution (in percentage %) by a unit.
    You can simply access these variables to retrieve the values:
    • DTR_OverallContribution[]
    • DTR_SpellContribution[]
    • DTR_PhysContribution[]
    However, these only work with DTR_TrackerEvent Equal to 2.00. You can also access contributors by enumerating DTR_Sources unit group during this event. These variables use unit's custom value as index. Check example 2.
  • Allows you to retrieve The Top Contributor and their contribution for each damage type.
    You can simply access these variables to retrieve the values:
    • DTR_TopContributor / DTR_TopPhysContributor / DTR_TopSpellContributor
    • DTR_TopContribution / DTR_TopPhysContribution / DTR_TopSpellContribution
    However, these only work with DTR_TrackerEvent Equal to 2.00. Check example 2.
  • Allows you to retrieve Total Overall/Spell/Physical Damage dealt by a player.
    You can simply access these variables to retrieve the values:
    • DTR_TotalPlayerDamage[]
    • DTR_TotalPlayerSpellDamage[]
    • DTR_TotalPlayerPhysDamage[]
    These ones are still accessible outside the custom events. Note: They are array and the index starts from 0 and also, for now the system only counts the Total Damage of registered units of each player.
  • Allows you to retrieve Total Overall/Spell/Physical Damage taken by a player.
    You can simply access these variables to retrieve the values:
    • DTR_TotalPlayerDamageT[]
    • DTR_TotalPlayerSpellDamageT[]
    • DTR_TotalPlayerPhysDamageT[]
    These ones are still accessible outside the custom events. Note: They are array and the index starts from 0 and also, for now the system only counts the Total Damage of registered units of each player.
  • API made for GUIers.
    You can simply fetch damage information of a certain pair by using:
    • Set DTR_SourceParam = YourSourceUnit
    • Set DTR_TargetParam = YourTargetUnit
    • Trigger - Run DTR_GetData (checking conditions)
    As long as the info is not cleaned up yet, the variables will be loaded with the latest data.
    The data is cleaned up when a unit dies or when auto cleanup is enabled.

Requirements:

Damage Tracker Config

Damage Tracker

Examples

Changelogs


  • Damage Tracker Config
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- - --------
      • -------- By default Damage Tracker requires you to register your unit first before its damage can be tracked --------
      • -------- This is done with the intention to preserve memory in the long run by tracking only necessary units --------
      • -------- However, you have the option to choose how Damage Tracker registers the unit --------
      • -------- Available Options: --------
      • -------- 0 = No restriction, all units willl be tracked without having to be registered first --------
      • -------- 1 = You must register the Damage Source only --------
      • -------- 2 = You must register the Damage Target only --------
      • -------- 3 = You must register both the Damage Source and Damage Target --------
      • -------- - --------
      • Set DTR_RegistrationMode = 1
      • -------- - --------
      • -------- There will be cases when a unit is damaged but stays alive for the whole game --------
      • -------- Which means damage information of that unit will also stay in the memory longer than it should be --------
      • -------- This features helps to prevent that by deleting damage info of inactive unit after a period of time --------
      • -------- A unit is considered inactive when it does not take any damage for a duration of time --------
      • -------- The duration can be adjusted in DTR_CleanupTime --------
      • -------- You have the option to disable this feature by setting DTR_AutoCleanup to false --------
      • -------- - --------
      • Set DTR_AutoCleanup = True
      • -------- - --------
      • -------- Adjust the value of CleanupTime according to your needs --------
      • -------- Make sure it's greater than zero to avoid any possible complications --------
      • -------- - --------
      • Set DTR_CleanupTime = 20.00
      • -------- - --------
      • -------- This is the interval of a timer used by the system to check when clean up time arrives --------
      • -------- Lower value means high accuracy in exchange of performance --------
      • -------- 1 second interval should be more than sufficient --------
      • -------- - --------
      • Set DTR_TimeInterval = 1.00
      • -------- - --------
      • -------- Do not enable any of these variables below --------
      • -------- They are simply there to make copying easier --------
      • -------- - --------
      • Set DTR_IsRegistered[0] = False
      • Set DTR_OverallContribution[0] = 0.00
      • Set DTR_PhysContribution[0] = 0.00
      • Set DTR_SpellContribution[0] = 0.00
      • Set DTR_Source = No unit
      • Set DTR_SourceParam = No unit
      • Set DTR_SourceArr[0] = No unit
      • Set DTR_Target = No unit
      • Set DTR_TargetParam = No unit
      • Set DTR_TargetArr[0] = No unit
      • Set DTR_TotalDamageTaken = 0.00
      • Set DTR_TotalSpellDamageTaken = 0.00
      • Set DTR_TotalPhysDamageTaken = 0.00
      • Set DTR_TotalUnitDamage = 0.00
      • Set DTR_TotalSpellDamage = 0.00
      • Set DTR_TotalPhysicalDamage = 0.00
      • Set DTR_TotalPlayerDamage[0] = 0.00
      • Set DTR_TotalPlayerPhysDamage[0] = 0.00
      • Set DTR_TotalPlayerSpellDamage[0] = 0.00
      • Set DTR_TotalPlayerDamageT[0] = 0.00
      • Set DTR_TotalPlayerPhysDamageT[0] = 0.00
      • Set DTR_TotalPlayerSpellDamageT[0] = 0.00
      • Set DTR_Table = DTR_Table
      • Set DTR_TrackEvent = 0.00
      • Set DTR_ActivePairs = 0
      • Set DTR_TimeCounter[0] = 0.00
      • Set DTR_Timer = DTR_Timer
      • Set DTR_TargetPairGroup[0] = DTR_TargetPairGroup[0]
      • Set DTR_Sources = DTR_Sources
      • Set DTR_TopContributor = No unit
      • Set DTR_TopPhysContributor = No unit
      • Set DTR_TopSpellContributor = No unit
      • Set DTR_TopContribution = 0.00
      • Set DTR_TopPhysContribution = 0.00
      • Set DTR_TopSpellContribution = 0.00
      • Set DTR_GetData = (This trigger)

JASS:
//===========================================================================
// Rheiko Presents
// Damage Tracker v1.2.3
//===========================================================================
//
// Requirements:
//  - Unit Indexer by Bribe
//  - Damage Engine v3.8 by Bribe
//
// Damage Tracker allows you to easily retrieve a total amount of damage that
// a unit has dealt by using Damage Engine to detect when a unit takes damage 
// then store the damage information into a hashtable. When the unit taking 
// damage dies, the system clean up the information.
//
// Features:
// - Allows you to retrieve Total Overall/Spell/Physical Damage dealt by a unit
// - Allows you to retrieve Total Overall/Spell/Physical Damage taken by a unit
// - Allows you to retrieve Damage Contribution (in percentage %) by a unit
// - Allows you to retrieve Total Damage dealt by a player
//
// In order to keep memory usage minimum and used only as necessary, the system
// requires you to register the units whose damage you wish to track first. To 
// do so, simply set DTR_IsRegistered[(Custom value of (Your unit))] = True
//
// Damage Tracker has custom events that can help you retrieve damage info,
// they are:
// - DTR_TrackerEvent Equal to 1.00
// - DTR_TrackerEvent Equal to 2.00
//
// You can treat DTR_TrackerEvent Equal to 1.00 just like DamageEvent Equal to 1.00
// (because it actually is based on that) from here, you can retrieve damage dealt
// and damage taken by the actors corresponding to these events, they are DTR_Source
// and DTR_Target.
//
// You can treat DTR_TrackerEvent Equal to 2.00 just like Unit - A unit dies event.
// However, in this event and in this event only, you can also retrieve Damage 
// Contribution by a unit.
//
// Finally, you have a variable called DTR_TotalPlayerDamage[PlayerId] which
// contain the total damage done by each player. This variable is an array 
// and the index is based on PlayerId. Be noted that the index starts from 0.
// 
// Make sure to check the examples to further explore the capability of this system.
// If you have any feedbacks or suggestions, feel free to let me know at
// https://www.hiveworkshop.com/members/rheiko.232216/
//
//===========================================================================


//=========== API ============

function GetDamageTrackerData takes nothing returns boolean
    local integer SourceKey
    local integer TargetKey    

    if udg_DTR_SourceParam != null and udg_DTR_TargetParam != null then
        set SourceKey = GetHandleId(udg_DTR_SourceParam)
        set TargetKey = GetHandleId(udg_DTR_TargetParam)

        // Load all the total damage taken
        set udg_DTR_TotalDamageTaken = LoadReal(udg_DTR_Table, 0, TargetKey)
        set udg_DTR_TotalSpellDamageTaken = LoadReal(udg_DTR_Table, 1, TargetKey)
        set udg_DTR_TotalPhysDamageTaken = LoadReal(udg_DTR_Table, 2, TargetKey)

        // Load all the total damage dealt
        set udg_DTR_TotalUnitDamage = LoadReal(udg_DTR_Table, TargetKey, SourceKey)
        set udg_DTR_TotalSpellDamage = LoadReal(udg_DTR_Table, TargetKey + 100000, SourceKey)
        set udg_DTR_TotalPhysicalDamage = LoadReal(udg_DTR_Table, TargetKey + 200000, SourceKey)
     
        return true
    endif

    return false
endfunction


//===========================================================================

function IsUnitAlive takes unit u returns boolean    
    return (GetWidgetLife(u) > 0.405) 
endfunction

function DTR_ValidateUnit takes unit Source, unit Target returns boolean
    local integer Mode = udg_DTR_RegistrationMode
    local integer SourceId = GetUnitUserData(Source)
    local integer TargetId = GetUnitUserData(Target)

    return (Mode == 1 and not udg_DTR_IsRegistered[SourceId]) or (Mode == 2 and not udg_DTR_IsRegistered[TargetId]) or (Mode == 3 and (not udg_DTR_IsRegistered[SourceId] or not udg_DTR_IsRegistered[TargetId]))
endfunction

function DTR_ClearTableData takes integer TargetKey returns nothing
    call RemoveSavedReal(udg_DTR_Table, 0, TargetKey)
    call RemoveSavedReal(udg_DTR_Table, 1, TargetKey)
    call RemoveSavedReal(udg_DTR_Table, 2, TargetKey)
    call RemoveSavedReal(udg_DTR_Table, 3, TargetKey)
    call RemoveSavedReal(udg_DTR_Table, 4, TargetKey)

    call FlushChildHashtable(udg_DTR_Table, TargetKey)
    call FlushChildHashtable(udg_DTR_Table, TargetKey + 100000)
    call FlushChildHashtable(udg_DTR_Table, TargetKey + 200000)

endfunction

function DTR_LoadTableData takes integer SourceKey, integer TargetKey returns nothing
    // Load all the total damage taken
    set udg_DTR_TotalDamageTaken = LoadReal(udg_DTR_Table, 0, TargetKey)
    set udg_DTR_TotalSpellDamageTaken = LoadReal(udg_DTR_Table, 1, TargetKey)
    set udg_DTR_TotalPhysDamageTaken = LoadReal(udg_DTR_Table, 2, TargetKey)

    // Load all the total damage dealt
    set udg_DTR_TotalUnitDamage = LoadReal(udg_DTR_Table, TargetKey, SourceKey)
    set udg_DTR_TotalSpellDamage = LoadReal(udg_DTR_Table, TargetKey + 100000, SourceKey)
    set udg_DTR_TotalPhysicalDamage = LoadReal(udg_DTR_Table, TargetKey + 200000, SourceKey)
endfunction

function DTR_UpdateTopContributor takes unit source, real damage, integer damageType returns nothing
    if damageType == 1 then
        if damage > udg_DTR_TopContribution then
            set udg_DTR_TopContribution = damage
            set udg_DTR_TopContributor = source
        endif
    endif

    if damageType == 2 then
        if damage > udg_DTR_TopSpellContribution then
            set udg_DTR_TopSpellContribution = damage
            set udg_DTR_TopSpellContributor = source
        endif
    endif

    if damageType == 3 then
        if damage > udg_DTR_TopPhysContribution then
            set udg_DTR_TopPhysContribution = damage
            set udg_DTR_TopPhysContributor = source
        endif
    endif
    
endfunction

function DTR_TimerCallback takes nothing returns nothing
    // Assign locals
    local integer i = 0
    local integer SourceKey
    local integer TargetKey
    local integer TargetId

    // Loop through active pairs
    loop
        set i = i + 1
        exitwhen i > udg_DTR_ActivePairs

        set SourceKey = GetHandleId(udg_DTR_SourceArr[i])
        set TargetKey = GetHandleId(udg_DTR_TargetArr[i])

        // Increase counter if its not expired yet
        if udg_DTR_TimeCounter[i] < udg_DTR_CleanupTime and IsUnitAlive(udg_DTR_TargetArr[i]) then

            set udg_DTR_TimeCounter[i] = LoadReal(udg_DTR_Table, 4, TargetKey)
            set udg_DTR_TimeCounter[i] = udg_DTR_TimeCounter[i] + 1
            call SaveReal(udg_DTR_Table, 4, TargetKey, udg_DTR_TimeCounter[i])

        else
        
            // Clear data
            if IsUnitAlive(udg_DTR_TargetArr[i]) then     
                set TargetId = GetUnitUserData(udg_DTR_TargetArr[i])
                call DTR_ClearTableData(TargetKey)
                call GroupRemoveUnit(udg_DTR_TargetPairGroup[TargetId], udg_DTR_SourceArr[i])
            endif
            
            // Deindex
            set udg_DTR_SourceArr[i] = udg_DTR_SourceArr[udg_DTR_ActivePairs]
            set udg_DTR_SourceArr[udg_DTR_ActivePairs] = null
            set udg_DTR_TargetArr[i] = udg_DTR_TargetArr[udg_DTR_ActivePairs]
            set udg_DTR_TargetArr[udg_DTR_ActivePairs] = null
            set udg_DTR_TimeCounter[i] = udg_DTR_TimeCounter[udg_DTR_ActivePairs]

            set i = i - 1
            set udg_DTR_ActivePairs = udg_DTR_ActivePairs - 1

            if udg_DTR_ActivePairs == 0 then
                call PauseTimer(udg_DTR_Timer)
            endif
        endif
    endloop
endfunction

function DTR_OnUnitDeath takes nothing returns boolean
    // Assign locals
    local unit Killer = GetKillingUnit()
    local unit Target = GetTriggerUnit()
    local integer KillerKey = GetHandleId(Killer)
    local integer TargetKey = GetHandleId(Target)  
    local integer TargetId = GetUnitUserData(Target)
    local integer SourceKey
    local integer SourceId
    local group g
    local unit u
    
    // Validate whether source and/or target registered
    if DTR_ValidateUnit(Killer, Target)  then
        set Killer = null
        set Target = null
        return false
    endif
    
    // Loop through the group owned by the target which contains sources
    set g = udg_DTR_TargetPairGroup[TargetId]
    set u = FirstOfGroup(g)
    loop            
        set SourceKey = GetHandleId(u)
        set SourceId = GetUnitUserData(u)
        
        // Load data of each source
        call DTR_LoadTableData(SourceKey, TargetKey)

        set udg_DTR_OverallContribution[SourceId] = 0.0
        set udg_DTR_SpellContribution[SourceId] = 0.0
        set udg_DTR_PhysContribution[SourceId] = 0.0

        // Ensure total damage taken is not 0
        if udg_DTR_TotalDamageTaken > 0.0 then
            set udg_DTR_OverallContribution[SourceId] = (udg_DTR_TotalUnitDamage / udg_DTR_TotalDamageTaken) * 100
        endif

        if udg_DTR_TotalSpellDamageTaken > 0.0 then
            set udg_DTR_SpellContribution[SourceId] = (udg_DTR_TotalSpellDamage / udg_DTR_TotalSpellDamageTaken) * 100
        endif

        if udg_DTR_TotalPhysDamageTaken > 0.0 then
            set udg_DTR_PhysContribution[SourceId] = (udg_DTR_TotalPhysicalDamage / udg_DTR_TotalPhysDamageTaken) * 100
        endif

        // Calculate top contributors
        call DTR_UpdateTopContributor(u, udg_DTR_OverallContribution[SourceId], 1)
        call DTR_UpdateTopContributor(u, udg_DTR_SpellContribution[SourceId], 2)
        call DTR_UpdateTopContributor(u, udg_DTR_PhysContribution[SourceId], 3)
        
        // Add them to a temp group for accessibility
        call GroupAddUnit(udg_DTR_Sources, u)
        
        call GroupRemoveUnit(g, u)        
        set u = FirstOfGroup(g)
        exitwhen u==null
    endloop   
    
    // Load data of the killer
    call DTR_LoadTableData(KillerKey, TargetKey)
    
    set udg_DTR_Source = Killer
    set udg_DTR_Target = Target
    
    set udg_DTR_TrackEvent = 0.0 
    set udg_DTR_TrackEvent = 2.0
    set udg_DTR_TrackEvent = 0.0 
    
    // Reset data
    
    set g = udg_DTR_TargetPairGroup[TargetId]
    set u = FirstOfGroup(g)
    loop              
        set SourceId = GetUnitUserData(u)

        set udg_DTR_OverallContribution[SourceId] = 0.0
        set udg_DTR_SpellContribution[SourceId] = 0.0
        set udg_DTR_PhysContribution[SourceId] = 0.0
        
        call GroupRemoveUnit(g, u)
        set u = FirstOfGroup(g)
        exitwhen u==null
    endloop

    set udg_DTR_TopContribution = 0.0
    set udg_DTR_TopSpellContribution = 0.0
    set udg_DTR_TopPhysContribution = 0.0
    
    set udg_DTR_TotalDamageTaken = 0.0
    set udg_DTR_TotalUnitDamage = 0.0
    
    set udg_DTR_TotalSpellDamageTaken = 0.0
    set udg_DTR_TotalSpellDamage = 0.0
    
    set udg_DTR_TotalPhysDamageTaken = 0.0
    set udg_DTR_TotalPhysicalDamage = 0.0

    set udg_DTR_Source = null
    set udg_DTR_Target = null
    
    call DTR_ClearTableData(TargetKey)
    
    call DestroyGroup(udg_DTR_TargetPairGroup[TargetId])    
    set udg_DTR_TargetPairGroup[TargetId] = null
    
    call GroupClear(udg_DTR_Sources)

    set g = null
    
    set Killer = null
    set Target = null
    
    return false
endfunction

function DTR_BeforeUnitDamage takes nothing returns boolean
    // Assign locals
    local unit Source = udg_DamageEventSource
    local unit Target = udg_DamageEventTarget
    local integer TargetKey = GetHandleId(Target)
    local real TargetHP = GetUnitState(Target, UNIT_STATE_LIFE)
    
    // Validate whether source and/or target registered
    if DTR_ValidateUnit(Source, Target) then
        set Source = null
        set Target = null
        return false
    endif

    // Save HP value before taking damage for comparison later
    call SaveReal(udg_DTR_Table, 3, TargetKey, TargetHP)

    set Source = null
    set Target = null
    return false
endfunction

function DTR_OnUnitDamage takes nothing returns boolean
    // Assign locals
    local unit Source = udg_DamageEventSource
    local unit Target = udg_DamageEventTarget
    local integer SourceKey = GetHandleId(Source)
    local integer TargetKey = GetHandleId(Target)
    local integer SourcePId = GetPlayerId(GetOwningPlayer(Source))
    local integer TargetPId = GetPlayerId(GetOwningPlayer(Target))
    local integer TargetId = GetUnitUserData(Target)
    local real TempDmg = udg_DamageEventAmount
    local real TargetHP = 0.0

    // Validate whether source and/or target registered
    if DTR_ValidateUnit(Source, Target) then
        set Source = null
        set Target = null
        return false
    endif

    // -> Load data <-    
    set TargetHP = LoadReal(udg_DTR_Table, 3, TargetKey)

    // Damage Correction when damage value is over current HP value
    if TargetHP <= TempDmg then
        set TempDmg = TargetHP
    endif
    
    call DTR_LoadTableData(SourceKey, TargetKey)
    
    // Prepares a group to contain all the damage sources
    if udg_DTR_TargetPairGroup[TargetId] == null then
        set udg_DTR_TargetPairGroup[TargetId] = CreateGroup()
    endif
    
    // Add the source if its not already in the group
    if not IsUnitInGroup(Source, udg_DTR_TargetPairGroup[TargetId]) then
        call GroupAddUnit(udg_DTR_TargetPairGroup[TargetId], Source)
    endif

    // Start a timer if auto clean is on
    if udg_DTR_TotalDamageTaken == 0.0 and udg_DTR_AutoCleanup == true then
        
        // New entry
        set udg_DTR_ActivePairs = udg_DTR_ActivePairs + 1
        set udg_DTR_TimeCounter[udg_DTR_ActivePairs] = 0.0
        set udg_DTR_SourceArr[udg_DTR_ActivePairs] = Source
        set udg_DTR_TargetArr[udg_DTR_ActivePairs] = Target
        
        if udg_DTR_ActivePairs == 1 then
            call TimerStart(udg_DTR_Timer, udg_DTR_TimeInterval, true, function DTR_TimerCallback)
        endif
    endif

    // Update Data

    call SaveReal(udg_DTR_Table, 4, TargetKey, 0.0) // Reset the counter to prolong its timer

    set udg_DTR_TotalPlayerDamage[SourcePId] = udg_DTR_TotalPlayerDamage[SourcePId] + TempDmg
    set udg_DTR_TotalPlayerDamageT[TargetPId] = udg_DTR_TotalPlayerDamageT[TargetPId] + TempDmg
    
    set udg_DTR_TotalDamageTaken = udg_DTR_TotalDamageTaken + TempDmg
    call SaveReal(udg_DTR_Table, 0, TargetKey, udg_DTR_TotalDamageTaken)

    set udg_DTR_TotalUnitDamage = udg_DTR_TotalUnitDamage + TempDmg
    call SaveReal(udg_DTR_Table, TargetKey, SourceKey, udg_DTR_TotalUnitDamage)

    if udg_IsDamageSpell == true then        
        set udg_DTR_TotalSpellDamageTaken = udg_DTR_TotalSpellDamageTaken + TempDmg
        call SaveReal(udg_DTR_Table, 1, TargetKey, udg_DTR_TotalSpellDamageTaken)
 
        set udg_DTR_TotalSpellDamage = udg_DTR_TotalSpellDamage + TempDmg
        call SaveReal(udg_DTR_Table, TargetKey + 100000, SourceKey, udg_DTR_TotalSpellDamage)

        set udg_DTR_TotalPlayerSpellDamage[SourcePId] = udg_DTR_TotalPlayerSpellDamage[SourcePId] + TempDmg
        set udg_DTR_TotalPlayerSpellDamageT[TargetPId] = udg_DTR_TotalPlayerSpellDamageT[TargetPId] + TempDmg

    else
        set udg_DTR_TotalPhysDamageTaken = udg_DTR_TotalPhysDamageTaken + TempDmg
        call SaveReal(udg_DTR_Table, 2, TargetKey, udg_DTR_TotalPhysDamageTaken)

        set udg_DTR_TotalPhysicalDamage = udg_DTR_TotalPhysicalDamage + TempDmg
        call SaveReal(udg_DTR_Table, TargetKey + 200000, SourceKey, udg_DTR_TotalPhysicalDamage)

        set udg_DTR_TotalPlayerPhysDamage[SourcePId] = udg_DTR_TotalPlayerPhysDamage[SourcePId] + TempDmg
        set udg_DTR_TotalPlayerPhysDamageT[TargetPId] = udg_DTR_TotalPlayerPhysDamageT[TargetPId] + TempDmg

    endif        

    set udg_DTR_Source = Source
    set udg_DTR_Target = Target

    set udg_DTR_TrackEvent = 0.0
    set udg_DTR_TrackEvent = 1.0
    set udg_DTR_TrackEvent = 0.0 

    // Reset data
    set udg_DTR_TotalDamageTaken = 0.0
    set udg_DTR_TotalUnitDamage = 0.0
    
    set udg_DTR_TotalSpellDamageTaken = 0.0
    set udg_DTR_TotalSpellDamage = 0.0
    
    set udg_DTR_TotalPhysDamageTaken = 0.0
    set udg_DTR_TotalPhysicalDamage = 0.0
    
    set udg_DTR_Source = null
    set udg_DTR_Target = null

    set Source = null
    set Target = null
    return false
endfunction

//===========================================================================
function InitTrig_Damage_Tracker takes nothing returns nothing
    local integer i = bj_MAX_PLAYERS
    local trigger mainTrg = CreateTrigger()
    local trigger secondTrg = CreateTrigger()
    local trigger deathTrg = CreateTrigger()
    
    set udg_DTR_GetData = CreateTrigger()
    call TriggerAddCondition( udg_DTR_GetData, Condition( function GetDamageTrackerData ) )

    call TriggerRegisterVariableEvent(mainTrg, "udg_DamageEvent", EQUAL, 1.00)
    call TriggerRegisterVariableEvent(secondTrg, "udg_DamageModifierEvent", EQUAL, 1.00)
    loop
        set i = i - 1
        call TriggerRegisterPlayerUnitEvent(deathTrg, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
        exitwhen i == 0
    endloop
    call TriggerAddCondition( mainTrg, Condition( function DTR_OnUnitDamage ))
    call TriggerAddCondition( secondTrg, Condition( function DTR_BeforeUnitDamage ))
    call TriggerAddCondition( deathTrg, Condition( function DTR_OnUnitDeath ))

    set udg_DTR_Table = InitHashtable()

    set mainTrg = null
    set secondTrg = null
    set deathTrg = null
endfunction

Register

Unit takes damage

Unit dies

Get Player Data

Get Unit Data


  • Register
    • Events
      • Player - Player 1 (Red) types a chat message containing <Empty String> as A substring
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Entered chat string) Equal to -reg
        • Then - Actions
          • Unit Group - Pick every unit in (Units currently selected by Player 1 (Red)) and do (Actions)
            • Loop - Actions
              • Game - Display to (All players) for 30.00 seconds the text: (Registering ... + (Name of (Picked unit)))
              • Set DTR_IsRegistered[(Custom value of (Picked unit))] = True
              • Game - Display to (All players) for 30.00 seconds the text: Registered!
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Entered chat string) Equal to -unreg
            • Then - Actions
              • Unit Group - Pick every unit in (Units currently selected by Player 1 (Red)) and do (Actions)
                • Loop - Actions
                  • Game - Display to (All players) for 30.00 seconds the text: (Unregistering ... + (Name of (Picked unit)))
                  • Set DTR_IsRegistered[(Custom value of (Picked unit))] = False
                  • Game - Display to (All players) for 30.00 seconds the text: Unregistered!
            • Else - Actions

  • Test
    • Events
      • Game - DTR_TrackEvent becomes Equal to 1.00
    • Conditions
    • Actions
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: ((Name of DTR_Source) + ( has dealt a total of + ((String(DTR_TotalUnitDamage)) + ( overall damage to + (Name of DTR_Target)))))
      • Game - Display to (All players) the text: ((Name of DTR_Source) + ( has dealt a total of + ((String(DTR_TotalSpellDamage)) + ( spell damage to + (Name of DTR_Target)))))
      • Game - Display to (All players) the text: ((Name of DTR_Source) + ( has dealt a total of + ((String(DTR_TotalPhysicalDamage)) + ( physical damage to + (Name of DTR_Target)))))
      • Game - Display to (All players) the text: -------------------...

  • Test2
    • Events
      • Game - DTR_TrackEvent becomes Equal to 2.00
    • Conditions
    • Actions
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: (Total Overall Damage of Killing Unit: + (String(DTR_TotalUnitDamage)))
      • Game - Display to (All players) the text: (Total Spell Damage of Killing Unit: + (String(DTR_TotalSpellDamage)))
      • Game - Display to (All players) the text: (Total Physical Damage of Killing Unit: + (String(DTR_TotalPhysicalDamage)))
      • Game - Display to (All players) the text: -------------------...
      • Unit Group - Pick every unit in DTR_Sources and do (Actions)
        • Loop - Actions
          • Game - Display to (All players) the text: -------------------...
          • Game - Display to (All players) the text: ((Total Overall Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_OverallContribution[(Custom value of (Picked unit))])) + %))
          • Game - Display to (All players) the text: ((Total Spell Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_SpellContribution[(Custom value of (Picked unit))])) + %))
          • Game - Display to (All players) the text: ((Total Physical Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_PhysContribution[(Custom value of (Picked unit))])) + %))
          • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: (Top Overall Damage Contributor is: + ((Name of DTR_TopContributor) + (, with their contribution: + ((String(DTR_TopContribution)) + %))))
      • Game - Display to (All players) the text: (TopSpell Damage Contributor is: + ((Name of DTR_TopSpellContributor) + (, with their contribution: + ((String(DTR_TopSpellContribution)) + %))))
      • Game - Display to (All players) the text: (Top Physical Damage Contributor is: + ((Name of DTR_TopPhysContributor) + (, with their contribution: + ((String(DTR_TopPhysContribution)) + %))))
      • Game - Display to (All players) the text: -------------------...

  • Test3
    • Events
      • Player - Player 1 (Red) skips a cinematic sequence
    • Conditions
    • Actions
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: (Player 1 Total Overall Damage: + (String(DTR_TotalPlayerDamage[((Player number of (Triggering player)) - 1)])))
      • Game - Display to (All players) the text: (Player 1 Total Spell Damage: + (String(DTR_TotalPlayerSpellDamage[((Player number of (Triggering player)) - 1)])))
      • Game - Display to (All players) the text: (Player 1 Total Physical Damage: + (String(DTR_TotalPlayerPhysDamage[((Player number of (Triggering player)) - 1)])))
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: (Player 1 Total Overall Damage Taken: + (String(DTR_TotalPlayerDamageT[((Player number of (Triggering player)) - 1)])))
      • Game - Display to (All players) the text: (Player 1 Total Spell Damage Taken: + (String(DTR_TotalPlayerSpellDamageT[((Player number of (Triggering player)) - 1)])))
      • Game - Display to (All players) the text: (Player 1 Total Physical Damage Taken: + (String(DTR_TotalPlayerPhysDamageT[((Player number of (Triggering player)) - 1)])))
      • Game - Display to (All players) the text: -------------------...

  • Get Data
    • Events
      • Player - Player 1 (Red) types a chat message containing -get as An exact match
    • Conditions
    • Actions
      • Set DTR_SourceParam = Blademaster 0003 <gen>
      • Set DTR_TargetParam = Footman 0007 <gen>
      • Trigger - Run DTR_GetData (checking conditions)
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: (Total Overall Damage Dealt By + ((Name of DTR_SourceParam) + ( = + (String(DTR_TotalUnitDamage)))))
      • Game - Display to (All players) the text: (Total Spell Damage Dealt By + ((Name of DTR_SourceParam) + ( = + (String(DTR_TotalSpellDamage)))))
      • Game - Display to (All players) the text: (Total Physical Damage Dealt By + ((Name of DTR_SourceParam) + ( = + (String(DTR_TotalPhysicalDamage)))))
      • Game - Display to (All players) the text: (Total Overall Damage Taken By + ((Name of DTR_TargetParam) + ( = + (String(DTR_TotalDamageTaken)))))
      • Game - Display to (All players) the text: (Total Spell Damage Taken By + ((Name of DTR_TargetParam) + ( = + (String(DTR_TotalSpellDamageTaken)))))
      • Game - Display to (All players) the text: (Total Physical Damage Taken By + ((Name of DTR_TargetParam) + ( = + (String(DTR_TotalPhysDamageTaken)))))
      • Game - Display to (All players) the text: -------------------...


v1.2.3
  • Merged GetDamageTrackerData trigger into DamageTracker trigger
  • You can now run GetData (Trigger Variable) instead
  • Added functionality to get the Top Contributor when target dies (Overall/Phys/Spell)
  • Added functionality to get the contribution of Top Contributor when target dies (Overall/Phys/Spell)
  • Adjusted the examples
v1.2.2
  • Fixed minor bug (Total Player Damage tracked incorrect damage)
  • Reset the data after an event has passed
  • You can now get the list of all damage contributors through DTR_Sources (Unit Group)
  • Changed Overall/Phys/SpellDamagePercentage variable names into Overall/Phys/SpellContribution
  • Changed the Contribution variables into arrays and use unit's custom value as the index

v1.2.1
  • The system now also stores Total Damage Taken of each players
v1.2
  • Added Registration Mode to configure whether damage source and/or damage target need to be registered
  • Re-added Timed Cleanup
  • Some code adjustments
v1.1
  • Switched to Hashtable
  • Added GetDamageTrackerData Trigger
  • Removed Timed Cleanup
  • Now requires user to register unit first by using DTR_IsRegistered[Unit Custom Value]
  • System stores Total Damage Taken by a target
  • System stores Total Spell and Physical Damage dealt by players
  • Added more examples
v1.0
  • First Public Release



  • Special thanks to Bribe for making this possible by taking advantage of his Unit Indexer and Damage Engine.


Git: Wc3DamageTracker
Previews
Contents

Damage Tracker v1.2.2 (Map)

Reviews
Antares
The API is somewhat restrictive in what the user can do. When the unit dies, you can only get the damage contribution of the killing unit. There's no function to get the list of all units that contributed damage, or a function to get the highest...
Why no Hashtable? 😂

On more serious note, does the O(N) from array is still faster than utilizing Hashtable to map the pairs and utilize the O(1) lookup Hashtable have?

Or maybe, as an expansion of the system, limits the unit that gets registered into the system? Some sort of register trigger might allow this to be suitable even in maps with tons of unit, since then we can isolate which units can trigger the system from the rest.

Anyway, big kudos for this contribution. Definitely something I can use for my maps 👍
 

Rheiko

Spell Reviewer
Level 26
Joined
Aug 27, 2013
Messages
4,214
Honestly, I am having a hard time deciding between the two. Since at first, this was meant for a smaller scale battle in my map, I went with array (thinking hashtable would be a bit overkill for such simple system) but after I made it work, I questioned myself if I should just go with hashtable all the way for faster lookup time. Uploaded it for some feedbacks as well to help me decide between the two.

On more serious note, does the O(N) from array is still faster than utilizing Hashtable to map the pairs and utilize the O(1) lookup Hashtable have?
I believe for smaller dataset it should be still faster considering the overhead hashtable has, although I have no proof. If not for that, I would confidently go for hashtable from the beginning. And also I didn't expect so many active pairings at once. The more reason I was leaning toward array.

Or maybe, as an expansion of the system, limits the unit that gets registered into the system? Some sort of register trigger might allow this to be suitable even in maps with tons of unit, since then we can isolate which units can trigger the system from the rest.
That's a great idea! I'll see what I can do.

Anyway, big kudos for this contribution. Definitely something I can use for my maps 👍
Thanks, I'll try to improve it so you can use it lmao.

Loops in JASS tend to be very slow and hashtables will almost always be faster.
Really? I remember people used to have some debate on array vs hashtable with array being faster. But I guess for lookup, that's just common sense. Stupid me.
Anyway, I guess switching to hashtable it is ~
 

Rheiko

Spell Reviewer
Level 26
Joined
Aug 27, 2013
Messages
4,214
Updated to v1.1
  • Switched to Hashtable
  • Added GetDamageTrackerData Trigger, you can run this trigger to get damage info of the units you specify
  • Removed Timed Cleanup, I have not found a way to do this with hashtable yet. Probably use unit groups. Idk.
  • Now requires user to register unit first by using DTR_IsRegistered[Unit Custom Value]
  • System stores Total Damage Taken by a target as well now
  • System stores Total Spell and Physical Damage dealt by players as well now
  • Added more examples and refined the testmap a bit
I don't usually use hashtable, hope I'm doing it right. Feedbacks and suggestions are very much welcomed!
 
This should have the GUI tag, not the JASS tag, as the API is written to be used by GUI-users.

I think whether the damage source and/or damage target need to be registered should be a configurable option.

JASS:
    // Ensure values are reset before loading data
    set udg_DTR_TotalUnitDamage = 0.0
    set udg_DTR_TotalSpellDamage = 0.0
    set udg_DTR_TotalPhysicalDamage = 0.0
    set udg_DTR_TotalDamageTaken = 0.0
    set udg_DTR_TotalSpellDamageTaken = 0.0
    set udg_DTR_TotalPhysDamageTaken = 0.0
These lines don't do anything as far as I can tell because they are overwritten shortly after.
 
The API is somewhat restrictive in what the user can do. When the unit dies, you can only get the damage contribution of the killing unit. There's no function to get the list of all units that contributed damage, or a function to get the highest contributor for example. In vJASS, the best way to implement this would be to link a struct to each unit via a hashtable and have an array for the damage contributions of individual units. Since this is for GUI, there are additional limitations to this which I'm not quite sure how you would deal with.

These limitations make it less likely that it does exactly what a user might be looking for. However, what the system does do it does very well, and the documentation is well-written and plenty of examples are given in the test map.

Approved
 

Rheiko

Spell Reviewer
Level 26
Joined
Aug 27, 2013
Messages
4,214
Updated to v1.2.2
  • Fixed minor bug (Total Player Damage tracked incorrect damage)
  • The system reset the data after event has passed
  • You can now get the list of all damage contributors through DTR_Sources (Unit Group)
  • Changed Overall/Phys/SpellDamagePercentage variable names into Overall/Phys/SpellContribution
  • Changed the Contribution variables into arrays and use unit's custom value as the index

This system still has a lot of room for improvement and I'm excited to make it even better! Thanks to Antares, I came up with a way to provide the list of all units that contributed damage. I had trouble trying to implement a way to get the highest/lowest contributor, it shouldn't be hard but I guess I'll keep it for the next update. Enjoy!

:peasant-cheers-back:
 
Level 3
Joined
Feb 28, 2025
Messages
11
Very Nice and useful System.
The only thing I would change is that you dont add the Killer to the DTR_Sources Grp so the Killer doesnt count as "Assister".

Something like:
if not (u == Killer) then
call GroupAddUnit(udg_DTR_Sources, u)
endif
 

Rheiko

Spell Reviewer
Level 26
Joined
Aug 27, 2013
Messages
4,214
Actually, the killer is already inside DTR_Sources. I added the killer into the group when they kill the target. You just didn't notice. :p
You can use this example here:
  • Example
    • Events
      • Game - DTR_TrackEvent becomes Equal to 2.00
    • Conditions
    • Actions
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: ((Total Overall Damage Contribution of + ((Name of DTR_Source) + : )) + ((String(DTR_OverallContribution[(Custom value of DTR_Source)])) + %))
      • Game - Display to (All players) the text: ((Total Spell Damage Contribution of + ((Name of DTR_Source) + : )) + ((String(DTR_SpellContribution[(Custom value of DTR_Source)])) + %))
      • Game - Display to (All players) the text: ((Total Physical Damage Contribution of + ((Name of DTR_Source) + : )) + ((String(DTR_PhysContribution[(Custom value of DTR_Source)])) + %))
      • Game - Display to (All players) the text: -------------------...
DTR_Source = Killer

But thank you for the kind comment and rating! Appreciate it much!
:peasant-grin:

Edit: I think I might have misunderstood what you mean. I think what you mean is that I shouldn't add the killer into the group? So the group is only for the assisters? That's not exactly the purpose of the system BUT you can achieve that result by doing this:
  • Example
    • Events
      • Game - DTR_TrackEvent becomes Equal to 2.00
    • Conditions
    • Actions
      • -------- DTR_Source = Killing Unit --------
      • -------- DTR_Target = Dying Unit --------
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: ((Name of DTR_Source) + ( has killed + (Name of DTR_Target)))
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: ASSISTERS
      • Game - Display to (All players) the text: -------------------...
      • Unit Group - Pick every unit in DTR_Sources and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Picked unit) Not equal to DTR_Source
            • Then - Actions
              • Game - Display to (All players) the text: -------------------...
              • Game - Display to (All players) the text: ((Total Overall Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_OverallContribution[(Custom value of (Picked unit))])) + %))
              • Game - Display to (All players) the text: ((Total Spell Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_SpellContribution[(Custom value of (Picked unit))])) + %))
              • Game - Display to (All players) the text: ((Total Physical Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_PhysContribution[(Custom value of (Picked unit))])) + %))
              • Game - Display to (All players) the text: -------------------...
            • Else - Actions
This will filter out the killer from the rest of the contributors aka assisters.
 
Last edited:
Level 3
Joined
Feb 28, 2025
Messages
11
Actually, the killer is already inside DTR_Sources. I added the killer into the group when they kill the target. You just didn't notice. :p
You can use this example here:
  • Example
    • Events
      • Game - DTR_TrackEvent becomes Equal to 2.00
    • Conditions
    • Actions
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: ((Total Overall Damage Contribution of + ((Name of DTR_Source) + : )) + ((String(DTR_OverallContribution[(Custom value of DTR_Source)])) + %))
      • Game - Display to (All players) the text: ((Total Spell Damage Contribution of + ((Name of DTR_Source) + : )) + ((String(DTR_SpellContribution[(Custom value of DTR_Source)])) + %))
      • Game - Display to (All players) the text: ((Total Physical Damage Contribution of + ((Name of DTR_Source) + : )) + ((String(DTR_PhysContribution[(Custom value of DTR_Source)])) + %))
      • Game - Display to (All players) the text: -------------------...
DTR_Source = Killer

But thank you for the kind comment and rating! Appreciate it much!
:peasant-grin:

Edit: I think I might have misunderstood what you mean. I think what you mean is that I shouldn't add the killer into the group? So the group is only for the assisters? That's not exactly the purpose of the system BUT you can achieve that result by doing this:
  • Example
    • Events
      • Game - DTR_TrackEvent becomes Equal to 2.00
    • Conditions
    • Actions
      • -------- DTR_Source = Killing Unit --------
      • -------- DTR_Target = Dying Unit --------
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: ((Name of DTR_Source) + ( has killed + (Name of DTR_Target)))
      • Game - Display to (All players) the text: -------------------...
      • Game - Display to (All players) the text: ASSISTERS
      • Game - Display to (All players) the text: -------------------...
      • Unit Group - Pick every unit in DTR_Sources and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Picked unit) Not equal to DTR_Source
            • Then - Actions
              • Game - Display to (All players) the text: -------------------...
              • Game - Display to (All players) the text: ((Total Overall Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_OverallContribution[(Custom value of (Picked unit))])) + %))
              • Game - Display to (All players) the text: ((Total Spell Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_SpellContribution[(Custom value of (Picked unit))])) + %))
              • Game - Display to (All players) the text: ((Total Physical Damage Contribution of + ((Name of (Picked unit)) + : )) + ((String(DTR_PhysContribution[(Custom value of (Picked unit))])) + %))
              • Game - Display to (All players) the text: -------------------...
            • Else - Actions
This will filter out the killer from the rest of the contributors aka assisters.
Yea thats what i meant im sorry i also wrote it wrong :D glad u understood it tho

Edit: Isn't the DTR_Source group meant to be to track the contributing mates? So the killer doesn't need to be in it since he is already in the DTR_TargetPairGroup like all other sources attacking the same enemy.

I was testing the PlayerTotalDamage and it delivers a wrong number i guess. When i attack the same target with 2 heroes shouldnt the outcome be the addition of both TotalUnitDamage?
 
Last edited:

Rheiko

Spell Reviewer
Level 26
Joined
Aug 27, 2013
Messages
4,214
Edit: Isn't the DTR_Source group meant to be to track the contributing mates? So the killer doesn't need to be in it since he is already in the DTR_TargetPairGroup like all other sources attacking the same enemy.
The purpose of DTR_Sources (unit group) is to track all the damage sources of a target when it dies, this includes the killer because he's also one of the sources. DTR_TargetPairGroup is not exactly meant to be used by the user as it is an array, uniquely belongs to each target and only stores the damage sources without damage contribution info (since this can only be calculated when the target dies for the best accuracy), and it will be emptied out and transferred to DTR_Sources during the calculation process.

I was testing the PlayerTotalDamage and it delivers a wrong number i guess. When i attack the same target with 2 heroes shouldnt the outcome be the addition of both TotalUnitDamage?
Can you elaborate? It does not deliver a wrong number in my tests. PlayerTotalDamage is the accumulation of all TotalUnitDamage owned by that player. Let's say Paladin deals 200 damage and Mountain King deals 100 damage to the same target. PlayerTotalDamage should be 300. Then Blademaser deals 300 damage to different target. PlayerTotalDamage now becomes 600. As the name suggests, it is a total damage dealt by the player throughout the game. That is also the reason you can access it anywhere and anytime without restriction unlike the other variables.

I'm planning to expand the system into vJASS for a better API. But it might take a while for me to do so. Not to mention, I'm having a half broken keyboard to work with right now. So it's a pain to type, especially this long, lol.
 
Last edited:
Level 3
Joined
Feb 28, 2025
Messages
11
Can you elaborate? It does not deliver a wrong number in my tests. PlayerTotalDamage is the accumulation of all TotalUnitDamage owned by that player. Let's say Paladin deals 200 damage and Mountain King deals 100 damage to the same target. PlayerTotalDamage should be 300. Then Blademaser deals 300 damage to different target. PlayerTotalDamage now becomes 600. As the name suggests, it is a total damage dealt by the player throughout the game.
Yea thats what i also thought what the suppose of the function is but in my tests when Paladin deals 100 damage and Blademaster 200 I got the result 89. I will check again maybe i got something wrong.

Edit: Found out i had something wrong in the triggers.
 
Last edited:

Rheiko

Spell Reviewer
Level 26
Joined
Aug 27, 2013
Messages
4,214
Updated to v1.2.3
  • Merged GetDamageTrackerData trigger into DamageTracker trigger
  • You can now run GetData (Trigger Variable) instead
  • Added functionality to get the Top Contributor when target dies (Overall/Phys/Spell)
  • Added functionality to get the contribution of Top Contributor when target dies (Overall/Phys/Spell)
  • Adjusted one of the examples
 
Top