• 🏆 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] Method being ran twice, unsure as to why, can you see it?

Level 7
Joined
Sep 16, 2016
Messages
185
JASS:
library RetsuDashLib requires TimerUtils, SpellEffectEvent, PauseUnitEx, RegisterPlayerUnitEvent
    scope RetsuDashScope
        struct RetsuDash
            static timer periodic = null
            static timer timeOut = null
            static unit u = null
            static real duration = 0.
            static boolean ready2dash = false
            static trigger RetsuDashLearn = CreateTrigger()

            static method RetsuDashTimeOut takes nothing returns nothing // Reset the ability and unit if in stance for 2 seconds
                if ready2dash == true then
                    call PauseUnitEx(u, false)
                    call BJDebugMsg("Timeout")
                    set ready2dash = false
                endif
                call ReleaseTimer(timeOut)
                set timeOut = null
            endmethod

            static method RetsuDaskClick takes nothing returns nothing // If in stance, next right click will dash the unit
                if ready2dash == true and BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_RIGHT then
                    call PauseUnitEx(u, false)
                    set ready2dash = false
                    call BJDebugMsg("Dashed")
                endif
            endmethod

            static method actRetsuDashLearn takes nothing returns nothing // Create one event for right mouse up for the unit owner when they learn the ability
                local trigger t = CreateTrigger()
                if GetLearnedSkill() == 'A003' then
                    call TriggerRegisterPlayerEvent(t, GetOwningPlayer(GetLearningUnit()), EVENT_PLAYER_MOUSE_UP)
                    call TriggerAddAction(t, function thistype.RetsuDaskClick)
                    call DestroyTrigger(RetsuDashLearn)
                endif
            endmethod

            static method GetOppositeFacing takes unit Caster returns real // Get the opposite facing angle of the unit
                local real facing = GetUnitFacing(Caster)
                local real oppositeFacing = facing + 180
                if oppositeFacing >= 360 then
                    set oppositeFacing = oppositeFacing - 360
                endif
                return oppositeFacing
            endmethod

            static method StartTimeOut takes nothing returns nothing // Starts the timeout timer, I had this in other places, but no matter where I place this, the bug remains
                set timeOut = CreateTimer() // The bug is "onPeriod" running twice and TimeOut functions for some reason not properly updating variable.
                call TimerStart(timeOut, 2, false, function thistype.RetsuDashTimeOut)
            endmethod

            static method onPeriod takes nothing returns nothing // Pushes the unit backwards for 0.2s, and setting dash variable to true (dash ready)
                local real oppositeAngle = GetOppositeFacing(u)
                local real x_fadeaway = NewX(GetUnitX(u), 20, oppositeAngle)
                local real y_fadeaway = NewY(GetUnitY(u), 20, oppositeAngle)
                if duration > 0.2 then
                    set duration = 0
                    set ready2dash = true
                    call ReleaseTimer(periodic)
                    set periodic = null
                else
                    call SetUnitX(u, x_fadeaway)
                    call SetUnitY(u, y_fadeaway)
                    set duration = duration + 0.03
                endif
            endmethod

            static method onCast takes nothing returns nothing // When ability is cast
                set periodic = CreateTimer()
                set u = GetTriggerUnit()
                call SetUnitAnimationByIndex(u, 4)
                call SetUnitTimeScalePercent(u, 150)
                call PauseUnitEx(u, true)
                call TimerStart(periodic, 0.03, true, function thistype.onPeriod)
                call thistype.StartTimeOut() // Culprit, this line causes onPeriod to run twice
            endmethod

            static method onInit takes nothing returns nothing // Init
                call RegisterSpellEffectEvent('A003', function thistype.onCast)
                call TriggerRegisterAnyUnitEventBJ(RetsuDashLearn, EVENT_PLAYER_HERO_SKILL)
                call TriggerAddAction(RetsuDashLearn, function thistype.actRetsuDashLearn)
            endmethod
        endstruct
    endscope
endlibrary

Read the code from bottom to up

