summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorquarrel07 <178681861+quarrel07@users.noreply.github.com>2026-07-23 11:21:36 -0700
committerGitHub <noreply@github.com>2026-07-23 12:21:36 -0600
commit78a012ce06b8756870e18230c3957c00cc2a66da (patch)
tree358041ef692c8e014f3b232d4ac2ffb9d17e6cf0
parent4c24ae2bc2294c96a81d296a68b6d404f34358e7 (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.cpp67
-rw-r--r--src/port/Game.h1
-rw-r--r--src/port/ui/Menu.h44
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: