HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

How to use timers instead of waits in vjass?

11-24-2009, 07:30 AM#1
shiFt...
How can I use timers I have never used them before, just waits triggersleep action etc. With timers will it enable my spells to be cast before the wait duration has expired?
11-24-2009, 11:50 AM#2
Anitarf
The lack of multi-instanceability of GUI spells is the consequence of a lack of local variables in GUI more than the use of waits instead of timers. We still prefer to use timers in JASS because they are (more) accurate.

We most often use timers in combination with TimerUtils, which allow us to recycle timers (thus avoiding some obscure bugs) as well as attach data to timers (thus allowing us to pass information to the function that the timer will run). A simple spell that kills all units in the target area after five seconds would look like this (the example also uses GroupUtils and SpellEvent):

Collapse JASS:
scope KillSpell initializer Init // The function Init will run when the map starts.
    // SPELL CALIBRATION SECTION
    globals
        private constant integer SPELL_ABILITY_ID = 'A000'
        private constant string EFFECT_PATH = "Spells//CustomSpellModel.mdx"
    endglobals

    private constant function EffectDelay takes integer level returns real
        return 5.0
    endfunction
    private constant function AreaOfEffect takes integer level returns real
        // The values set in the object editor should match these values.
        return 200.0+50.0*level //level 1: 250   level 2: 300   level 3: 350
    endfunction
    // END OF SPELL CALIBRATION

    private function GroupEnumFilter takes nothing returns nothing
        // If we wanted the spell to not kill all units, we would need to
        // add an if statemet here to check if the unit is a valid target.
        // For the purpose of this example, though, we'll just kill them all.
        call KillUnit(GetFilterUnit())
    endfunction

    private struct timerData
        // This is the struct used to pass data to the delayed function.
        unit caster
        real targetx
        real targety
        real radius
        effect fx
    endstruct

    private function DelayedEffect takes nothing returns nothing
        // This is the function called by the timer.
        local timer t = GetExpiredTimer() // Get the timer that called this function.
        local integer i = GetTimerData(t) // Get the integer attached to the timer.
        local timerData d = timerData(i) // Convert the integer to our struct.
        // Okay, this was deliberately a bit long, it can be shortened to one line:
        // local timerData d = timerData(GetTimerData(GetExpiredTimer()))

        // The following line requires GroupUtils, it allows us to affect
        // all units covered by the spell's targeting circle.
        call GroupEnumUnitsInArea(ENUM_GROUP, d.targetx, d.targety, d.radius, Condition( function GroupEnumFilter ))

        // Cleanup.
        call DestroyEffect(d.fx)
        call d.destroy()
        call ReleaseTimer(GetExpiredTimer())
    endfunction

    function SpellEffect takes nothing returns nothing
        // Get a timer.
        local timer t = NewTimer()
        // Get a timerData struct.
        local timerData d = timerData.create()
        // Convert the struct to an integer so we can attach it to the timer.
        local integer i = integer(d)
        // Get the level of the spell (example uses SpellEvent).
        local integer level = GetUnitAbilityLevel(SpellEvent.CastingUnit, SPELL_ABILITY_ID)
        // Set the timerData values (example uses SpellEvent).
        set d.caster = SpellEvent.CastingUnit
        set d.targetx = SpellEvent.TargetX
        set d.targety = SpellEvent.TargetY
        set d.radius = AreaOfEffect(level)
        set d.fx=AddSpecialEffect( EFFECT_PATH, d.targetx, d.targety )
        // Attach the timer data to the timer.
        call SetTimerData(t, i)
        // We could have also done this without the local integer:
        // call SetTimerData(t, integer(d))
        // Start the timer:
        call TimerStart(t, EffectDelay(level), false, function DelayedEffect )
    endfunction

    private function Init takes nothing returns nothing
        // Using SpellEvent here:
        call RegisterSpellEffectResponse(SPELL_ABILITY_ID, SpellEffect)
    endfunction
endscope
11-24-2009, 12:41 PM#3
shiFt...
what does it mean to set data to the timer ?

is it creating a timer specific to this trigger?

what is spellevent/grouputils? and why use it?
11-24-2009, 01:59 PM#4
Anitarf
Quote:
Originally Posted by shiFt...
what does it mean to set data to the timer ?

is it creating a timer specific to this trigger?
Normally, when a timer expires and runs a function, that function has no context: there is no way to know under what conditions the timer was started, in our case that means we can not know what the target coordinates of the spell were. However, with TimerUtils, we are able to attach an integer to a timer and then retrieve this integer from the timer in the function it calls, thus allowing us to pass information between the function that started the timer and the function that runs when the timer finishes. In the example, this integer represents a struct index so we are able to store all the data we need in our struct and then later obtain it again using only this integer.

Quote:
what is spellevent/grouputils? and why use it?
They are libraries that make your life easier. Links are provided in my previous post.
11-24-2009, 05:29 PM#5
shiFt...
ah thanks for explaining will explore timers with a bit more confidence now :)
11-25-2009, 10:47 AM#6
Saishy
Uhm, why a constant function instead of a constant integer? And why it takes a integer if it always returns 5?
11-25-2009, 01:09 PM#7
Anitarf
Quote:
Originally Posted by Saishy
Uhm, why a constant function instead of a constant integer? And why it takes a integer if it always returns 5?
Not always. It only returns 5 in this example.