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

Timer problem

Status
Not open for further replies.
Level 5
Joined
May 25, 2009
Messages
103
Why does my trigger Timer fire with each of the 3 versions (here combined)?
My debug message "elapsed time for timer" says always 0.000 (until it is resumed ofc) as it should.

  • Timer Init
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Countdown Timer - Start Timer1 as a One-shot timer that will expire in 20.00 seconds
      • Countdown Timer - Pause Timer1
  • Timer
    • Events
    • Conditions
      • Or - Any (Conditions) are true
        • Conditions
          • (Remaining time for Timer1) Not equal to (Initial time for Timer1)
          • (Remaining time for Timer1) Not equal to 20.00
          • (Elapsed time for Timer1) Not equal to 0.00
    • Actions
      • Countdown Timer - Resume Timer1
 
Why does my trigger Timer fire with each of the 3 versions (here combined)?
You've put "Or" for conditions which means any of conditions will fire the trigger. I'm not sure but I think it should be "And" if you want all conditions to be true ("And" didn't work for me sometimes, tho).
 
Level 5
Joined
May 25, 2009
Messages
103
Thx for your reply, but it was simply meant that all three don't work - therefore the "combined" - sry for the misleading.

But there the conditions are working. So never mind, I'll manage it (could at least start the timer new, with fitting time)
It's a pitiy that we cant delete unanswered threads :( (before you've answered)
 
Last edited:
Level 13
Joined
May 10, 2009
Messages
868
I tried to reproduce the same thing, and it only worked upon using Map Initialization event - actions from second trigger aren't executed, as expected. However, somehow, the same problem happens with me as soon as I use "Time - Elapsed game time 0.00" event.
  • Start Timer
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • Countdown Timer - Start timer as a One-shot timer that will expire in 20.00 seconds
      • Countdown Timer - Pause timer
So this is the timer info at this point
  • Remaining time: 20.
  • Initial Time (timeout): 20.
  • Elapsed: 0.

Then:
  • checking in GUI
    • Events
      • Player - Player 1 (Red) types a chat message containing c as An exact match
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Remaining time for timer) Not equal to (Initial time for timer)
        • Then - Actions
          • Game - Display to (All players) the text: (Remaining: + ((String((Remaining time for timer))) + ( is not equal to initial: + (String((Initial time for timer))))))
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Remaining time for timer) Not equal to 20.00
        • Then - Actions
          • Game - Display to (All players) the text: (Remaining: + ((String((Remaining time for timer))) + is not equal to 20.00))
        • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Elapsed time for timer) Not equal to 0.00
        • Then - Actions
          • Game - Display to (All players) the text: (Elapsed: + ((String((Elapsed time for timer))) + is not equal to 0.00))
        • Else - Actions
result.png

None of those should be printed

Even if I use the following JASS code
JASS:
    local real remaining = TimerGetRemaining(udg_timer)
    local real elapsed = TimerGetElapsed(udg_timer)
    local real timeout = TimerGetTimeout(udg_timer)
 
    if remaining != timeout then
        call BJDebugMsg("remaining: "+R2S(remaining)+" != timeout: "+R2S(timeout))
    endif
 
    if remaining != 20. then
        call BJDebugMsg("remaining: "+R2S(remaining)+" != preset: 20.")
    endif
 
    if elapsed != 0. then
        call BJDebugMsg("elapsed: "+R2S(elapsed)+" != preset: 0.")
    endif
Untitled-2.png

I get the same results; every comparison returns true. Now, an interesting point is this:
JASS:
    local real remaining = TimerGetRemaining(udg_timer)
    local real elapsed = TimerGetElapsed(udg_timer)
    local real timeout = TimerGetTimeout(udg_timer)
 
    if not (remaining == timeout) then
        call BJDebugMsg("remaining: "+R2S(remaining)+" != timeout: "+R2S(timeout))
    endif
 
    if not (remaining == 20.) then
        call BJDebugMsg("remaining: "+R2S(remaining)+" != preset: 20.")
    endif
 
    if not (elapsed == 0.) then
        call BJDebugMsg("elapsed: "+R2S(elapsed)+" != preset: 0.")
    endif
The code simply works as intended.

EDIT: Also, if I change the value of "Time - Elapsed game time" from 0.00 to any number >= 0.50, all the triggers mentioned above work properly.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,178
Avoid absolute value comparisons with floating points. Arbitrary rounding can give you unexpected results. This is why the equals comparison for types real has an epsilon to it.

