HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Getting the same index "assigned" to an object

07-22-2011, 03:01 AM#1
Ignitedstar
Here's a simple respawning trigger:
Collapse JASS:
library RefreshEnemies initializer init requires TimerUtils

private struct Respawn
    unit c
    timer t
    real x
    real y
endstruct

globals
    Respawn array RespawnEnemyGroup
    integer EnemyRefreshCount = 0
endglobals

private function Conditions takes nothing returns boolean
    return GetOwningPlayer(GetDyingUnit()) == Player(11) and RectContainsUnit(gg_rct_W_Battlefields, GetDyingUnit()) == false
endfunction

private function TimerCallback takes nothing returns nothing
    local Respawn data = RespawnEnemyGroup[EnemyRefreshCount]
    local timer t = GetExpiredTimer()
    call PauseTimer(t)
    call SetUnitPosition(data.c, data.x, data.y)
    set RespawnEnemyGroup[EnemyRefreshCount] = RespawnEnemyGroup[EnemyRefreshCount] - 1
    set EnemyRefreshCount = EnemyRefreshCount - 1
    call ReleaseTimer(t)
    call RespawnEnemyGroup[EnemyRefreshCount].destroy()
endfunction

function RemoveEnemyGroup takes unit u returns nothing
    local Respawn data = Respawn.create()
    set EnemyRefreshCount = EnemyRefreshCount + 1
    set RespawnEnemyGroup[EnemyRefreshCount] = data
    set data.c = u
    set data.x = GetUnitX(data.c)
    set data.y = GetUnitY(data.c)
    set data.t = NewTimer()
    call SetUnitPosition(data.c, GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()))
    call TimerStart(data.t, 60., false, function TimerCallback)
endfunction

//===========================================================================
private function init takes nothing returns nothing
endfunction

endlibrary

What this trigger does is that whenever an enemy unit of a certain type dies, it gets moved to the center of the map (I will change this later), makes a timer that counts down for a minute, then puts the unit back where it disappeared. I probably didn't need a struct to do this, but I really need to practice simple struct usage.

This works great and all, but there's one issue and I knew that this trigger was going to do this when I was making the trigger: the unit that dies is assigned an index in the struct to keep track of it and put it back after a minute. However, I don't know how to identify which index that a particular unit has in the struct array. This causes problems, like so:

Unit A dies and becomes struct index [0]. Timer starts for [0]. Unit B dies and becomes index [1] ten seconds later. But because the index does not change what it is assigned to until timer[0] finishes, when timer[0] does finish we're still at the struct array at [1], causing Unit B to be moved back. Even though Unit A moved first.

How do I go about this the right way? My alternative route is to make this trigger for every unit type, but seriously... that's tedious and I imagine 99% unnecessary. Please tell me that this as easy as it sounds.
07-22-2011, 04:24 AM#2
Fledermaus
Why do you need to store it in an array? Since you're already using TimerUtils you can just store the struct index in the timer and then get it in the callback:

Collapse JASS:
library RefreshEnemies initializer init requires TimerUtils

private struct Respawn
    unit c
    timer t
    real x
    real y
endstruct

private function Conditions takes nothing returns boolean
    return GetOwningPlayer(GetDyingUnit()) == Player(11) and RectContainsUnit(gg_rct_W_Battlefields, GetDyingUnit()) == false
endfunction

private function TimerCallback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Respawn data = Respawn(GetTimerData(t))
    call PauseTimer(t)
    call SetUnitPosition(data.c, data.x, data.y)
    call ReleaseTimer(t)
    call data.destroy()
    set t = null //Remember to null the timer :o
endfunction

function RemoveEnemyGroup takes unit u returns nothing
    local Respawn data = Respawn.create()
    set data.c = u
    set data.x = GetUnitX(data.c)
    set data.y = GetUnitY(data.c)
    set data.t = NewTimer()
    call SetTimerData(data.t, integer(data))
    call SetUnitPosition(data.c, GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()))
    call TimerStart(data.t, 60., false, function TimerCallback)
endfunction

//===========================================================================
private function init takes nothing returns nothing
endfunction

endlibrary
07-22-2011, 06:07 AM#3
Ignitedstar
... Really? Ah, I didn't know I could do that with TimerUtils... Hmm... Gives me ideas.

