• 🏆 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!

[vJASS] TableArray and timers need help

Status
Not open for further replies.
Level 15
Joined
Nov 30, 2007
Messages
1,202
I'm trying to create a timer attached to [Option][PlayerId] and a way to retrieve the time remaining. But I'm not sure if im allocating too much memory for it and how to write the remove statements.

It's entierly possible probably to completely ditch Table cdTable and just use TableArray, but I'm not sure how to write this.

Futhermore, this whole struct is probably redudant too, but since I cant really use TableArray this was the best I could come up with.

Hope it's clear what I'm trying to acomplish from code.

JASS:
struct Cooldown
        static TableArray ta
        static Table cdTable
        private SpellOption option
        private integer pid
 
        static method getTime takes SpellOption o, integer pid returns real
            local timer t = ta[o].timer[pid]
            return TimerGetRemaining(t)
        endmethod
 
        static method onFinish takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local integer id = GetHandleId(t)
            local Cooldown cd = cdTable[id]
            call ta[option].timer.remove(cd.pid)        // <----
            call cdTable.remove(id)                 // <----
            call PauseTimer(t)
            call DestroyTimer(t)
            set t = null
            call cd.deallocate()
        endmethod
 
        static method create takes integer pid, SpellOption o, integer time returns thistype
            local thistype this = .allocate()
            local timer t = CreateTimer()
            set this.option = o
            set this.pid = pid
            call TimerStart(t, o.cooldown, false, function thistype.onFinish)
            set ta[o].timer[pid] = t         
            set cdTable[GetHandleId(t)] = this
            set t = null
            return this
        endmethod
 
        private static method onInit takes nothing returns nothing
            set ta = TableArray[24]    // I got 24 child keys to each option, is this correct then?
            set cdTable = Table.create()
        endmethod
    endstruct
 
Last edited:
Syntax is a bit off, but concept should work. But is the table array required? Maybe there can be:
  • Each player has 1 table, and SpellOption is used as index
  • 1 static table to bind instance to timer
JASS:
struct Cooldown extends array
    private static Table table_clocks
    private Table table
   
    static method getTime takes SpellOption o, integer pid returns real
        local thistype this = pid
        if .table.timer.has(o) then
            return TimerGetRemaining(.table.timer[o])
        endif
        return 0.
    endmethod

    private static method onFinish takes nothing returns nothing
        local timer clock = GetExpiredTimer()
        local integer id = GetHandleId(clock)
        local thistype this = table_clocks[id]
   
        call table_clocks.remove(id)
        call .table.timer.remove(table_clocks[-id])
        call DestroyTimer(clock)
        set clock = null
    endmethod

    static method create takes integer pid, SpellOption o returns thistype
        local thistype this = pid
        local integer id
   
        if not .table.timer.has(o) then
            set .table.timer[o] = CreateTimer()
            set id = GetHandleId(.table.timer[o])
            set table_clocks[id] = this
            set table_clocks[-id] = o
        endif
        call TimerStart(.table.timer[o], o.cooldown, false, function thistype.onFinish)
        return this
    endmethod

    private static method onInit takes nothing returns nothing
        local integer i = 0
        loop
            exitwhen i > bj_MAX_PLAYERS
            set thistype(i).table = Table.create()
            set i = i + 1
        endloop
        set table_clocks = Table.create()
    endmethod
endstruct
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
JASS:
scope MCooldown
 
    struct SpellOption
        integer cooldown
    endstruct

    struct Cooldown extends array
        private static Table table_clocks
        private Table table
     
        static method remaining takes integer pid, SpellOption o returns real
            local thistype this = pid
            if .table.timer.has(o) then
                return TimerGetRemaining(.table.timer[o])
            endif
            return 0.
        endmethod
     
        static method removeFromOption takes SpellOption o returns nothing
            local thistype this
            local integer pid = 0
            local integer id
            local timer clock
            loop
                exitwhen pid > bj_MAX_PLAYERS
                set this = pid
                if .table.timer.has(o) then
                    set clock = .table.timer[o]
                    set id = GetHandleId(clock)
                    call table_clocks.remove(id)
                    call .table.timer.remove(table_clocks[-id])
                    call PauseTimer(clock)
                    call DestroyTimer(clock)
                    set clock = null
                endif
                set pid = pid + 1
            endloop
        endmethod
     
        static method onFinish takes nothing returns nothing
            local timer clock = GetExpiredTimer()
            local integer id = GetHandleId(clock)
            local thistype this = table_clocks[id]
            call table_clocks.remove(id)
            call .table.timer.remove(table_clocks[-id])
            call DestroyTimer(clock)
            set clock = null
        endmethod
 
        static method start takes integer pid, SpellOption o returns nothing
            local thistype this = pid
            local integer id
            if not .table.timer.has(o) then
                set .table.timer[o] = CreateTimer()
                set id = GetHandleId(.table.timer[o])
                set table_clocks[id] = this
                set table_clocks[-id] = o
            endif
            call TimerStart(.table.timer[o], o.cooldown, false, function thistype.onFinish)
        endmethod
     
        private static method onInit takes nothing returns nothing
            local integer i = 0
            loop
                exitwhen i > bj_MAX_PLAYERS
                set thistype(i).table = Table.create()
                set i = i + 1
            endloop
            set table_clocks = Table.create()
        endmethod
    endstruct