Use a type cast exploit to examine the actual 32bit floating point construction of the two reals being compared. There might be 1 or more bits different.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,178
Is there any way of visualizing the entirety of a float on war3?
Use one of the various unpatched type cast exploits to convert from real to integer. The integer will be some strange large number that makes no sense. One can set a calculator to programmer mode and enter the decimal number of the integer into it to convert it to hex and binary. Use the specification for 32bit floating points to decode this into sign bit, exponent and significand. At this stage the exact structural differences will become very clear.
 
Level 5
Joined
May 25, 2009
Messages
103
Thank you both!

I tried to reproduce the same thing, and it only worked upon using Map Initialization event - actions from second trigger aren't executed, as expected.

Thx for the hint. It's rigged with elapsed game time under 1 second, too. With 1 second my trigger works, but with Super Good's note

Avoid absolute value comparisons with floating points. Arbitrary rounding can give you unexpected results. This is why the equals comparison for types real has an epsilon to it.

Use a type cast exploit to examine the actual 32bit floating point construction of the two reals being compared. There might be 1 or more bits different.

I'll think of
  • Untitled Trigger 003
    • Events
    • Conditions
      • (Integer((Elapsed time for Timer1))) Not equal to 0
    • Actions
instead (and keep in mind, that these integers are cutted, not rounded) - this works for Map Initialization, too.
This solution seems to be more bug resistant (like the above Initialization/below 1 second problem), but if the 1 second solution works (like some testing suggests), it would be more accurate (if something happens close behind one another) I think - as long as I check only for the elapsed time = 0.
So I tend to real-comparisons with an initial timer setup at 1 second gaming time. Any apprehensions?

:thumbs_up:
 
Last edited:
Level 13
Joined
May 10, 2009
Messages
868
Then that sounds worse than I thought, because for me (in single player) the elapsed time being 0.50 or higher makes those conditions work as intended, whereas, for you, the event should have its value set to 1.00 or higher.
I tried to type cast reals into integer using this simple function:
JASS:
function r2i takes real r returns integer
    return r
endfunction
// which is not the same as the native R2I
and I have done another test where I use that 'r2i' custom function, trying to see the timer's real value when the game elapsed time is 0.10 and 0.50.

0.10:
  • Initialization
    • Events
      • Time - Elapsed game time is 0.10 seconds
    • Conditions
    • Actions
      • Countdown Timer - Start timer as a One-shot timer that will expire in 1.00 seconds
      • Countdown Timer - Pause timer
0.50:
  • Initialization
    • Events
      • Time - Elapsed game time is 0.50 seconds
    • Conditions
    • Actions
      • Countdown Timer - Start timer as a One-shot timer that will expire in 1.00 seconds
      • Countdown Timer - Pause timer
Display timer info:
JASS:
function r2i takes real r returns integer
    return r
endfunction

function ShowValues takes nothing returns nothing
    call ClearTextMessages()
    call BJDebugMsg("|cffffcc00Blizzard's type casting (R2I)|r")
    call BJDebugMsg("Remaining: "+I2S(R2I(TimerGetRemaining(udg_timer))))
    call BJDebugMsg("Timeout: "+I2S(R2I(TimerGetTimeout(udg_timer))))
    call BJDebugMsg("Elapsed: "+I2S(R2I(TimerGetElapsed(udg_timer))))
    call BJDebugMsg("|cffffcc00Custom function (r2i)|r")
    call BJDebugMsg("Remaining: "+I2S(r2i(TimerGetRemaining(udg_timer))))
    call BJDebugMsg("Timeout: "+I2S(r2i(TimerGetTimeout(udg_timer))))
    call BJDebugMsg("Elapsed: "+I2S(r2i(TimerGetElapsed(udg_timer))))
    call BJDebugMsg(" ")
endfunction

//===========================================================================
function InitTrig_ESC takes nothing returns nothing
    set gg_trg_ESC = CreateTrigger(  )
    call TriggerRegisterPlayerEvent(gg_trg_ESC, Player(0), EVENT_PLAYER_END_CINEMATIC)
    call TriggerAddAction(gg_trg_ESC, function ShowValues)
endfunction
Screenshots:
0.10:
Untitled-1.png


0.50:
Untitled-2.png


