diff options
| author | quarrel07 <178681861+quarrel07@users.noreply.github.com> | 2026-07-23 11:21:36 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-23 12:21:36 -0600 |
| commit | 78a012ce06b8756870e18230c3957c00cc2a66da (patch) | |
| tree | 358041ef692c8e014f3b232d4ac2ffb9d17e6cf0 | |
| parent | 4c24ae2bc2294c96a81d296a68b6d404f34358e7 (diff) | |
Fix the reset button doing nothing or looping the start screen when pressed repeatedly (#719)
* Make the reset button reliable from any screen
Pressing reset repeatedly near app start could do nothing, fade the music
with no reset, or bounce the press-start screen back to itself. Three
defects in the old handler:
- It ran from the ImGui widget, racing the menu state machine; a press
landing mid-fade was re-advanced by the in-flight transition (the
press-start loop).
- It always wrote gGamestateNext = MAIN_MENU_FROM_QUIT; once the game was
already in that state a repeat write is swallowed by the != guard in
main.c, so nothing happened.
- CM_ResetAudio ran unconditionally, so the music faded even when the
reset was swallowed.
The widget now only sets an atomic request flag; ApplyPendingReset (top of
push_frame, on the game loop) performs the reset. It alternates between
the two identical FROM_QUIT gamestates so every press trips the gamestate
switch, and re-enters the menus through the intro's own transition
protocol (FADE_MODE_LOGO -> setup_menus rebuild + fresh fade-in), which
replaces any in-flight transition. Audio only fades when a reset actually
executes. The dead gSkipIntro switch in the old handler (an unconditional
override below it always won) is gone; behavior is unchanged: reset lands
on the logo intro, or the start menu in debug mode.
Play-verified: hammering reset during the splash screens restarts the
logo every press; reset mid-fade after press-start no longer loops back;
resets from the main menu and mid-race work as before.
* Honor gSkipIntro when picking the post-reset screen
Review feedback on #719: restore the gSkipIntro switch from the old
handler instead of hard-coding the logo intro, keeping the debug-mode
override on top (no else), matching the boot-time logic in main().
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: MegaMech <MegaMech@users.noreply.github.com>
| -rw-r--r-- | src/port/Game.cpp | 67 | ||||
| -rw-r--r-- | src/port/Game.h | 1 | ||||
| -rw-r--r-- | src/port/ui/Menu.h | 44 |
3 files changed, 72 insertions, 40 deletions
diff --git a/src/port/Game.cpp b/src/port/Game.cpp index 8ccd382e6..1c6d49af9 100644 --- a/src/port/Game.cpp +++ b/src/port/Game.cpp @@ -6,6 +6,7 @@ #include <fast/Fast3dWindow.h> #include <memory> +#include <atomic> #include "engine/World.h" #include "engine/AllTracks.h" @@ -911,7 +912,73 @@ void CM_ResetAudio(void) { } } +static std::atomic<bool> sResetRequested{ false }; + +void CM_RequestReset(void) { + sResetRequested.store(true); +} + +// The reset widget only requests; the reset is applied here at the top of the +// game frame. Applying it from the widget raced the menu state machine: a +// press landing mid-fade was re-advanced by the in-flight transition, and a +// repeat press rewrote the gamestate it had already set, which the != guard +// in main.c swallows while the audio fade still runs (silent no-op). +static void ApplyPendingReset() { + if (!sResetRequested.exchange(false)) { + return; + } + + // The FROM_QUIT gamestates run identical inits; alternating keeps + // gGamestateNext != gGamestate true so every press re-enters the menus. + gGamestateNext = (gGamestate == MAIN_MENU_FROM_QUIT) ? START_MENU_FROM_QUIT : MAIN_MENU_FROM_QUIT; + gIsGamePaused = 0; + // Reset credits + D_800DC5E4 = 0; + gTourComplete = false; + SetMarioRaceway(); + memset(&gGameModeMenuColumn, 0, sizeof(s8) * NUM_ROWS_GAME_MODE_MENU); + memset(&gGameModeSubMenuColumn, 0, sizeof(s8) * NUM_COLUMN_GAME_MODE_SUB_MENU * NUM_ROWS_GAME_MODE_SUB_MENU); + + CM_ResetAudio(); + + // Close the editor. + if (gEditor.IsEnabled()) { + gEditor.Disable(); + } + + // Set the debug menu track browsing index back to zero + TrackBrowser::Instance->Reset(); + + // Land on the same screen the gSkipIntro setting picks at boot. + switch (CVarGetInteger("gSkipIntro", 0)) { + case 0: + gMenuSelection = HARBOUR_MASTERS_MENU; + break; + case 1: + gMenuSelection = LOGO_INTRO_MENU; + break; + case 2: + gMenuSelection = START_MENU; + break; + case 3: + gMenuSelection = MAIN_MENU; + break; + } + + // Debug mode override gSkipIntro + if (CVarGetInteger("gEnableDebugMode", 0) == true) { + gMenuSelection = START_MENU; + } + // Re-enter through the intro's own transition protocol (see HM_TickIntro): + // FADE_MODE_LOGO makes setup_menus rebuild the menu items and start a + // fresh fade-in, replacing any in-flight transition that would otherwise + // advance the stale screen right after the reset. + gMenuFadeType = 0; + gFadeModeSelection = FADE_MODE_LOGO; +} + void push_frame() { + ApplyPendingReset(); GameEngine::StartAudioFrame(); GameEngine::Instance->StartFrame(); thread5_iteration(); diff --git a/src/port/Game.h b/src/port/Game.h index 63ad92a4b..edd1902d8 100644 --- a/src/port/Game.h +++ b/src/port/Game.h @@ -35,6 +35,7 @@ extern Registry<ActorInfo, const SpawnParams&> gActorRegistry; extern Registry<ItemInfo> gItemRegistry; extern DataRegistry<RandomItemTable> gItemTableRegistry; World* GetWorld(void); // Retrieve the world instance +void CM_RequestReset(void); // Queue a game reset; applied at the top of the next game frame #endif // NOLINTBEGIN(readability-identifier-naming) diff --git a/src/port/ui/Menu.h b/src/port/ui/Menu.h index f3b2fd3dd..7e84e5d26 100644 --- a/src/port/ui/Menu.h +++ b/src/port/ui/Menu.h @@ -62,46 +62,10 @@ class Menu : public GuiWindow { "Searches all menus for the given text, including tooltips.")) } } } }; virtual void ProcessReset() { - gGamestateNext = MAIN_MENU_FROM_QUIT; - gIsGamePaused = 0; - // Reset credits - D_800DC5E4 = 0; - gTourComplete = false; - SetMarioRaceway(); - memset(&gGameModeMenuColumn, 0, sizeof(s8) * NUM_ROWS_GAME_MODE_MENU); - memset(&gGameModeSubMenuColumn, 0, sizeof(s8) * NUM_COLUMN_GAME_MODE_SUB_MENU * NUM_ROWS_GAME_MODE_SUB_MENU); - - CM_ResetAudio(); - - switch(CVarGetInteger("gSkipIntro", 0)) { - case 0: - gMenuSelection = HARBOUR_MASTERS_MENU; - break; - case 1: - gMenuSelection = LOGO_INTRO_MENU; - break; - case 2: - gMenuSelection = START_MENU; - break; - case 3: - gMenuSelection = MAIN_MENU; - break; - } - - // Close the editor. - if (gEditor.IsEnabled()) { - gEditor.Disable(); - } - - // Set the debug menu track browsing index back to zero - TrackBrowser::Instance->Reset(); - - // Debug mode override gSkipIntro - if (CVarGetInteger("gEnableDebugMode", 0) == true) { - gMenuSelection = START_MENU; - } else { - gMenuSelection = LOGO_INTRO_MENU; - } + // Only request the reset: it is applied at the top of the next game + // frame (ApplyPendingReset in Game.cpp), where it cannot race the menu + // state machine mid-fade or be swallowed as a repeated gamestate write. + CM_RequestReset(); } private: |
