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: