• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Pending Rewrite] [Snippet] GetKiller

Level 23
Joined
Apr 16, 2012
Messages
4,041
I needed something like this for more fair deciding of who killed which unit so I wrote it:

JASS:
library GetKiller uses UnitIndexer, DamageEvent, TimerUtils, GetUnitCountOnMap
/*
    by edo494 v1.3
    
    requires -  UnitIndexer: http://www.hiveworkshop.com/forums/jass-resources-412/system-unit-indexer-172090/
                TimerUtils: http://www.wc3c.net/showthread.php?t=101322
                DamageEvent: http://www.hiveworkshop.com/forums/jass-resources-412/snippet-damageevent-186829/
                GetUnitCount: http://www.hiveworkshop.com/forums/submissions-414/snippet-getunitcount-228479/
    
    note: this uses 2 libraries wrote by me, they are included in the thread
    
    this is a system expandint idea of GetKillingUnit to not return you the
    unit that dealt last hit but unit that dealt most damage to the unit.
    
    If you set flag selfDamage to false(by default), if you deal damage to units owned by
    your ally it will not count the damage dealt and killing friendly targets
    results in suicide(returning null), otherwise it will count the damage and return unit
    or its user data according to who dealt most damage.
    
    Note: from 1.2 and on, you can set selfDamage per unit or globally.
    
    This system however requires the call to functions in normal API to not be instant
    so you cant use event Unit Dies.
    
    For this cases, there is a RegisterDeadEvent function that takes function
    and runs those functions(can be called multiple times) whenever units take damage with
    a very short delay.
    
    Because of the delay, in functions registered to RegisterDeadEvent we can't use
    GetTriggerUnit() dirrectly, so we must use function called GetDeadUnit() instead
    
    The RegisterDeadEvent works on similar principes as DmgSys does and so it has
    debug double register protection and in case we no longer want the function to be
    called whenever unit dies, we can call UnregisterDeadEvent, which will remove the
    function from list of called functions.
    
    note: you can only have up to 8191 functions registered to RegisterDeadEvent at once.
    
    UnregisterDeadEvent in debug mode checks if the function is registered, if it is not or
    debug mode is disabled it loops through all registered functions to see if there
    is one, if there is this removes it and moves all further functions accordingly.
    
    normal API:
        
        constants: 
                   boolean selfDamage       - if you want to count damage done by units
                                            - to change value, call ChangeSelfDamage(unit, boolean)
                                            - for a global setting(for all units), call ChangeSelfDamageAll(boolean)
                   
                   integer timeToRefreshUnit - if we dont want global refresh but specific for each unit
                                             - call SetTimeToRefreshDealt(unit, real) or SetTimeToRefreshAll(real)

                   those two variables above are arrays, based on Unit's Index
                   
                   constant integer INTIAL_TIME - intial time for units to refresh

                   constant WANT_TO_REFRESH - if set to true, this will automatically
                                              register a timer, and after timeToRefresh
                                              passed from last damage dealt to the unit,
                                              the system clears all damage dealt to that unit(s)
                                            - in case you dont want to have automatic register, set this to false
        
                   constant AUTO_INTIAL_NEW_UNITS - if set to true, all new units added to the map
                                                     will have their timeToRefreshUnit set to INTIAL_TIME
                                                     if set to false, user has to do it himself
        
        
        GetKiller takes unit deadunit returns unit
            - returns the unit that dealt most damage to the unit
            
        GetKillerId takes unit deadunit returns integer
            - returns index of unit(its UnitUserData) of the unit that dealt most damage to the unit
        
        GetDamageDoneToUnit takes unit tounit, unit which returns real
            - returns damage dealt by "which" unit to "tounit"
        
        ResetDamageDone takes unit tounit returns nothing
            - resets the damage dealt to the unit from all sources immedietally
        
    Modifiers API:    
        
        ChangeSelfDamage takes unit tounit, boolean flag returns nothing
            - sets a flag for a given unit, whether or not to count ally damage as damage dealt
        
        ChangeSelfDamageAll takes boolean flag returns nothing
            - sets a flag for all units on map, whether or not to count ally damage as damage dealt
        
        GetSelfDamageFlag takes unit tounit returns boolean
            - returns whether or not unit tounit will count selfdamage
        
        SetTimeToRefreshUnit takes unit torefresh, integer timeto returns nothing
            - time it takes from last hit to refresh the unit's damage taken(damage dealt of others to this unit)
        
        SetTimeToRefreshAll takes integer timeto returns nothing
            - time it takes from last hit to refresh all units damage taken(damage dealt of others to all units)
        
        GetTimeToRefreshUnit takes unit torefresh returns integer
            - returns time after the unit will refresh itself
        
        
    trigger API:
        
        RegisterDeadEvent takes code c returns nothing
            - takes function c and registers it to the system(private onDead struct)
              which runs all functions registered to it every time unit dies with little
              delay for expected results(without delay GetKiller would return null instead of unit)
        
        UnregisterDeadEvent takes code c returns nothing
            - in case we no longer want some function to run when Unit Dies,
              we can remove this function from the system
        
        GetDeadUnit takes nothing returns unit
            - returns the unit dying which triggers the functions registered to RegisterDeadEvent
              to run
            
*/

    globals
        private boolean array selfDamage
        
        private constant boolean WANT_TO_REFRESH = true
        private integer array timeToRefreshUnit
        private constant integer INTIAL_TIME = 20   //this is set to all units present at the map
                                                    //on map initialization
        private constant boolean AUTO_INTIAL_NEW_UNITS = true
    endglobals
    
    //-----------------------------------------------------------
    //system
    
    
    globals
        private TableArray damage
        private unit array killer
    endglobals

    function GetDamageDoneToUnit takes unit tounit, unit which returns real
        return damage[GetUnitUserData(tounit)].real[GetUnitUserData(which)]
    endfunction
    
    function GetKiller takes unit deadunit returns unit
        return killer[GetUnitUserData(deadunit)]
    endfunction
    
    function GetKillerId takes unit deadunit returns integer
        return GetUnitId(killer[GetUnitUserData(deadunit)])
    endfunction
    
    function ResetDamageDone takes unit towhichunit returns nothing
        local integer i = 1
        local integer getunitcount = GetUnitCount() + 1
        local integer o = GetUnitUserData(towhichunit)
        if killer[o] == null and not IsUnitType(towhichunit, UNIT_TYPE_DEAD) then
            loop
                set damage[o].real[i] = 0
                exitwhen i == getunitcount
                set i = i + 1
            endloop
        endif
    endfunction
    
    function ChangeSelfDamage takes unit tochange, boolean flag returns nothing
        set selfDamage[GetUnitUserData(tochange)] = flag
    endfunction
    
    function ChangeSelfDamageAll takes boolean flag returns nothing
        local integer i = 1
        local integer count = GetUnitCount() + 1
        loop
            set selfDamage[i] = flag
            exitwhen i == count
            set i = i + 1
        endloop
    endfunction
    
    function GetSelfDamageFlag takes unit tounit returns boolean
        return selfDamage[GetUnitUserData(tounit)]
    endfunction
    
    private function setKiller takes nothing returns nothing
        local unit u = GetUnitById(GetTimerData(GetExpiredTimer()))
        local integer i = 1
        local integer o = GetTimerData(GetExpiredTimer())
        local integer unitsOnMap = GetUnitCount() + 1
        call ReleaseTimer(GetExpiredTimer())
        if IsUnitType(u, UNIT_TYPE_DEAD) then
            loop
                exitwhen i > unitsOnMap
                if damage[o].real[i] > damage[o].real[i-1] then
                    set killer[o] = GetUnitById(i)
                endif
                set i = i + 1
            endloop
        endif
        set u = null
    endfunction
    
    private function IncreaseDamageDone takes nothing returns nothing
        local integer take = GetUnitUserData(GetTriggerUnit())
        local integer deal = GetUnitUserData(GetEventDamageSource())
        if selfDamage[take] then
            if IsPlayerEnemy(GetOwningPlayer(GetUnitById(take)), GetOwningPlayer(GetUnitById(deal))) then
                set damage[take].real[deal] = damage[take].real[deal] + GetEventDamage()
            endif
        else
            set damage[take].real[deal] = damage[take].real[deal] + GetEventDamage()
        endif
        call TimerStart(NewTimerEx(take), 0, false, function setKiller)
    endfunction
    
    
    
    
    static if WANT_TO_REFRESH then
        function SetTimeToRefreshUnit takes unit torefresh, integer time returns nothing
            set timeToRefreshUnit[GetUnitUserData(torefresh)] = time
        endfunction
        
        function SetTimeToRefreshAll takes integer time returns nothing
            local integer i = 1
            local integer count = GetUnitCount() + 1
            loop
                set timeToRefreshUnit[i] = time
                exitwhen i == count
                set i = i + 1
            endloop
        endfunction
        
        function GetTimeToRefreshUnit takes unit torefresh returns integer
            return timeToRefreshUnit[GetUnitUserData(torefresh)]
        endfunction
        
        private function iRT takes nothing returns nothing
            local integer i = 0
            local integer count = GetUnitCount() + 1
            loop
                exitwhen i >= count
                set i = i + 1
                set timeToRefreshUnit[i] = INTIAL_TIME
            endloop
        endfunction
        
        private struct RHOLD extends array
            static integer array refreshdur
            static integer array dummyref
        endstruct
    
        private function DamageSystemRefresh takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local integer i = GetTimerData(t)
            if RHOLD.dummyref[i] < (2*timeToRefreshUnit[i]) then
                set RHOLD.refreshdur[i] = RHOLD.refreshdur[i] + 1
                if RHOLD.refreshdur[i] == timeToRefreshUnit[i] then
                    call ResetDamageDone(GetUnitById(i))
                endif
                set RHOLD.dummyref[i] = 0
            else
                set RHOLD.dummyref[i] = RHOLD.dummyref[i] + 1
            endif
            call ReleaseTimer(t)
            set t = null
        endfunction
    
        private function DamageRefreshCount takes nothing returns nothing
            local integer i = GetUnitUserData(GetTriggerUnit())
            if RHOLD.dummyref[i] != 0 then
                set RHOLD.dummyref[i] = 0
            endif
            call TimerStart(NewTimerEx(i), 0.1, false, function DamageSystemRefresh)
        endfunction
        
        static if AUTO_INTIAL_NEW_UNITS then
            private struct THOLD extends array
                static Table unithold       //to not request too much timers from TimerUtils
            endstruct
            
            private function EnterRefreshEx takes nothing returns nothing
                local timer t = GetExpiredTimer()
                set timeToRefreshUnit[GetUnitUserData(THOLD.unithold.unit[GetHandleId(t)])] = INTIAL_TIME
                set THOLD.unithold.unit[GetHandleId(t)] = null
                call DestroyTimer(t)
                set t = null
            endfunction
            
            private function EnterRefresh takes nothing returns boolean
                local timer t = CreateTimer()
                set THOLD.unithold.unit[GetHandleId(t)] = GetTriggerUnit()
                call TimerStart(t, 0.01, false, function EnterRefreshEx)
                set t = null
                return false
            endfunction
            
        endif
    endif
        
    private module init
        static method onInit takes nothing returns nothing
            local code c = function IncreaseDamageDone
            static if AUTO_INTIAL_NEW_UNITS then
                local trigger t = CreateTrigger()
                call TriggerRegisterEnterRegion(t, WorldBounds.worldRegion, null)
                call TriggerAddCondition(t, Condition(function EnterRefresh))
                set t = null
                set THOLD.unithold = Table.create()
            endif
            call DamageEvent.ANY.register(Condition(c), 0)
            set damage = TableArray[0x2000]
            static if WANT_TO_REFRESH then
                call ExecuteFunc(SCOPE_PRIVATE+"iRT")   //to ensure the init Thread wont crash
                set c = function DamageRefreshCount
                call DamageEvent.ANY.register(Condition(c), 0)
            endif
        endmethod
    endmodule

    private struct s_init extends array
        implement init
    endstruct
    
    globals
        private unit getDeadUnit = null
    endglobals

    private struct onDead
        private static conditionfunc array cond
        private static trigger onDeadtrig = CreateTrigger()
        private static trigger torun = CreateTrigger()
        
        private static integer array stack
        private static integer stackcount = 0
        
        debug static method has takes conditionfunc a returns boolean
            debug local integer i = 1
            debug local integer has_count = stackcount + 1
            debug loop
                debug if a == cond[i] then
                    debug return true
                debug endif
                debug exitwhen i == has_count
                debug set i = i + 1
            debug endloop
            debug return false
        debug endmethod
        
        static method unregisteronDeadEvent takes code c returns nothing
            local integer i = 1
            local conditionfunc co = Condition(c)
            local boolean found = false
            debug if not onDead.has(Condition(c)) then
                debug call BJDebugMsg("no such function registered")
                debug call DestroyBoolExpr(co)
                debug set co = null
                debug return
            debug endif
            loop
                if co == cond[i] then
                    set found = true
                    call DestroyBoolExpr(co)
                    set stack[i] = stack[0]
                    set stack[0] = i
                endif
                exitwhen found or i == stackcount
                set i = i + 1
            endloop
            if not found then
                debug call BJDebugMsg("no such function registered")
                call DestroyBoolExpr(co)
            endif
            set co = null
        endmethod
        
        static method registeronDeadEvent takes code c returns nothing
            local integer i = stack[0]
            debug if onDead.has(Condition(c)) then
                debug if stackcount >= 8191 then
                    debug call BJDebugMsg("Too many functions registered(onDead struct)")
                debug else
                    debug call BJDebugMsg("Function already registered(onDead struct)")
                debug endif
                debug return
            debug endif
            if stackcount >= 8191 then
                debug call BJDebugMsg("Too many functions registered(onDead struct)")
                return
            endif
            if i == 0 then
                set i = stackcount + 1
                set stackcount = i
            else
                set stack[0] = stack[i]
            endif
            set cond[stackcount] = Condition(c)
            call TriggerAddCondition(torun, cond[stackcount])
        endmethod
        
        static method runex takes nothing returns nothing
            set getDeadUnit = GetUnitById(GetTimerData(GetExpiredTimer()))
            call TriggerEvaluate(torun)
            call ReleaseTimer(GetExpiredTimer())
            set getDeadUnit = null
        endmethod
        
        static method run takes nothing returns nothing
            call TimerStart(NewTimerEx(GetUnitUserData(GetTriggerUnit())), 0.01, false, function thistype.runex)
        endmethod
        
        static method onInit takes nothing returns nothing
            local code c = function thistype.run
            call TriggerRegisterAnyUnitEventBJ(onDeadtrig, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(onDeadtrig, Condition(c))
        endmethod
    endstruct
    
    function RegisterDeadEvent takes code c returns nothing
        call onDead.registeronDeadEvent(c)
    endfunction
    
    function UnregisterDeadEvent takes code c returns nothing
        call onDead.unregisteronDeadEvent(c)
    endfunction
    
    function GetDeadUnit takes nothing returns unit
        return getDeadUnit
    endfunction
    
endlibrary
 
Last edited:
Level 19
Joined
Aug 8, 2007
Messages
2,765
why not make an extension to damage event that tracks all damage dealt to a unit than when a unit dies returns the unit group with how much each unit dealt.

seems like it would have much more uses than this does
 
Level 19
Joined
Aug 8, 2007
Messages
2,765
That wouldn't need to be an extension, it could be its own resource using Damage Event : \

really?

event - unit is damaged
actions - if attacker isnt in unit group, then add
add damage done to total damage pool
add damage done to attacker damage pool

evnt - unit dies
action - call unitdead(damagedone,unitpool,damagepool)

(not sure how to do the whole damage pool thing without using Table)
 
Level 6
Joined
Nov 24, 2012
Messages
218
really?

event - unit is damaged
actions - if attacker isnt in unit group, then add
add damage done to total damage pool
add damage done to attacker damage pool

evnt - unit dies
action - call unitdead(damagedone,unitpool,damagepool)

(not sure how to do the whole damage pool thing without using Table)

Yeah but I could have done like 500% max hp to my enemy before he dies if he's a hero with regen abilities / regen items, then he can be on max hp again I do nothing some ally come chops off 100% hp and I still get kill?

--
Nice snippet, and I would love to see this in some Dota mirrors that I play. Kinda annoyed of red/green computer taking final hit.
Could come in handy making a custom experience gain system as well.
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
updated a little bit,
Hesitation made a very good point and I was deciding whether Im going to do it in the system or let the users to do it and I decided that startnig a 25 second timer every time unit takes damage is not good so I keep it up to users for the time being, however I added new function: ResetDamageDealt, which as name says resets the damage dealt to the unit
 
JASS:
    function ResetDagameDealt takes unit towhichunit returns nothing
        local integer i = 1
        local integer getunitcount = GetUnitCount() + 1
        local integer o = GetUnitUserData(towhichunit)
        if killer[o] == null and not IsUnitType(towhichunit, UNIT_TYPE_DEAD) then
            loop
                set damage[o].real[i] = 0
                exitwhen i == getunitcount
                set i = i + 1
            endloop
        endif
    endfunction
Shouldn't the name be ResetDamageDealt?
Btw, please use lowerCamelCase for local variable names and parameters.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I know and I could separate it but the DDS and the GetUnitCount would not get approved.
I may change this to some actually approved DDS and no this doesnt have the fancy stuff your has but I dont need to register one unit after another but I can just register function into it and any time any unit takes damage those functions are run.
If Im wrong correct me but I went through the whole library several times and there was only registering units into the DamageEvent

anyways, I updated it with quite some stuff
Fixed the typo(thanks Anachron), added several new modifiers and functions:
  • changed modifier selfdamage(now array and not constant, changable during game)
  • added constant boolean WANT_TO_REFRESH - whether or not we want the units to refresh automatically after some time they didnt take damage
  • added modifier: timeToRefreshUnit, array, noncostant(changable during game) which is the time unit takes no damage to reset
  • added modifier: INTIAL_TIME, contsant, time set to every unit on map init
  • added modifier: AUTO_INTIAL_NEW_UNITS, constant, nonarray, if set to true, all units that enter map will automatically have set their time to refresh damage taken to INTIAL_TIME
  • added functions:
    • ChangeSelfDamage takes unit tounit, boolean flag
    • ChangeSelfDamageAll takes boolean flag
    • GetSelfDamageFlag takes unit tounit returns boolean
    • SetTimeToRefreshUnit takes unit torefresh, integer timeto
    • SetTimeToRefreshAll takes integer timeto
    • GetTimeToRefreshUnit takes unit torefresh returns integer

Current disadvantage of WANT_TO_REFRESH is that it is working with integer for better manipuation and more accurate results
 
I think you should come up with a better name.
This could be useful for some type of kill-assist system but the delay is a no no

Anyway, what's the point of the code variable?

JASS:
static method onInit takes nothing returns nothing
     local code c = function thistype.run
     call TriggerRegisterAnyUnitEventBJ(onDeadtrig, EVENT_PLAYER_UNIT_DEATH)
     call TriggerAddCondition(onDeadtrig, Condition(c))
endmethod
Also the fact it doesn't work with Unit - Dies is kinda lame, but maybe there's no way around it.
 
It's been a couple years since this was looked into. Here's what I see as a future for this project:

Make it strictly an assist-based program. A function to track who has damaged a unit within the last 3-5 seconds is enough.

Also, if you want it to be extra balanced, simply award all players of a team for the kill. It is less complex and gives little opportunity for trolls.
 
Top