Thank you for showing me this.
07-22-2011, 09:44 AM#4
Fledermaus
Yeah, TimerUtils rocks.
07-22-2011, 11:12 AM#5
Bribe
You don't need "call PauseTimer(t)" because TimerUtils pauses the
timer automatically when you do ReleaseTimer.
07-22-2011, 01:34 PM#6
Anitarf
There's nothing wrong with attaching instances to timers using TimerUtils, however due to the constant timer timeout this is one of those rare cases that can be solved with just a FIFO data structure like a queue. The speed advantage that gives you is utterly insignificant, but it can be an interesting exercise that provides an answer to your original problem rather than bypassing it.

Collapse JASS:
private struct Respawn
    unit c
    real x
    real y

    static thistype first=0
    static thistype last=0
    thistype next
    static method callback takes nothing returns nothing
        // Get the instance at the start of the queue and remove it.
        local thistype this=.first
        set .first=.next
        // Move the unit back to the original coordinates.
        call SetUnitPosition(.c, .x, .y)
        // Destroy the instance.
        call .destroy()
        // Recycle the timer.
        call ReleaseTimer(GetExpiredTimer())
    endmethod

    static method create takes unit u returns thistype
        local thistype this=thistype.allocate()
        set .c=u
        set .x=GetUnitX(u)
        set .y=GetUnitY(u)
        // Move the unit to the center of the map.
        call SetUnitPosition(.c, GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()))
        // Add the new instance to the end of the queue.
        if .first==0 then // List doesn't contain instances.
            set .first=this
        else // List contains instances.
            set .last.next=this
        endif
        set .last=this
        set .next=0
        // Start the timer.
        call TimerStart(NewTimer(), 60.0, false, function thistype.callback)
        return this
    endmethod

    method onDestroy takes nothing returns nothing
        set c=null // Not really needed.
    endmethod
endstruct
/* Note: this simple example code does not support the destruction of an
   instance while its timer is still running, but I think it is sufficient for your
   purposes.
07-23-2011, 06:48 AM#7
Ignitedstar
Quote:
Originally Posted by Bribe
You don't need "call PauseTimer(t)" because TimerUtils pauses the
timer automatically when you do ReleaseTimer.
Uugh, really? Well, looks like I should get rid of all of the unneeded pauses then.

Quote:
Originally Posted by Anitarf
There's nothing wrong with attaching instances to timers using TimerUtils, however due to the constant timer timeout this is one of those rare cases that can be solved with just a FIFO data structure like a queue. The speed advantage that gives you is utterly insignificant, but it can be an interesting exercise that provides an answer to your original problem rather than bypassing it.

Hidden information:
Collapse JASS:
private struct Respawn
    unit c
    real x
    real y

    static thistype first=0
    static thistype last=0
    thistype next
    static method callback takes nothing returns nothing
        // Get the instance at the start of the queue and remove it.
        local thistype this=.first
        set .first=.next
        // Move the unit back to the original coordinates.
        call SetUnitPosition(.c, .x, .y)
        // Destroy the instance.
        call .destroy()
        // Recycle the timer.
        call ReleaseTimer(GetExpiredTimer())
    endmethod

    static method create takes unit u returns thistype
        local thistype this=thistype.allocate()
        set .c=u
        set .x=GetUnitX(u)
        set .y=GetUnitY(u)
        // Move the unit to the center of the map.
        call SetUnitPosition(.c, GetRectCenterX(GetWorldBounds()), GetRectCenterY(GetWorldBounds()))
        // Add the new instance to the end of the queue.
        if .first==0 then // List doesn't contain instances.
            set .first=this
        else // List contains instances.
            set .last.next=this
        endif
        set .last=this
        set .next=0
        // Start the timer.
        call TimerStart(NewTimer(), 60.0, false, function thistype.callback)
        return this
    endmethod

    method onDestroy takes nothing returns nothing
        set c=null // Not really needed.
    endmethod
endstruct
/* Note: this simple example code does not support the destruction of an
   instance while its timer is still running, but I think it is sufficient for your
   purposes.

Oh, how interesting. It kind of confuses me, but I will learn it... albeit slowly. I hadn't realized it before, but Vex putting structs into Jass was essentially creating object oriented programming for Warcraft III. Never once have I really been formally taught any kind of programming, so yeah it is... difficult thinking in this kind of way. But I've been getting better! Although the material is getting more complicated.

I am going to keep both versions in my map. The former I can use as a TimerUtils usage reference. The latter solves my problem directly. Thanks, you guys.