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

[System] Timer Tools

Timer Tools now supports .03125 timeouts

Happy.png


Timer tools max timeout is now 25

Okay.png
 
Level 10
Joined
May 27, 2009
Messages
494
I'm currently making some sort of spell apparently.. i can't seem to let it work with either CTM or CTTC.. and CTTC is worst 'cause the first cast will stop (if there are no enumerated units) and gives me a fatal error on second simultaneous cast (if there are any enumerated units)

here's the code lol
JASS:
scope MetalShot
    
    private struct MetalShot
        unit caster
        unit array arrows[3]
        player owner 
        integer lvl
        real array cos[3]
        real array sin[3]
        real dmg 
        real dist 
        private static constant real TIMEOUT = 0.031250000
        private static hashtable ht = InitHashtable()
        private group tmpGroup = bj_lastCreatedGroup
        
        implement CTT 
            local unit f
            local integer i
            local real x
            local real y
        implement CTTExpire
            if dist > 0 then
                set i = 0
                loop
                    exitwhen i >= 3
                    set x = GetUnitX(this.arrows[i]) + 21 * this.cos[i]
                    set y = GetUnitY(this.arrows[i]) + 21 * this.sin[i]
                    call SetUnitX(this.arrows[i],x)
                    call SetUnitY(this.arrows[i],y)
                    
                    call GroupEnumUnitsInRange(tmpGroup,x,y,250,null)
                    loop
                        set f = FirstOfGroup(tmpGroup)
                        exitwhen f == null
                        call GroupRemoveUnit(tmpGroup,f)
                        if IsUnitEnemy(f,this.owner) and not IsUnitType(f,UNIT_TYPE_STRUCTURE) and UnitAlive(f) and not HaveSavedBoolean(ht,this,GetHandleId(f)) then
                            call UnitDamageTarget(this.caster,f,this.dmg,false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_MAGIC,null)
                            call SaveBoolean(ht,this,GetHandleId(f),true)
                        endif
                    endloop
                    set i = i + 1
                endloop
                set dist = dist - 21
            else
                set i = 0
                loop
                    exitwhen i >= 3
                    call RemoveUnit(this.arrows[i])
                    set i = i + 1
                endloop
                call FlushChildHashtable(ht,this)
                call this.destroy()
            endif
        implement CTTNull
            set f = null
        implement CTTEnd
        
        static method start takes nothing returns boolean
            local thistype this = thistype.create()
            local integer i = 0
            local real angle 
            set this.caster = GetTriggerUnit()
            set this.owner = GetTriggerPlayer()
            set this.lvl = GetUnitAbilityLevel(this.caster,'A009')
            set this.dist = 700
            set this.dmg = 1000
            set angle = GetUnitFacing(this.caster)-45
            loop
                exitwhen i >= 3 
                set this.arrows[i] = CreateUnit(Player(15),'h006',GetUnitX(this.caster),GetUnitY(this.caster),angle)
                set this.cos[i] = Cos(angle*bj_DEGTORAD)
                set this.sin[i] = Sin(angle*bj_DEGTORAD)
                set angle = angle + 45
                set i = i + 1
            endloop
            return false 
        endmethod
        
        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A009',function thistype.start)
        endmethod
    endstruct
endscope
To reproduce it, you must cast the spell simultaneously... or while the first cast is still in effect

pretty bad lol
 
Level 10
Joined
May 27, 2009
Messages
494
lol that stopped me from using arrays..
lemme test

EDIT:
Yey it's working magnificently!! But i still use arrays >:D
Also, please update the docs... it didn't say that it is required that I must extend array
only the examples said it... o.o

ohh well
Nes Nes is awesome
By Nes Nes

Nes Nes is so awesome
That I wrote this awesome poem
How awesome

EDIT 2:
Perhaps you may update your Recycle library or this library.. struct Timer is being redeclared o.o
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
What does this tell you? Using standard timers is smart and better than using TimerUtils >.>, lolz.
Haha, thanks for pointing that out. :D
Now I don't feel bad anymore for not using any timer system at all in Gaias Retaliation, but manually handling all timer stuff.

