GMS 2.3+ Switch state needs to be set TWICE via the same code before it maintains state

Smacktard

Member
I have a menu system with different states. One of the states is activated by an external event -- oComputer -- that then passes the following values on to the oPauseMenu event:

GML:
{oPauseMenu.savedelay = 1; oPauseMenu.subpointer = 1; oPauseMenu.pausemenustate = pausestate.computeruse} // Asks the question and runs the variables
oPauseMenu's main state is unpaused, which happens when you press "Escape" to get out of the menu. This changes the state to the small computer menu displaying.
oPauseMenu.savedelay is a timer that stops from automatically activating the "No" option and closing out of the menu in the same step that you open the menu. It counts down to 0 as soon as the oPauseMenu is opened.
oPauseMenu.subpointer just shows which option you're selecting. It defaults to 1.

The problem is within the code in the oPauseMenu object, specfically in pausestate.computeruse, and specifically in the bolded section below (NOTE: search for the [.B] and [/.B] :

GML:
    #region Computer Menu
    case pausestate.computeruse:     
        pause = 1;
     
        if screensave == 0
            {screensave = sprite_create_from_surface(application_surface,0,0,surface_get_width(application_surface),surface_get_height(application_surface),0,1,0,0);}
        instance_deactivate_all(true);
     
        var _compboxX = display_get_gui_width()/2;    // Where to start drawing text
        var _compboxY = display_get_gui_height()/5;    // Where to start drawing text
        var _compboxXscale = ((_compboxX * 0.25) + (_compboxX * 1.25)) / xscaleL / 32 // Refers to the width in pixels of the sprite being drawn
        var _compboxYscale = ((_compboxY * 1.25) + (_compboxY * 1.50)) / yscaleL / 32
        var _compfontsize = 29 * xscaleN    // For making window symmetrical even with ">" drawn in front of "Yes"
        // Places pointer in correct spot if game is translated
        var _fa_nocompbox_pointer = string_length(DisplayText("9")) * 29 * xscaleL    // 29 = size of each letter at scale of 1
 
        // Draws the box
        {draw_sprite_ext(windowsprite,0,(_compboxX),(_compboxY*1.5),_compboxXscale,_compboxYscale,0,c_white,1)}
        {draw_sprite_ext(spr_gui_tabs_vex,0,(_compboxX*1.31),(_compboxY*0.72),xscaleN,xscaleN,0,c_white,1)}
 
                draw_set_halign(fa_center)
                draw_text_transformed(_compboxX, _compboxY, DisplayText("104"), xscaleL,yscaleL, 0);        // Reset the room?
                draw_set_halign(fa_left)
                draw_text_transformed(_compboxX*0.75+_compfontsize, _compboxY*1.5, DisplayText("8"), xscaleL,yscaleL, 0);        // Yes
                draw_set_halign(fa_right)
                draw_text_transformed(_compboxX*1.25, _compboxY*1.5, DisplayText("9"), xscaleL,yscaleL, 0);        // No
                 
                if key_left {subpointer--;}
                else if key_right {subpointer++;}
                subpointer = clamp(subpointer,0,1)
                     
                if (subpointer = 0)
                {
                    draw_set_halign(fa_left)
                    if key_use && savedelay < 1 // Resets room variables; savedelay is important as a timer
                    {
                        instance_activate_all();
                        pause = 0;
                        screensave = 0;
                        subpointer = 0;
                        scr_compreset();
                    }
                    draw_text_transformed(_compboxX*0.70+_compfontsize, _compboxY*1.5, ">", xscaleL,yscaleL, 0);
                }
                else if (subpointer = 1)
                {
[B]                    if key_use && savedelay < 1
                    {
                        instance_activate_all();
                        pausemenustate = pausestate.unpaused;
                        screensave = 0;
                        pause = 0;
                        savedelay = 0;
                        subpointer = 0;
                    }[/B]

                    draw_text_transformed(_compboxX*1.25 - _fa_nocompbox_pointer, _compboxY*1.5, ">", xscaleL,yscaleL, 0);
                }
                savedelay--; // Just used as a timer so that this menu biz doesn't activate automatically when you use the computer
                 
    break;
    #endregion
When this state runs, and subpointer = 1, I should only have to press key_use ONCE to close the menu.

When this state runs, I should only have to press key_use ONCE to close the menu. However, I in fact have to press it TWICE. On the first press, the screensave screenshot is deleted, but none of the events are reactivated. Also, the "Reset the room?" text, box, pointers, etc. still work. I can even move the pointer over to "Yes" and reset the room. If I want to close the menu, I have to press "No" again. Then I finally get the result I'd been looking for.

What's strange is that I SEE the above code change pausemenustate to unpaused, and then immediately after, it's changed back. When I press "Use" again, the same code runs again, but I get a different result. Nowhere else in the game is pausemenustate set to pausemenustate = pausestate.computeruse changed in the game... so why would it be changed IMMEDIATELY back to it after supposedly changing states?

edit: if I remove the "&& savedelay < 1" the code runs correctly, EXCEPT THAT the menu closes IMMEDIATELY as soon as it opens, which is obviously undesirable. So the timer I'm using to *not* break the code is what breaks it in a different and inexplicable way.
 
Last edited:

FredFredrickson

Artist, designer, & developer
GMC Elder
Why are you using an "else if", rather than an "else", after checking if subpointer is 0?

Basically, if subpointer is 0, it will skip the check for it being 1 until you call this code again. I think that might be your problem.

You should change it to an "else" or just break it out into its own if/then case.
 
Last edited:

Smacktard

Member
Why are you using an "else if", rather than an "else", after checking if subpointer is 0?

Basically, if subpointer is 0, it will skip the check for it being 1 until you call this code again. I think that might be your problem.

You should change it to an "else" or just break it out into its own if/then case.
Nope, neither of these solutions solve the problem.

EDIT: Some observances:

All of the above code is placed in DRAW GUI.

If the "bolded" code form original post is placed in DRAW GUI End, it works. HOWEVER, when the menu is first opened, it looks like there is a screenshot taken, displayed, then erased, then taken and displayed again. After this, the menu works normally.
 
Last edited:

Smacktard

Member
This is probably your problem. Look at the stuff you're doing in a Draw event... activating instances? Manipulating timers? Those things aren't drawing, so they shouldn't be in Draw events.
And yet, when I place it in a Step event, the same result occurs. In fact, the only place this code seems to work (semi-)appropriately is in the Draw GUI End event.
 

Smacktard

Member
Have you considered the placement of your ++ and - -?
I moved everything non-draw-related into the step event and the problem was still occurring. So, I (accidentally) stumbled upon a solution: if I have the "end state, close menu" event in BOTH the step AND the Draw GUI End, the menu closes normally.

I suppose this makes sense, considering it had always taken, no matter what solution I'd come up with, two "walk-throughs" of that code for the event to work normally. Running it concurrently in two different steps on the same event seems to just work, for god knows why.
 

FredFredrickson

Artist, designer, & developer
GMC Elder
Cool, but that's not a good solution.

You realize that having ++ and - - at the end of your variables makes them return the previous value, yes? Could that be the issue?
 

Smacktard

Member
Cool, but that's not a good solution.

You realize that having ++ and - - at the end of your variables makes them return the previous value, yes? Could that be the issue?
It may not be "good", but when it's the only solution I can find that'll work, it's "good enough" :D

-- and ++ at the end of variables returns the previous value + or - 1. That's what I wanted to happen. I wanted the -- event to run down AFTER the timer check, not before, otherwise it would automatically close the menu immediately after it was opened.
 

FredFredrickson

Artist, designer, & developer
GMC Elder
-- and ++ at the end of variables returns the previous value + or - 1.
That's not right.

It returns the previous value, then increments the value. Which means the values you're getting are lagging by a frame.

It will only return the value +/- 1 if you put it before the variable.
 

Smacktard

Member
That's not right.

It returns the previous value, then increments the value. Which means the values you're getting are lagging by a frame.

It will only return the value +/- 1 if you put it before the variable.
Excuse my obtuseness, but that's what I wanted, isn't it? If it returned that value the same frame, then the "key_use && savedelay < 1" would run the same frame the menu was opened, and thus instantly close it before the player had a chance to do anything.

...Effectively meaning that it's the same as putting --savedelay; BEFORE the "key_use && savedelay < 1" event, right?

Or am I wrong here?
 

Nidoking

Member
It returns the previous value, then increments the value. Which means the values you're getting are lagging by a frame.

It will only return the value +/- 1 if you put it before the variable.
You'll have to point out where the code posted is using the result of an increment or decrement operator directly. I've been through it about five times and I don't see a single place where it isn't an entire expression.

What you're talking about would be the difference between if (savedelay++ < 1) and if (++savedelay < 1), but savedelay++; if (savedelay < 1) and ++savedelay; if (savedelay < 1) are exactly functionally equivalent.
 

FredFredrickson

Artist, designer, & developer
GMC Elder
You'll have to point out where the code posted is using the result of an increment or decrement operator directly. I've been through it about five times and I don't see a single place where it isn't an entire expression.

What you're talking about would be the difference between if (savedelay++ < 1) and if (++savedelay < 1), but savedelay++; if (savedelay < 1) and ++savedelay; if (savedelay < 1) are exactly functionally equivalent.
Sure, you're right.

Thing is, it seems like the problem is a race condition of it works in the end draw event, and not anywhere else. Either within this code or with silver other object. So to me, it makes sense to eliminate all possibilities of variables returning unexpected values.
 

FredFredrickson

Artist, designer, & developer
GMC Elder
Then it would be better to focus on possibilities of variables returning unexpected values rather than impossibilities.
Haha, alright dude. Thanks for the tip.

I admit that it might not have been helpful to troubleshoot that particular aspect of it. Clearly something is wrong with the code though, and more snark isn't going to fix it. šŸ™ƒ
 

Smacktard

Member
For anyone who's curious, I fixed this issue by moving all of "if subpointer = 0" and "if subpointer = 1" code to the Draw GUI End and inserted a "io_clear()" both the computer event AND the subpointer event.
 
Top