endscope

Thanks Bo, will test this later.
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
I tested it and it doesn't work. The cooldown of one PID is shared between different options.

JASS:
struct SpellOption
 
        method startCooldown takes integer pid returns nothing
            call Cooldown.start(pid, this)
        endmethod
 
        method getCooldown takes integer pid returns integer
            return R2I(Cooldown.remaining(pid, this))
        endmethod
    endstruct

Or actuallythe the timer is fine. It's something wrong with BlzSetCooldown this time. When it needs to be set to 0 or to whatever the timer it, it doesn't change.


JASS:
private static method onOptionSelected takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local integer pid = GetPlayerId(p)
            local thistype menu = thistype.playerOpenMenu[pid]
            local integer spell = GetSpellAbilityId()
            local SpellOption opt
            if spell == AB_NEXT then
                call menu.showWindow(p, pid, thistype.playerWindow[pid] + 1)
            else
                set opt = SpellMenu.playerOptionMap[pid][spell]
                if opt.cooldown > 0 then
                    call BlzSetUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], spell, pid + 1, opt.cooldown)
                    call opt.startCooldown(pid)
                endif
         
                set udg_SBM_optionKey = opt
                set udg_SBM_menuKey = menu
                call TriggerExecute(opt.trg)
                set udg_SBM_event = 0.
                set udg_SBM_event = 1.
            endif
            set p = null
        endmethod

JASS:
private method showWindow takes player p, integer pid, integer window returns nothing
            local integer i
            local integer j = 0
            local SpellOption op
            local integer maxWindow = .getNumWindows()
            local integer curSpell
            set thistype.playerWindow[pid] = window
            if thistype.playerWindow[pid] > maxWindow then
                set thistype.playerWindow[pid] = 0
            endif
            call BJDebugMsg("--------")
            set i = thistype.playerWindow[pid]*OPTIONS_PER_WINDOW
            set j = 0
            loop
                exitwhen j == 12
                if i < .length then
                    set op = .options[i]
                    if not op.hidden then
 
                        if op.enabled then
                            call SetPlayerAbilityAvailable(p, thistype.spell[j], true)
                            call SetPlayerAbilityAvailable(p, thistype.passive[j], false)
                            set curSpell = thistype.spell[j]
                            set playerOptionMap[pid][curSpell] = op
                            call BlzSetAbilityTooltip(curSpell, op.label + " " + thistype.hotkey[j], pid + 1)
                        else
                            call SetPlayerAbilityAvailable(p, thistype.spell[j], false)
                            call SetPlayerAbilityAvailable(p, thistype.passive[j], true)
                            set curSpell = thistype.passive[j]
                        endif
                   
                        call BJDebugMsg("Cooldown: " + I2S(op.getCooldown(pid)))
                   
                        call BlzSetAbilityExtendedTooltip(curSpell, op.text, pid + 1)
                        call BlzSetAbilityTooltip(curSpell, op.label + " " + thistype.hotkey[j], pid + 1)
                        if GetLocalPlayer() == p then
                            call BlzSetAbilityIcon(curSpell, op.icon)
                        endif
                        // THIS PART DOESN'T WORK!
                        call BlzEndUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], curSpell)
                        call BlzSetUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], curSpell, pid + 1, op.getCooldown(pid))
                   
                    else
                        call SetPlayerAbilityAvailable(p, thistype.spell[j], false)
                        call SetPlayerAbilityAvailable(p, thistype.passive[j], false)
                    endif
                else
                    call SetPlayerAbilityAvailable(p, thistype.spell[j], false)
                    call SetPlayerAbilityAvailable(p, thistype.passive[j], false)
                endif
                set i = i + 1
                set j = j + 1
            endloop
            if .length > 12 then    // Replaces last with next button if multiple windows
                call SetPlayerAbilityAvailable(p, thistype.spell[11], false)
                call SetPlayerAbilityAvailable(p, thistype.passive[11], false)
                call SetPlayerAbilityAvailable(p, thistype.spell[12], true)
            else
                call SetPlayerAbilityAvailable(p, thistype.spell[12], false)
            endif
            call BlzSetUnitName(thistype.playerMenuUnit[pid], .name + " " + I2S(thistype.playerWindow[pid] + 1) + "/" + I2S(maxWindow + 1))
            call ShowUnit(thistype.playerMenuUnit[pid], true)
            if GetLocalPlayer() == p then
                call ClearSelection()
                call SelectUnit(thistype.playerMenuUnit[pid], true)
            endif
        endmethod