As DSG said, they are not safe to be compared like that. I'd still use typecasting instead.
Also, while I was testing this, I tried to change the elapsed game time to 0.25, and it returned the same results as the 0.50's, which means that my claim about having an elapsed game time higher than X value is wrong and not safe at all. You'd be better off typecasting when comparing real data types. If you're worried about rounding values, try to use this:

JASS:
function RoundUp takes real r returns integer
    return R2I(r + .5)
endfunction
// or
function RoundDown takes real r returns integer
    return R2I(r - .5)
endfunction

Example:
  • Initialization
    • Events
      • Time - Elapsed game time is 0.10 seconds
    • Conditions
    • Actions
      • Countdown Timer - Start timer as a One-shot timer that will expire in 1.00 seconds
      • Countdown Timer - Pause timer
JASS:
function ShowValues takes nothing returns nothing
    call ClearTextMessages()
    call BJDebugMsg("|cffffcc00Blizzard's typcasting (|rR2I|cffffcc00)|r")
    call BJDebugMsg("Remaining: "+I2S(R2I(TimerGetRemaining(udg_timer))))
    call BJDebugMsg("Timeout: "+I2S(R2I(TimerGetTimeout(udg_timer))))
    call BJDebugMsg("Elapsed: "+I2S(R2I(TimerGetElapsed(udg_timer))))
    call BJDebugMsg("|cffffcc00Custom function (|rRoundUp|cffffcc00)|r")
    call BJDebugMsg("Remaining: "+I2S(RoundUp(TimerGetRemaining(udg_timer))))
    call BJDebugMsg("Timeout: "+I2S(RoundUp(TimerGetTimeout(udg_timer))))
    call BJDebugMsg("Elapsed: "+I2S(RoundUp(TimerGetElapsed(udg_timer))))
    call BJDebugMsg(" ")
endfunction

//===========================================================================
function InitTrig_ESC takes nothing returns nothing
    set gg_trg_ESC = CreateTrigger(  )
    call TriggerRegisterPlayerEvent(gg_trg_ESC, Player(0), EVENT_PLAYER_END_CINEMATIC)
    call TriggerAddAction(gg_trg_ESC, function ShowValues)
endfunction
Untitled-4.png
 
Last edited:

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,178
instead (and keep in mind, that these integers are cutted, not rounded) - this works for Map Initialization, too.
This solution seems to be more bug resistant (like the above Initialization/below 1 second problem), but if the 1 second solution works (like some testing suggests), it would be more accurate (if something happens close behind one another) I think - as long as I check only for the elapsed time = 0.
So I tend to real-comparisons with an initial timer setup at 1 second gaming time. Any apprehensions?
That has nothing to do with what I said?

Very small values have a lot of decimal places of accuracy due to how floats work. Far more decimal places that JASS allows one to print. As such it is completely possible that for small values of time the error is too small to detect via normal means and is large enough to throw off comparisons, expect equals which has an epsilon built into it.

The custom conversion function is actually converting a 32bit float bitwise into an integer, similar to a C++ style reinterpret cast. It allows you to see differences in floating points that would otherwise be printed the same. The numeric value returned is a lossless representation of the float, the actual integer value is not related to the value of the float because floats encode numbers differently from integers. In languages like Java and C++ one can dump floats to a very high precision meaning such conversion is not necessary, however in JASS one has to convert like this to tell the difference between very small values.

What is silly with JASS is that there exists real values where by the following is true.
A and B are real variables.
A == B // ture
A != B // true​
 
Level 5
Joined
May 25, 2009
Messages
103
Thx, I get it now :)

While testing I saw that these bitwise integers differ only in the last positions, too.

Since I mostly need to check if a timer has already started and since wc3 seem to inprint these microseconds into the 32bit, I could try this workaround, f.e.:
  • Untitled Trigger 005 Copy
    • Events
    • Conditions
    • Actions
      • Custom script: set udg_TempString = I2S(r2i(TimerGetRemaining(udg_timer)))
      • Set TempInteger = ((Integer(TempString)) / 1000)
and therefore cut the last positions. Then I could compare Integers which are quite accurate.
That way I reduce the possible overlap with other events during my check (and the possible events beeing able to overlap with my timer checks arent as many).

Sure, a big workaround without comparing the timer directly would do it, too.
So either way, thx for beeing able to understand these matters a little bit more.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,178
I suggest looking how floating points work. That hack might or might not be always reliable.

I think simplest solution to test if a timer is running is to set a boolean variable to true when running and set it to false when the timer expires.
 
Last edited:
Status
Not open for further replies.
Top