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

Ability add/remove event

Status
Not open for further replies.
Level 13
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:
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 =)
 
Status
Not open for further replies.
Top