Suppose I could fake a cooldown by just changing the displayed text and icon to DISBTN but that would be less than ideal.
 
Last edited:
Would this then also work? ..
Yes should, I guess. There can be now even a own remove function, since it's needed at more than one place. I also forgot removing an integer entry in table.. so:
JASS:
scope MCooldown
    struct SpellOption
        integer cooldown
    endstruct

    struct Cooldown extends array
        private static Table table_clocks
        private Table table
     
        private method remove takes SpellOption o returns nothing
            local timer clock = .table.timer[o]
            local integer id = GetHandleId(clock)
         
            call table_clocks.remove(id)
            call table_clocks.remove(-id)
            call .table.timer.remove(table_clocks[-id])
         
            call PauseTimer(clock)
            call DestroyTimer(clock)
            set clock = null
        endmethod
   
        static method removeOptionForPlayer takes integer pid, SpellOption o returns nothing
            local thistype this = pid
            if .table.timer.has(o) then
                call .remove(o)
            endif
        endmethod
   
        static method removeOption takes SpellOption o returns nothing
            local integer pid = 0
            loop
                exitwhen pid > bj_MAX_PLAYERS
                call removeOptionForPlayer(pid, o)
                set pid = pid + 1
            endloop
        endmethod
   
        static method onFinish takes nothing returns nothing
            local integer id = GetHandleId(GetExpiredTimer())
            call thistype(table_clocks[id]).remove(table_clocks[-id])
        endmethod

        static method start takes integer pid, SpellOption o returns nothing
            local thistype this = pid
            local integer id
            if not .table.timer.has(o) then
                set .table.timer[o] = CreateTimer()
                set id = GetHandleId(.table.timer[o])
                set table_clocks[id] = this
                set table_clocks[-id] = o
            endif
            call TimerStart(.table.timer[o], o.cooldown, false, function thistype.onFinish)
        endmethod
   
        private static method onInit takes nothing returns nothing
            local integer i = 0
            loop
                exitwhen i > bj_MAX_PLAYERS
                set thistype(i).table = Table.create()
                set i = i + 1
            endloop
            set table_clocks = Table.create()
        endmethod
    endstruct
endscope

For other problem, so I'm not sure I very understand what the important thing is, there's too many unrelated logics. Could you reduce the problem? :)
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
So it works like this:

The menu opens for a player with a given pid all the abilities in the menu command card needs to be updated to match the data. Each player has an unique level for the ability that they see, so for player red the level is 1 (pid + 1).

When the menu opens it reads your cooldown timer correctly, however when i try to apply the retrieved timer value using the following lines the cooldown is simply just destroyed and never reset to the new time.

JASS:
// this is to remove any previous timer that we no longer want
call BlzEndUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], curSpell)

// this is to start a new cooldown with the current timer (if any)
call BlzSetUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], curSpell, pid + 1, op.getCooldown(pid))

When a spell is selected the cooldown is also changed using this, which works:
JASS:
call BlzSetUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], spell, pid + 1, opt.cooldown)


What ends up happening is that the cooldown timer starts on selection, as it should. Then a brief moment later maybe 0.5 seconds it's destroyed because refresh() was called. However it never applies any of the cooldown time that was recieved from struct Cooldown.

The image shows how far it gets with a 15 second timer configured. But is immedietly destroyed and never set properly again afterwards.
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
Is this wanted?

Does End and Set new cooldown even works both directly in Spell Cast event? Maybe the native requires a minimum of timeout to be applied after cast?

No it's not really wanted but to trigger the memory of the struct for the different cases would be a pain in the neck to do, so I'm trying to brute force it. I'll try changing the icon after a 0.01 timer, but I fear this might cause bugs to occur... who knows^^

And yes, they did work directly after spell cast even. That cooldown you see is a function call.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
I'm a bit confused what the problem is, I thought the problem is they didn't work at your event?

First time the spell is cast it doesn't get any cooldown applied. If i cast it quickly afterwards it gets proper cooldown. (this is only on spell cast bug). Another issue is that the applied cooldown seem to be faster than the timer.