Your system with the increase of speed when using one shot timers sounds neat.
However, there is something about it that will scare away most of the mappers in my oppinion:
It requires total recoding of almost everything as it uses a syntax that is totally different from using ordinary timers and other timer systems (with all the module stuff).
It's not done by replacing one or two lines with a text editor, so the amount of work people would have to invest into changing from like TimerUtils or T32 to this one on higher complexity maps is insane.
Not really your problem, though, just something I wanted to say. Maybe you can do something about it to make a switch between the systems easier? Maybe create a textmacro for compat for this?

Other than that: awesome stuff! I really love that module syntax. Too bad I am to lazy to switch to your system. ;(

... then again, I probably won't use enough timers anyway to get a noticable difference in fps.
 
Level 10
Joined
May 27, 2009
Messages
494
i don't know if it is a bug but it keeps on happening
So here's the scenario

this spell (based on channel, unit target, no cooldown) targets the caster itself repeatedly (ofc since the damage is too low) until it dies.. well it works normally but after my hero is revived.. and I cast it repeatedly on a target (this time, not the caster) it will go into an infinite loop or until the target dies.... dealing intense damage :O

well I know it can be avoided using an Enemy filter or adding an exception to the caster (and those only.. as of now) but... i would just like to share this one =D

i also like to ask if there are any possible code to skip the current loop? something like return or exitwhen true but instead.. the current instance only

here's the code..

JASS:
scope DragonUppercut

    private struct DUppercut extends array
        unit caster
        unit target
        player owner 
        real ux
        real uy
        real dmg
        integer count 
        integer lvl
        private static constant real TIMEOUT = 0.031250000
        private static constant real TIME = (0.7/TIMEOUT)
        private static boolean b = false
        
        implement CTTC
            local real x
            local real y
            local real conv 
            local real height 
            local real speed
        implement CTTCExpire
            
            if this.count <= TIME then
                
                set conv = (50/TIME)*this.count
                
                set height = Pow(conv-25,2)
                
                call SetUnitFlyHeight(this.target, 200-height,0)
                set this.count = this.count + 1
                
            else
                call UnitDamageTarget(this.caster,this.target,this.dmg,false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null)
                call SetUnitFlyHeight(this.target,GetUnitDefaultFlyHeight(this.target),0)
                call PauseUnit(this.target,false)
                call IssueImmediateOrder(this.target,"stop")
                call ResetHeroBurningMode(this.caster)
                call this.destroy()
                
            endif
            
            if not UnitAlive(this.target) then
                call SetUnitFlyHeight(this.target,GetUnitDefaultFlyHeight(this.target),0)
                call PauseUnit(this.target,false)
                call ResetHeroBurningMode(this.caster)
                call this.destroy()
            endif
            
        implement CTTCEnd
        
        static method start takes nothing returns boolean
            local thistype this = thistype.create()
            set this.caster = GetTriggerUnit()
            set this.target = GetSpellTargetUnit()
            set this.owner = GetTriggerPlayer()
            set this.ux = GetUnitX(this.target)
            set this.uy = GetUnitY(this.target)
            set this.count = 1
            set this.lvl = GetUnitAbilityLevel(this.caster,'A00C')
            
            call IssueImmediateOrder(this.target,"stop")
            call PauseUnit(this.target,true)
            if UnitAddAbility(this.target,'Arav') then
                call UnitRemoveAbility(this.target,'Arav')
            endif
            set this.dmg = (1+(0.4*this.lvl))*GetHeroStr(this.caster,true)
            
            return false 
        endmethod
        
        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A00C',function thistype.start)
            call AbilityPreload('A00C')
        endmethod
    endstruct
endscope
 
Level 10
Joined
May 27, 2009
Messages
494
ok i cross another problem
this time.. it will only allow one instance of this code to run... (the recently cast spell will run after a spell which was cast before it is finished)

--code secured--
:ogre_hurrhurr:

well this spell is meant to loop through 5 units but i just made it to loop on one since i'd like to test the code's output...
o.o
well i guess the code is pretty heavy for calculations lol
i attached a testmap
 
Last edited:
Level 10
Joined
May 27, 2009
Messages
494
if i don't use exitwhen true.. it will result into a double free attempt thus making the spells in queue stop from working

if i put the if then else code that contains the this.destroy() line at the end of the CTTCExpire.. it will go for a forever loop and dropping fps to 1 i think lol

well still.. i think exitwhen true only exits the current loop right? not the parent loop itself... since CTTC runs codes in a loop o.o

hmm.. still same problem occurs... the spells run in a "queue mode" or one after another D:
 
Level 10
Joined
May 27, 2009
Messages
494
hmm tried it but doesn't helped me too much.. it just made the code execute at a very fast rate... lol

perhaps i might just find a workaround with the loops
thanks

hmm i debugged the code.. and it looks like it goes into a loop at a maximum of 358-359 before the instance is destroyed...
i can't find the culprit but it seems like it's the same problem with my recent scenario.. having an infinite loop
plus i already optimized the code.. i guess

can't figure it out on my code D: keeps on looping something like that

JASS:
scope ArrowStorm

    private struct Data extends array
        boolean posNeg
        integer times 
        real maxAngle
        real dist 
        real angle
        real dx
        real dy
    endstruct
    
    private struct AStorm extends array
        unit caster
        player owner 
        integer count
        boolean allArrows
        real ux
        real uy
        real nextAngle
        real wait 
        private static constant real TIMEOUT = 0.031250000
        private static hashtable ht = InitHashtable()
        private static group tmpGroup
        
        implement CTTC
            local unit d
            local boolean b = false 
            local integer i = 0
            local integer int 
            local real x
            local real y
        implement CTTCExpire
            
            loop
                exitwhen i == this.count 
                
                call BJDebugMsg("Loop: " +I2S(i))
                set d = LoadUnitHandle(ht,this,i)
                set int = GetUnitId(d)
                
                if Data[int].posNeg then
                    
                    set Data[int].angle = Data[int].angle + 3
                    
                    if Data[int].angle >= Data[int].maxAngle then
                    
                        set x = Data[int].dx + Data[int].dist * Cos(Data[int].maxAngle * bj_DEGTORAD)
                        set y = Data[int].dy + Data[int].dist * Sin(Data[int].maxAngle * bj_DEGTORAD)
                        set b = true 
                        
                    else
                        set x = Data[int].dx + Data[int].dist * Cos(Data[int].angle * bj_DEGTORAD)
                        set y = Data[int].dy + Data[int].dist * Sin(Data[int].angle * bj_DEGTORAD)
                    endif
                    call BJDebugMsg("Set data positive")
                else
                    set Data[int].angle = Data[int].angle - 3
                    
                    if Data[int].angle <= Data[int].maxAngle then
                        set x = Data[int].dx + Data[int].dist * Cos(Data[int].maxAngle * bj_DEGTORAD)
                        set y = Data[int].dy + Data[int].dist * Sin(Data[int].maxAngle * bj_DEGTORAD)
                        set b = true 
                    else
                        set x = Data[int].dx + Data[int].dist * Cos(Data[int].angle * bj_DEGTORAD)
                        set y = Data[int].dy + Data[int].dist * Sin(Data[int].angle * bj_DEGTORAD)
                    endif
                    call BJDebugMsg("Set data negative")
                endif
                
                if b then
                    if Data[int].times >= 5 then
                        call RemoveUnit(d)
                        set this.count = this.count - 1
                    else
                        set Data[int].dist = Data[int].dist + 100
                        set Data[int].times = Data[int].times + 1
                        set Data[int].dx = x + Data[int].dist * Cos(Data[int].maxAngle * bj_DEGTORAD)
                        set Data[int].dy = y + Data[int].dist * Sin(Data[int].maxAngle * bj_DEGTORAD)
                        set Data[int].angle = Data[int].angle + 180
                        
                        if Data[int].posNeg then
                            set Data[int].posNeg = false 
                            set Data[int].maxAngle = Data[int].angle - 270
                        else
                            set Data[int].posNeg = true
                            set Data[int].maxAngle = Data[int].angle + 270
                        endif
                        call BJDebugMsg("times")
                    endif
                endif
                call SetUnitX(d,x)
                call SetUnitY(d,y)
                
                set b = false
                set i = i + 1
            endloop
            
            if this.count <= 0 then
                call FlushChildHashtable(ht,this)
                call this.destroy()
                call BJDebugMsg("destroy all")
            /*elseif not this.allArrows then
                if this.wait >= 0.4 then
                    set d = CreateUnit(Player(15),'hpea',this.ux,this.uy,0)
                    call SaveUnitHandle(ht,this,0,d)
                    set int = GetUnitId(d)
                    set Data[int].posNeg = true 
                    set Data[int].maxAngle = 360
                    set Data[int].dist = 150
                    set Data[int].angle = this.nextAngle
                    set Data[int].times = 0
                    set Data[int].dx = this.ux
                    set Data[int].dy = this.uy
                    set this.count = this.count + 1
                    set this.wait = 0
                    call BJDebugMsg("create unit " + I2S(this.count))
                    if this.count >= 5 then
                        set this.allArrows = true
                        call BJDebugMsg("limit unit")
                    endif
                else
                    set this.wait = this.wait + TIMEOUT
                endif*/
            endif
            
        implement CTTCNull
            set d = null
        implement CTTCEnd
        
        static method start takes nothing returns boolean
            local thistype this = thistype.create()
            local unit d
            local integer int 
            set this.caster = GetTriggerUnit()
            set this.owner = GetTriggerPlayer()
            set this.ux = GetUnitX(this.caster)
            set this.uy = GetUnitY(this.caster)
            set this.count = 1
            set this.allArrows = false 
            set this.wait = 0
            
            set d = CreateUnit(Player(15),'hpea',this.ux,this.uy,0)
            call SaveUnitHandle(ht,this,0,d)
            set int = GetUnitId(d)
            set Data[int].posNeg = true 
            set Data[int].maxAngle = 360
            set Data[int].dist = 150
            set Data[int].angle = GetRandomReal(0,360)
            set Data[int].times = 0
            set Data[int].dx = this.ux
            set Data[int].dy = this.uy
            
            set this.nextAngle = Data[int].angle 
            
            set d = null
            return false 
        endmethod
        
        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A000',function thistype.start)
        endmethod
    endstruct
endscope

EDIT:
seems the super duper loop bug can be fixed by changing the line exitwhen i == this.count to exitwhen i >= this.count
pretty weird bug...

EDIT 2:
well i guess i can't really fix that good ol' bug... the code will just run simultaneously and will not allow MUI casts...
i tested it on t32 and it works with MUI casts... perhaps i should use t32 on this one D:
well i removed my recent test map.. kinda lame lol
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Hm... I really don't know.

The double free error is coming from the same timer probably running multiple times, but to know that for sure, you'd need to output a timer instance to see if the same instance shows up again after the instance was destroyed within the timer.

When a timer is destroyed while it is expired, that timer is moved to a destruction stack. After everything is expired, the destruction stack is iterated over and all of the destroyed instances are freed.

Really, you need to be more descriptive with the errors. I can debug it as well, but I don't even have any time until next Thursday + I don't mod for wc3 anymore ;\.
 
Level 6
Joined
Oct 23, 2011
Messages
182
Anyone using this without any problems?
I'm keep getting error messages "Null timer expired on x"

All I did was change CTL into CTM (literally just the module names, and input .03125)
and bam. 1~2 minute into game, game bugs out

I'll try with other modules as well.

*Edit : I think this still bugs. With CTTC modules, tried changing timeouts but no success. Only thing I found was the error showed when the instance ended, and it requires more than 1 instance to happen.

*Edit2: Ok. Here is a demo map. Code is really simple. Learn endurance aura on chieftain, and try turning on/off immolation on demon hunter a few times.

Tt 2.1.6.0 doesn't seem to bug for some reason
 

Attachments

  • Bug.w3x
    85.6 KB · Views: 56
Last edited:
Level 2
Joined
Feb 22, 2012
Messages
13
Kinda confuse how do I set up the timers..

How would this look using your timer system.
JASS:
function Trig_Position_Actions takes nothing returns nothing
    if Red == true then
        call S(1)
    endif
endfunction

function InitTrig_Position takes nothing returns nothing
    set gg_trg_Position = CreateTrigger(  )
    call TriggerRegisterTimerEvent(gg_trg_Position, 0.01, true)
    call TriggerAddAction( gg_trg_Position, function Trig_Position_Actions )
endfunction

I learn by examples so please show me how the top jass code would look like using your system.
 
Level 9
Joined
Dec 3, 2010
Messages
162
Kinda confuse how do I set up the timers..

How would this look using your timer system.
JASS:
function Trig_Position_Actions takes nothing returns nothing
    if Red == true then
        call S(1)
    endif
endfunction

function InitTrig_Position takes nothing returns nothing
    set gg_trg_Position = CreateTrigger(  )
    call TriggerRegisterTimerEvent(gg_trg_Position, 0.01, true)
    call TriggerAddAction( gg_trg_Position, function Trig_Position_Actions )
endfunction

I learn by examples so please show me how the top jass code would look like using your system.

You would need to use a Struct.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,454
If you didn't understand from the description of TimerTools, if you are coding with "== true", if you are coding from converted GUI, if you are coding timed events using triggers instead of timers...

Maybe you should start with something more simple.

JASS:
@scope Position initializer Init@

@private @function Actions takes nothing returns nothing
    if Red/* == true*/ then
        call S(1)
    endif
endfunction

@private @function Init takes nothing returns nothing
    @call TimerStart(CreateTimer(), 0.01, true, function Actions)@
    /*
    set gg_trg_Position = CreateTrigger(  )
    call TriggerRegisterTimerEvent(gg_trg_Position, 0.01, true)
    call TriggerAddAction( gg_trg_Position, function Trig_Position_Actions )
    */
endfunction

@endscope@
 
Level 2
Joined
Feb 22, 2012
Messages
13
Meh, Wanted to use the system since its faster but since you want me to do that... I guess I'll stick with TimerUtils. I'm use to using TimerUtils its more easier to use for noobies like me. Since all you have to do is.

JASS:
local timer t=NewTimer()
call TimerStart(t, 0.01, true, function Handlerfunc)
set t=null

*Edit*
I never knew you can just can do "if red then" <-- which will read as = to true.. If you want it to read as false is it just "if not red then" <-- right? thanks for that.. learn something new lol.
What is scope for and "Private". I never learn about them they confuse me.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Scope makes everything inside inaccessible from the outside.
Private means that nothing outside the scope or library can use it.


It's usually useless to use private inside Scopes because they make things private anyways.

By the way, your code can be changed to:

call TimerStart(NewTimer(), 0.01, true, function Handlerfunc)

What ?!

Last time i checked no keyword private/public = public (without prefix, with the name as it), maybe it's private in Zinc, but definetly not with vJass.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
In theory here you don't need it, as long the handle will never be destroyed, which is the case with TimerUtils, since timers are recycled, never destroyed.
Then you don't null to the local handle variable, since the handle id will never be freed, and so the handle id leak bug can't obviously happen (the reason why we set the handle local variables to null).

Now, it's only the theory, i can't guarantee that there is not some other weird con, like a leak or a performance issue of the jass vm later if it happens many time.
Because it's jass.
 
Top