- Joined
- Nov 7, 2014
- Messages
- 571
Ability add/remove event
An event that triggers when an ability gets added/removed from a unit. It works by iterating over the unit's abilities over and over again with a small delay (polling), which means that events won't trigger if we do something like:
because these are synchronous operations.
Api
demo:
abilityaddremevent:
PS: beware of bugs =)
An event that triggers when an ability gets added/removed from a unit. It works by iterating over the unit's abilities over and over again with a small delay (polling), which means that events won't trigger if we do something like:
JASS:
call UnitAddAbility(u, 'AHbz')
call UnitRemoveAbility(u, 'AHbz')
because these are synchronous operations.
Api
JASS:
// the polling_delay parameter specifies how often we iterate over unit u's abilities looking for
// differences between it's current abilities and the abilities it had the last time we check them
//
// cb is the callback that gets called when the event triggers
// the returned integer can be passed to the unregister function
function register_ability_add_rem_event takes unit u, code cb, real polling_delay returns integer
// unreg must be a value returned from the register function
function unregister_ability_add_rem_event takes integer unreg returns integer
demo:
JASS:
library playground initializer init /*
*/ uses /*
*/ abilityaddremevent
private function to_base256 takes integer i returns string
local string digits = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
local string result = ""
local integer n
local integer d
local integer t
set n = 0
loop
set n = n + 1
set t = i / 256
set d = i - t * 256
set i = t
set d = d - 0x21
if d <= 94 then
set result = SubString(digits, d, d + 1) + result
else
set result = "?" + result
endif
exitwhen n == 4
endloop
return result
endfunction
globals
private unit h1
private unit h2
endglobals
private function on_abi_add_rem takes nothing returns nothing
local Ability_Add_Rem_Event ev = Ability_Add_Rem_Event.get()
local string name = GetUnitName(ev.u)
if ev.is_added then
call BJDebugMsg(name + " got '" + to_base256(ev.ability_id) + "'")
else
call BJDebugMsg(name + " lost '" + to_base256(ev.ability_id) + "'")
endif
endfunction
globals
private timer tmr = CreateTimer()
private integer unreg
endglobals
private function h1_unreg takes nothing returns nothing
call unregister_ability_add_rem_event(unreg)
call BJDebugMsg("unregistering ability-add-rem-event for " + GetUnitName(h1))
call DestroyTimer(GetExpiredTimer())
endfunction
private function init takes nothing returns nothing
local integer i
set h1 = CreateUnit(Player(0), 'Hpal', 0.0, 0.0, 270.0)
set h2 = CreateUnit(Player(0), 'Ntin', 96.0, 0.0, 270.0)
call SetHeroLevel(h1, 10, /*show_eyecandy:*/ false)
call SetHeroLevel(h2, 10, /*show_eyecandy:*/ false)
set i = 1
loop
exitwhen i > 10
call CreateItem('tret', 0.0, -256.0)
set i = i + 1
endloop
set unreg = register_ability_add_rem_event(h1, function on_abi_add_rem, 0.1)
call register_ability_add_rem_event(h2, function on_abi_add_rem, 0.1)
call TimerStart(tmr, 20.0, false, function h1_unreg)
endfunction
endlibrary
abilityaddremevent:
JASS:
library abilityaddremevent initializer init /*
*/ uses /*
*/ Memory, /*
*/ Utils // https://www.hiveworkshop.com/threads/accessing-memory-from-the-script-its-time-of-the-revolution.279262/
struct Ability_Add_Rem_Event extends array
static unit u
static integer ability_id
static integer ability_p // ability's address in memory
static boolean is_added // added = true, removed = false
static method get takes nothing returns Ability_Add_Rem_Event
return Ability_Add_Rem_Event(1)
endmethod
endstruct
globals
private constant real Dt = 1.0 / 32.0
private timer ticker = CreateTimer()
private boolean ticking = false
private code on_tick_cb
private force exec = CreateForce()
endglobals
globals
private constant integer Observed_Unit = 1
private constant integer Event = 2
private constant integer Event_Handler = 3
private constant integer Ability = 4
endglobals
private struct Any
// the lists that we use are doubly-linked with a sentinel node
integer ty
Any prev
Any next
Any ev
// Observed_Unit
//
// Any prev
// Any next
unit u
Any evs // events
// Event
//
// Any prev
// Any next
Any ou
integer polling_delay
integer rem_dur
Any evhs // event handlers
Any abis // the abilities of the unit when we last polled
// Event_Handler
//
// Any prev
// Any next
// Any ev
integer cb
// Ability
//
// Any prev
// Any next
// Any ev
integer id
integer p // ability pointer
endstruct
globals
private Any observed_units = Any(0)
endglobals
private function node_add_to_list takes Any node, Any sent returns nothing
set node.prev = sent.prev
set node.next = sent
set node.prev.next = node
set node.next.prev = node
endfunction
private function node_remove_from_list takes Any node returns nothing
set node.prev.next = node.next
set node.next.prev = node.prev
// set node.prev = node
// set node.next = node
endfunction
private function alloc takes integer ty returns Any
local Any a = Any.create()
set a.ty = ty
return a
endfunction
private function dealloc takes Any a returns nothing
call a.destroy()
endfunction
private function Observed_Unit_create takes unit u returns Any
local Any ou = alloc(Observed_Unit)
local Any evs
set ou.u = u
set evs = alloc(Event)
set evs.prev = evs
set evs.next = evs
set ou.evs = evs
call node_add_to_list(ou, observed_units)
if not ticking then
set ticking = true
call TimerStart(ticker, Dt, true, on_tick_cb)
endif
return ou
endfunction
private function Ability_create takes Any ev, integer id, integer p returns Any
local Any abi = alloc(Ability)
set abi.id = id
set abi.p = p
call node_add_to_list(abi, ev.abis)
return abi
endfunction
private function Event_create takes Any ou, integer polling_delay returns Any
local Any ev = alloc(Event)
local Any abis
local Any evhs
local integer a_p
local integer a_id
set ev.ou = ou
set ev.polling_delay = polling_delay
set ev.rem_dur = polling_delay
set evhs = alloc(Event_Handler)
set evhs.prev = evhs
set evhs.next = evhs
set ev.evhs = evhs
set abis = alloc(Ability)
set abis.prev = abis
set abis.next = abis
set ev.abis = abis
set a_p = GetAgentFromRef((ConvertHandle(ou.u)+0x1DC)/4)
loop
exitwhen a_p == 0
set a_id = Memory[(a_p+0x34)/4]
call Ability_create(ev, a_id, a_p)
set a_p = GetAgentFromRef((a_p+0x24)/4)
endloop
call node_add_to_list(ev, ou.evs)
return ev
endfunction
private function Event_Handler_create takes Any ev, code cb returns Any
local Any evh = alloc(Event_Handler)
set evh.ev = ev
set evh.cb = C2I(cb)
call node_add_to_list(evh, ev.evhs)
return evh
endfunction
private function Event_Handler_destroy takes Any evh returns nothing
call node_remove_from_list(evh)
call dealloc(evh)
endfunction
private function Ability_destroy takes Any abi returns nothing
call node_remove_from_list(abi)
call dealloc(abi)
endfunction
private function Event_destroy takes Any ev returns nothing
local Any evhs
local Any evh
local Any abis
local Any abi
local Any next
set evhs = ev.evhs
set evh = evhs.next
loop
exitwhen evh == evhs
set next = evh.next
call Event_Handler_destroy(evh)
set evh = next
endloop
call dealloc(evhs)
set abis = ev.abis
set abi = abis.next
loop
exitwhen abi == abis
set next = abi.next
call Ability_destroy(abi)
set abi = next
endloop
call dealloc(abis)
call node_remove_from_list(ev)
call dealloc(ev)
endfunction
private function Observed_Unit_destroy takes Any ou returns nothing
local Any evs
local Any ev
local Any next
set evs = ou.evs
set ev = evs.next
loop
exitwhen ev == evs
set next = ev.next
call Event_destroy(ev)
set ev = next
endloop
call dealloc(evs)
set ou.u = null
call node_remove_from_list(ou)
call dealloc(ou)
if observed_units.prev == observed_units then
set ticking = false
call PauseTimer(ticker)
endif
endfunction
globals
private integer array abi_ids
private integer array abi_ps
endglobals
private function on_tick takes nothing returns nothing
local Any ou
local Any ou_next
local Any evs
local Any ev
local Any evhs
local Any evh
local Any abis
local Any abi
local Any abi_next
local integer abi_id
local integer u_p
local integer a_p
local integer i
local integer ii
local boolean found
local Ability_Add_Rem_Event aarev = Ability_Add_Rem_Event(1)
set ou = observed_units.next
loop
exitwhen ou == observed_units
set ou_next = ou.next
set u_p = ConvertHandle(ou.u)
if u_p == 0 then
// ou.u got removed from the game
call Observed_Unit_destroy(ou)
else
set aarev.u = ou.u
set evs = ou.evs
set ev = evs.next
loop
exitwhen ev == evs
if ev.rem_dur == 0 then
set ev.rem_dur = ev.polling_delay
set i = 1
set a_p = GetAgentFromRef((u_p+0x1DC)/4)
loop
exitwhen a_p == 0
set abi_ids[i] = Memory[(a_p+0x34)/4]
set abi_ps[i] = a_p
set i = i + 1
set a_p = GetAgentFromRef((a_p+0x24)/4)
endloop
set ii = i - 1
set abis = ev.abis
set i = 1
loop
exitwhen i > ii
set abi_id = abi_ids[i]
set abi = abis.next
set found = false
loop
exitwhen abi == abis
if abi_id == abi.id then
set found = true
exitwhen true
endif
set abi = abi.next
endloop
if not found then
set aarev.ability_id = abi_id
set aarev.ability_p = abi_ps[i]
set aarev.is_added = true
set evhs = ev.evhs
set evh = evhs.next
loop
exitwhen evh == evhs
call ForForce(exec, I2C(evh.cb))
set evh = evh.next
endloop
endif
set i = i + 1
endloop
set abi = abis.next
loop
exitwhen abi == abis
set abi_id = abi.id
set i = 1
set found = false
loop
exitwhen i > ii
if abi_id == abi_ids[i] then
set found = true
exitwhen true
endif
set i = i + 1
endloop
if not found then
set aarev.ability_id = abi_id
set aarev.ability_p = abi.p
set aarev.is_added = false
set evhs = ev.evhs
set evh = evhs.next
loop
exitwhen evh == evhs
call ForForce(exec, I2C(evh.cb))
set evh = evh.next
endloop
endif
set abi = abi.next
endloop
set i = 1
set abi = abis.next
loop
exitwhen i > ii
if abi == abis then
set abi = Ability_create(ev, abi_ids[i], abi_ps[i])
else
set abi.id = abi_ids[i]
set abi.p = abi_ps[i]
endif
set abi = abi.next
set i = i + 1
endloop
loop
exitwhen abi == abis
set abi_next = abi.next
call Ability_destroy(abi)
set abi = abi_next
endloop
else // if ev.rem_dur != 0
set ev.rem_dur = ev.rem_dur - 1
endif
set ev = ev.next
endloop
endif // u_p == 0
set ou = ou_next
endloop
set aarev.u = null
set aarev.ability_id = 0
set aarev.ability_p = 0
set aarev.is_added = false
endfunction
function register_ability_add_rem_event takes unit u, code cb, real polling_delay_r returns integer
local Any ou
local Any ev
local Any evh
local integer polling_delay
if u == null or cb == null then
return 0
endif
set ou = observed_units.next
loop
exitwhen ou == observed_units
exitwhen ou.u == u
set ou = ou.next
endloop
if ou == observed_units then
set ou = Observed_Unit_create(u)
endif
set polling_delay = R2I(polling_delay_r * 1.0 / Dt)
set ev = ou.evs.next
loop
exitwhen ev == ou.evs
exitwhen ev.polling_delay == polling_delay
set ev = ev.next
endloop
if ev == ou.evs then
set ev = Event_create(ou, polling_delay)
endif
set evh = Event_Handler_create(ev, cb)
return integer(evh) // opaque
endfunction
function unregister_ability_add_rem_event takes integer unreg returns integer
local Any evh = Any(unreg)
local Any ev
local Any ou
if evh == 0 then
return 1 // error
endif
if evh.ty != Event_Handler then
return 2 // error
endif
set ev = evh.ev
set ou = ev.ou
call Event_Handler_destroy(evh)
if ev.evhs.prev == ev.evhs then
call Event_destroy(ev)
if ou.evs.prev == ou.evs then
call Observed_Unit_destroy(ou)
endif
endif
return 0 // success
endfunction
private function init takes nothing returns nothing
set on_tick_cb = function on_tick
call ForceAddPlayer(exec, Player(15))
set abi_ids[31] = 0
set abi_ps[31] = 0
endfunction
endlibrary
PS: beware of bugs =)