And the other bug is that when i select another menu the cooldwon is destroyed, however, when reselecting the previous menu that should get a new cooldown applied to it, it never gets one it's driving me insane ~.^

This is the update part:
JASS:
set cooldown = op.getCooldown(pid)
                        call BJDebugMsg("Remaining: " + R2S(BlzGetUnitAbilityCooldownRemaining(SpellMenu.playerMenuUnit[pid], curSpell)))
                        if (cooldown > 0) then
                            call BJDebugMsg("Changed cooldown to: " + I2S(cooldown))
                            call BlzSetUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], curSpell, pid + 1, cooldown)
                        else
                           // We should end the current cooldown because this option doesn't have any, yet the spell has one.
                            if BlzGetUnitAbilityCooldownRemaining(SpellMenu.playerMenuUnit[pid], curSpell) > 0. then
                                call BJDebugMsg("Has cooldown but is at 0")
                                call BlzEndUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], curSpell)
                            endif
                        endif

On spell:

JASS:
    set cooldown = opt.cooldown
                if cooldown > 0 then
                    call BlzSetUnitAbilityCooldown(SpellMenu.playerMenuUnit[pid], spell, pid + 1, cooldown)
                    call BJDebugMsg("Starting cooldown on spell: " + I2S(cooldown))
                    call opt.startCooldown(pid)
                endif

An example run could say "Changed cooldown to 6 and the ability yet nothing happens.

I can provide test map, but don't think anyone will look. ^^

Left image: The first run at the top didn't work, then i trigger it twice and it works (because I enver deselect the menu) then at the bottom i try again and this time it doesn't apply any changes.

Right image: With deselection i select a different menu and reopen the previos one, the timer never updates even though it should and the function is called.


upload_2018-8-18_17-46-12.png
upload_2018-8-18_17-49-21.png
 
Last edited:
Level 15
Joined
Nov 30, 2007
Messages
1,202
Can you post it, and withby exactly say expected result you want achieve? Maybe it's easier when taking a look directly there..I hope. ^^

Spell Based Menu System, it's attached here as map 0.09.

Test case: Select the circle of power to the bottom right. The third spell (Option 3 [E]) has a cooldown associated to it (Trigger LocalMenu).

Error 1: The first time you activate the spell no cooldown starts, the second times it does rightly.
Error 2: If you deselect the menu or reselect the menu the cooldown will be destroyed and never recreated properly. By reselection here I mean selecting a different Circle of power or selecting the same one again.

Error 3: Some times the cooldown doesn't match the cooldown defined by the struct (this is probably because its custom data is 1 second and the native never activates it.)

I'm not hopeful though and I'll probably do some sort of workaround for this and just disable the spell if it's on cooldown.

If you do take a look I wish you best of luck, I'll stick around a bit for questions if you have any, but then I'll have to call it a day.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Update: IcemanBo was right, again!

It appears the only way to properly set the ability cooldown is to destroy it in one trigger (for example when the unit is selected) and then issue the unit to recast the ability and there you can set it. In the other trigger you detect the casted spell and set the ability cooldown. The only issue is that if you reselect the unit fast enough the cooldown will never be set and to solve this you need some sort of timer to disable/enable the selection trigger temporarily for 0.4 seconds.

Test:

  • Selection
    • Events
      • Player - Player 1 (Red) Selects a unit
    • Conditions
    • Actions
      • Game - Display to (All players) the text: selection
      • Custom script: call BlzEndUnitAbilityCooldown(udg_u, udg_ab)
      • Unit - Order u to Night Elf Druid Of The Claw - Roar
  • OnCast
    • Events
      • Unit - A unit Begins casting an ability
      • //Unit - A unit Is issued an order with no target
    • Conditions
    • Actions
      • // Custom script: if GetIssuedOrderId() == 852164 then
        • Custom script: call BlzSetUnitAbilityCooldown(udg_u, udg_ab, 1, 5.)
        • //Custom script: endif
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,010
The rapid selection breaking the method you're experiencing is because the select event is slow to fire, not because the cooldown reset takes time. (I may not fully understand your method here but I have fucked with the select event in the past and been annoyed that it doesn't fire until well after it should.)
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
The rapid selection breaking the method you're experiencing is because the select event is slow to fire, not because the cooldown reset takes time. (I may not fully understand your method here but I have fucked with the select event in the past and been annoyed that it doesn't fire until well after it should.)

My options are basically to have a .4 seconds delay on opening the menu or have cooldowns that work using disable instead. Though as you say, I could try other open menu events.
 
Status
Not open for further replies.
Top