The issue comes from "call thistype.StartTimeOut()". What happens with this line being in the code is, after its timer (timeOut) runs the function "onPeriod" for some reason runs again (meaning the unit position gets changed again, its what onPeriod does). If I comment out "call thistype.StartTimeOut()", everything works properly (effectively meaning I don't use "StartTimeOut()" function). By working, the unit fades backwards (onPeriod runs once, not twice), and next time they right click "Dashed" runs.

However, I want "call thistype.StartTimeOut()" in the code so that in the event the player does nothing after this timer runs out (meaning bool ready2dash == true after 2 seconds), it'll reset the boolean to false and unpause the unit "effectively resetting the ability/unit". The ability works so when they cast it, they get pushed backwards and enters a stance, afterwards the next time they right click they will trigger the dash function, but they can only hold it for ~2 seconds (due to timeOut timer, which is currently bugging for me). Can you see why I encounter this bug?


I noticed that even if RetsuDashTimeOut() runs and executes its code, I can still right click to dash, meaning the boolean variable "ready2dash" is not even properly updated in the RetsuDashTimeOut(). However, it is updated in RetsuDaskClick() (meaning when you right click in the stance setting its bool value to false. But without the timer timeOut the stance is up permanently).

Eg. Printing "Timeout" first, I can still print out "Dashed" (improper). Afterwards, to do another print I have to use the ability again. Printing "Dashed" first can not print "Timeout" (proper). Printing "Timeout" first, should make printing "Dashed" impossible due to "TimeOut" setting bool to false (does not work).


TLDR:
OnPeriod runs twice when it should run once with "call thistype.StartTimeOut()" in the code (without this code, it runs once and everything works properly). Variable ready2dash not properly updating in RetsuDashTimeOut().
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
I'm not super familiar with most of these libraries and I haven't read all of your code yet, but just from experience I know that using BlzPauseUnitEx() can cause problems if you do it in response to casting a spell. I believe you need to "pause" the unit (it's really just a Stun) after a 1 frame delay, so a Timer of 0.0 seconds would suffice.
 
Level 7
Joined
Sep 16, 2016
Messages
185
I'm not super familiar with most of these libraries and I haven't read all of your code yet, but just from experience I know that using BlzPauseUnitEx() can cause problems if you do it in response to casting a spell. I believe you need to "pause" the unit (it's really just a Stun) after a 1 frame delay, so a Timer of 0.0 seconds would suffice.
Omg, I tried so long to debug this, and you're right, because my ability had 1s cooldown, it casted it again due to PuaseUnitEx, after using changing to PauseUnit, its fixed. I didn't think of the possibility of unit casting the spell again for whatever reason, thank you!
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
Omg, I tried so long to debug this, and you're right, because my ability had 1s cooldown, it casted it again due to PuaseUnitEx, after using changing to PauseUnit, its fixed. I didn't think of the possibility of unit casting the spell again for whatever reason, thank you!
No problem, and this type of issue can happen with other Events as well. The general fix is to add a 1 frame delay and see if that solves things - it usually does. An example of another case would be when issuing an Order in response to another issued Order. Also, I believe Item related Events can have a similar issue as well where the Item isn't considered "acquired" or "lost" yet at the time of the Event firing. It's a timing issue.

Using Lua as your scripting language makes this issue far less annoying since Timers are easier to manage:
Lua:
function DoAfterFrame(func)
    local t = CreateTimer()
    TimerStart(t, 0.0, false, function()
        func()
        PauseTimer(t)
        DestroyTimer(t)
    end)
end
That lets you run any function after a 1 frame delay.

Alternatively, you could start that Timer in your onCast function and reference the caster using a local variable:
Lua:
function onCast() -- When ability is cast
    local u = GetTriggerUnit()
    SetUnitAnimationByIndex(u, 4)
    SetUnitTimeScalePercent(u, 150)
    -- pause the unit after a 1 frame delay
    local t = CreateTimer()
    TimerStart(t, 0.0, false, function()
        PauseUnitEx(u, true)
        PauseTimer(t)
        DestroyTimer(t)
    end)
    -- do other stuff
end

Lua tends to do everything better and faster. The only flaw is that it doesn't have as many dedicated resources since Jass has been around forever. Mind you, it makes a lot of these Jass resources obsolete since it can do things in a much better way.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
Oh, but you shouldn't use PauseUnit(). That function sucks and adds even more issues. Just run BlzPauseUnitEx() after a 1 frame delay - maybe the library you have has an option for that (I would add one if it doesn't). You can also add it to the onPeriod function as a one time thing (if bool == false, pause, set bool = true). A 0.03 second delay should be short enough to prevent any weirdness.
 
Last edited:
Level 7
Joined
Sep 16, 2016
Messages
185
@Uncle I was mainly doing vjass because the old map I had is running in jass only, but if there was a simple tool to convert Lua to Jass/vJass, I would use lua in a heartbeat. Do you by any chance know if such a tool exists? vJass/Jass to Lua is not an option, maybe if I had a lot of free time I would consider converting the 180k jass lines, but I'm mostly doing this as a hobby so its not something I want to consider at the moment. :p

Your point regarding the delay and not using PauseUnit() has been conveyed, I also like the BlzPause more anyway. :)
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
@Uncle I was mainly doing vjass because the old map I had is running in jass only, but if there was a simple tool to convert Lua to Jass/vJass, I would use lua in a heartbeat. Do you by any chance know if such a tool exists? vJass/Jass to Lua is not an option, maybe if I had a lot of free time I would consider converting the 180k jass lines, but I'm mostly doing this as a hobby so its not something I want to consider at the moment. :p

Your point regarding the delay and not using PauseUnit() has been conveyed, I also like the BlzPause more anyway. :)

There is a tool that will convert all of your Jass to Lua but I have my doubts about it's precision. Bribe is much smarter than me but Warcraft 3 is such a mess that it's hard to believe that this will work smoothly. If your project was smaller I'd be more confident in suggesting it to you:

So I think you're stuck with Jass for now, writing Lua that converts to Jass sounds like it defeats the whole purpose. It's not the end of the world though, you get the benefit of 15+ years of resources and documentation.

To clarify about my comments on Pause:

This Pause function will freeze a unit, stopping any buff timers and expiration timers as well as freezing animations and possibly causing other weird side effects:
vJASS:
call PauseUnit(u, bool)

This Pause function will Stun a unit, the same way Storm Bolt or War Stomp works, but with the bonus of not requiring a Dummy + Dummy Spell:
vJASS:
call BlzPauseUnitEx(u, bool)
It also uses a counter system which tracks the number of Stuns applied by it. This allows the Stuns to stack properly as well as giving you the option to make a unit Stun Immune if you so desire. Could be useful for a boss fight.

The devs really should've just named it BlzStunUnit()... Anyway, I've made quite a few dash spells before and my solution has always been to use a 0.02 second periodic interval and apply BlzPauseUnitEx() on the very first execution of my periodic function. I also choose that time to play a "dash" animation. This appears to get the job done and seems to prevent any room for the unit to sneak in another action between the cast executing and the first 0.02 seconds passing.
 
Last edited:
Top