From 295e8dd0ce93bb9ae0fc89d842ce4b777080ec9c Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Jul 2025 19:25:52 +0100 Subject: [PATCH 01/17] Add Goner Choice - Selection between Tails' Way (existing Tutorial) and Eggman's Way (Playground) - Semi-passable UI - Characterful descriptions - Add "PlaygroundRoute" condition to Challenges - Fires if you select Eggman's Way - 0 Chao Keys unless you go back to Goner for the outro (which Playground skips) --- src/deh_soc.c | 8 +- src/doomstat.h | 1 + src/g_game.c | 3 +- src/g_gamedata.cpp | 2 + src/g_gamedata.h | 4 +- src/k_menu.h | 3 + src/lua_script.c | 3 + src/m_cond.c | 5 + src/m_cond.h | 4 +- src/menus/main-goner.cpp | 304 +++++++++++++++++++++++++++++++++++---- 10 files changed, 305 insertions(+), 32 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 53da34d0b..53ad24ae6 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2965,7 +2965,8 @@ static void readcondition(UINT16 set, UINT32 id, char *word2) || (++offset && fastcmp(params[0], "REPLAY")) || (++offset && fastcmp(params[0], "CRASH")) || (++offset && fastcmp(params[0], "TUTORIALSKIP")) - || (++offset && fastcmp(params[0], "TUTORIALDONE"))) + || (++offset && fastcmp(params[0], "TUTORIALDONE")) + || (++offset && fastcmp(params[0], "PLAYGROUNDROUTE"))) { //PARAMCHECK(1); ty = UC_ADDON + offset; @@ -3633,6 +3634,11 @@ void readmaincfg(MYFILE *f, boolean mainfile) titlemap = Z_StrDup(word2); titlechanged = true; } + else if (fastcmp(word, "TUTORIALPLAYGROUNDMAP")) + { + Z_Free(tutorialplaygroundmap); + tutorialplaygroundmap = Z_StrDup(word2); + } else if (fastcmp(word, "TUTORIALCHALLENGEMAP")) { Z_Free(tutorialchallengemap); diff --git a/src/doomstat.h b/src/doomstat.h index 748b0773e..9a8efea18 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -280,6 +280,7 @@ extern boolean looptitle; extern char * bootmap; //bootmap for loading a map on startup extern char * podiummap; // map to load for podium +extern char * tutorialplaygroundmap; // map to load for playground extern char * tutorialchallengemap; // map to load for tutorial skip extern UINT8 tutorialchallenge; #define TUTORIALSKIP_NONE 0 diff --git a/src/g_game.c b/src/g_game.c index d9c8c1551..be9af9549 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -179,6 +179,7 @@ boolean looptitle = true; char * bootmap = NULL; //bootmap for loading a map on startup char * podiummap = NULL; // map to load for podium +char * tutorialplaygroundmap = NULL; // map to load for playground char * tutorialchallengemap = NULL; // map to load for tutorial skip UINT8 tutorialchallenge = TUTORIALSKIP_NONE; @@ -5076,7 +5077,7 @@ void G_EndGame(void) // Only do evaluation and credits in singleplayer contexts if (!netgame) { - if (gametype == GT_TUTORIAL) + if (gametype == GT_TUTORIAL && gamedata->gonerlevel < GDGONER_DONE) { // Tutorial was finished gamedata->tutorialdone = true; diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index 077eb0b81..2695dffc9 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -83,6 +83,7 @@ void srb2::save_ng_gamedata() ng.milestones.enteredtutorialchallenge = gamedata->enteredtutorialchallenge; ng.milestones.sealedswapalerted = gamedata->sealedswapalerted; ng.milestones.tutorialdone = gamedata->tutorialdone; + ng.milestones.playgroundroute = gamedata->playgroundroute; ng.milestones.gonerlevel = gamedata->gonerlevel; ng.prisons.thisprisoneggpickup = gamedata->thisprisoneggpickup; ng.prisons.prisoneggstothispickup = gamedata->prisoneggstothispickup; @@ -471,6 +472,7 @@ void srb2::load_ng_gamedata() gamedata->enteredtutorialchallenge = js.milestones.enteredtutorialchallenge; gamedata->sealedswapalerted = js.milestones.sealedswapalerted; gamedata->tutorialdone = js.milestones.tutorialdone; + gamedata->playgroundroute = js.milestones.playgroundroute; gamedata->gonerlevel = js.milestones.gonerlevel; gamedata->thisprisoneggpickup = js.prisons.thisprisoneggpickup; diff --git a/src/g_gamedata.h b/src/g_gamedata.h index 0027ef582..9209ecb2b 100644 --- a/src/g_gamedata.h +++ b/src/g_gamedata.h @@ -96,6 +96,7 @@ struct GamedataMilestonesJson final bool enteredtutorialchallenge; bool sealedswapalerted; bool tutorialdone; + bool playgroundroute; SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMilestonesJson, @@ -109,7 +110,8 @@ struct GamedataMilestonesJson final finishedtutorialchallenge, enteredtutorialchallenge, sealedswapalerted, - tutorialdone + tutorialdone, + playgroundroute ) }; diff --git a/src/k_menu.h b/src/k_menu.h index 28db4810e..b612151da 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -229,9 +229,12 @@ void M_GonerBGTick(void); void M_GonerBGImplyPassageOfTime(void); void M_DrawGonerBack(void); void M_GonerProfile(INT32 choice); +void M_GonerChoice(INT32 choice); void M_GonerTutorial(INT32 choice); +void M_GonerPlayground(INT32 choice); void M_GonerResetLooking(int type); void M_GonerCheckLooking(void); +void M_GonerResetText(void); void M_GonerGDQ(boolean opinion); boolean M_GonerMusicPlayable(void); diff --git a/src/lua_script.c b/src/lua_script.c index 1f01010cb..c7e9745f3 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -216,6 +216,9 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"podiummap")) { lua_pushstring(L, podiummap); return 1; + } else if (fastcmp(word,"tutorialplaygroundmap")) { + lua_pushstring(L, tutorialplaygroundmap); + return 1; } else if (fastcmp(word,"tutorialchallengemap")) { lua_pushstring(L, tutorialchallengemap); return 1; diff --git a/src/m_cond.c b/src/m_cond.c index 026dace10..81908d533 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -663,6 +663,7 @@ void M_ClearStats(void) gamedata->finishedtutorialchallenge = false; gamedata->sealedswapalerted = false; gamedata->tutorialdone = false; + gamedata->playgroundroute = false; gamedata->musicstate = GDMUSIC_NONE; gamedata->importprofilewins = false; @@ -1755,6 +1756,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return (gamedata->finishedtutorialchallenge == true); case UC_TUTORIALDONE: return (gamedata->tutorialdone == true); + case UC_PLAYGROUND: + return (gamedata->playgroundroute == true); case UC_PASSWORD: return (cn->stringvar == NULL); @@ -2641,6 +2644,8 @@ static const char *M_GetConditionString(condition_t *cn) return "successfully skip the Tutorial"; case UC_TUTORIALDONE: return "complete the Tutorial"; + case UC_PLAYGROUND: + return "pick the Playground"; case UC_PASSWORD: return "enter a secret password"; diff --git a/src/m_cond.h b/src/m_cond.h index 10b9e4883..f636b9515 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -68,6 +68,7 @@ typedef enum UC_CRASH, // Hee ho ! UC_TUTORIALSKIP, // Complete the Tutorial Challenge UC_TUTORIALDONE, // Complete the Tutorial at all + UC_PLAYGROUND, // Go to the playground instead..? UC_PASSWORD, // Type in something funny @@ -301,7 +302,7 @@ typedef enum { #define GDCONVERT_ROUNDSTOKEY 5 -#define GDINIT_CHAOKEYS 10 // Start with 10 Chao Keys !! +#define GDINIT_CHAOKEYS 0 // Start with ZERO Chao Keys. You get NONE. fizzy lifting dink #define GDINIT_PRISONSTOPRIZE 15 // 15 Prison Eggs to your [Wild Prize] !! typedef enum { @@ -395,6 +396,7 @@ struct gamedata_t boolean finishedtutorialchallenge; boolean sealedswapalerted; boolean tutorialdone; + boolean playgroundroute; gdmusic_t musicstate; UINT8 gonerlevel; diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 75b41d172..2e2a2f542 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -32,6 +32,7 @@ #include "../core/string.h" static void M_GonerDrawer(void); +static void M_GonerChoiceDrawer(void); static void M_GonerConclude(INT32 choice); static boolean M_GonerInputs(INT32 ch); @@ -55,9 +56,9 @@ menuitem_t MAIN_Goner[] = "ASSIGN VEHICLE INPUTS.", NULL, {.routine = M_GonerProfile}, 0, 0}, - {IT_STRING | IT_CALL, "BEGIN TUTORIAL", - "PREPARE FOR INTEGRATION.", NULL, - {.routine = M_GonerTutorial}, 0, 0}, + {IT_STRING | IT_CALL, "MAKE CHOICE", + "PREPARE FOR INTEGRATION?", NULL, + {.routine = M_GonerChoice}, 0, 0}, {IT_STRING | IT_CALL, "START GAME", "I WILL SUCCEED.", NULL, @@ -82,9 +83,57 @@ menu_t MAIN_GonerDef = { M_GonerInputs, }; +menuitem_t MAIN_GonerChoice[] = +{ + {IT_STRING | IT_CALL, "Tails' way", + "As a child scientist, Tails has recorded bits\n" + "and pieces of an adventure he and Eggman went\n" + "on while trying out their new Ring Racers.\n" + "\n" + "This is a structured, back-to-basics session\n" + "that will likely take ""\x88""10-20 minutes""\x80"" of your time.", + NULL, {.routine = M_GonerTutorial}, 0, 0}, + + //{IT_STRING, NULL, NULL, NULL, {.routine = M_QuitSRB2}, 0, 0}, // will be replaced + + {IT_STRING | IT_CALL, "Eggman's way", + "As a childlike scientist, Eggman has turned the\n" + "wrecked Egg Carrier into a giant skatepark,\n" + "dotted with fun collectables to test drivers.\n" + "\n" + "You can ""\x88""exit immediately""\x80"" and get to racing...\n" + "or spend ""\x88""as long as you want""\x80"" in the playground!", + NULL, {.routine = M_GonerPlayground}, 0, 0}, +}; + +menu_t MAIN_GonerChoiceDef = { + sizeof (MAIN_GonerChoice) / sizeof (menuitem_t), + &MAIN_GonerDef, + 0, + MAIN_GonerChoice, + 26, 160, + 0, 0, + MBF_UD_LR_FLIPPED, + "_GONER", + 0, 0, + M_GonerChoiceDrawer, + M_DrawGonerBack, + NULL, + NULL, + NULL, + NULL, +}; + namespace { +typedef enum +{ + GONERCHOICE_TAILS = 0, + //GONERCHOICE_NONEBINEY, + GONERCHOICE_EGGMAN +} gonerchoices_t; + typedef enum { GONERSPEAKER_EGGMAN = 0, @@ -429,16 +478,6 @@ void Miles_Electric_Lower() int goner_levelworking = GDGONER_INIT; bool goner_gdq = false; -void M_GonerResetText(void) -{ - goner_typewriter.ClearText(); - LinesToDigest.clear(); - LinesOutput.clear(); - - goner_scroll = 0; - goner_scrollend = -1; -} - static void Initial_Control_Info(void) { if (cv_currprofile.value != -1) @@ -669,11 +708,22 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Now, Metal... it's important you pay attention."); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, - "It's time to ""\x87""begin your Tutorial""\x80""!"); + "We have a ""\x88""choice""\x80"" ready for you."); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, - "Remember, MS-1. Even when you move on from this setup, you "\ - "can always change your ""\x87""Options""\x80"" at any time from the menu."); + "You can play back our testing data as a sort of ""\x82""tutorial""\x80"\ + " and learn the core parts of driving in a safe environment..."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + "...or if you're too headstrong and want to figure things out"\ + " for yourself, we can let you loose in our ""\x85""playground""\x80""!"); + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, + "If you do run into trouble, the ""\x82""tutorial""\x80"" can"\ + " always be found in the ""\x87""Extras""\x80"" menu later on."); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Either way, MS-1. Even when you move on from this setup,"\ + " you can always change your ""\x87""Options""\x80"" at any time."); LinesToDigest.emplace_front(0, Miles_Look_Electric); break; @@ -704,8 +754,6 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, "But yes. Perhaps now you have a better appreciation of what "\ "we're building here, Metal."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, - "If you need to learn more, you can always come back to the Tutorial later in the ""\x87""Extras""\x80"" menu."); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, "Now, I'm willing to let bygones be bygones."); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, @@ -1218,6 +1266,123 @@ static void M_GonerDrawer(void) M_DrawHorizontalMenu(); } +static void M_GonerChoiceDrawer(void) +{ + srb2::Draw drawer = srb2::Draw(); + + const INT32 lex = (24 + BASEVIDWIDTH/2)/2; + + if (itemOn == GONERCHOICE_TAILS) + { + drawer + .size((BASEVIDWIDTH/2) + 25, BASEVIDHEIGHT) + .fill(60); + + drawer + .xy((BASEVIDWIDTH/2) + 40 + 1, 28+3) + .colormap(SKINCOLOR_ORANGE) + .flags(V_FLIP) + .patch("MENUPLTR"); + + drawer + .xy(lex, 28) + .font(srb2::Draw::Font::kGamemode) + .align(srb2::Draw::Align::kCenter) + .text(currentMenu->menuitems[itemOn].text); + + drawer + .xy(8, 72) + .font(srb2::Draw::Font::kThin) + .align(srb2::Draw::Align::kLeft) + .text(currentMenu->menuitems[itemOn].tooltip); + + drawer + .xy(lex, 154) + .font(srb2::Draw::Font::kFreeplay) + .align(srb2::Draw::Align::kCenter) + .text("(unlocks 20 )"); + + drawer + .xy(lex, 154+14) + .font(srb2::Draw::Font::kThin) + .align(srb2::Draw::Align::kCenter) + .flags(V_TRANSLUCENT) + .text("+ more surprises to find"); + + drawer + .xy(lex + 26, 154-4) + .patch("UN_CHA00"); + } + else if (itemOn == GONERCHOICE_EGGMAN) + { + drawer + .x((BASEVIDWIDTH/2) - 24) + .size((BASEVIDWIDTH/2) + 24, BASEVIDHEIGHT) + .fill(44); + + drawer + .xy((BASEVIDWIDTH/2) - 40, 28+3) + .colormap(SKINCOLOR_RED) + .patch("MENUPLTR"); + + drawer + .xy(BASEVIDWIDTH - lex, 28) + .font(srb2::Draw::Font::kGamemode) + .align(srb2::Draw::Align::kCenter) + .text(currentMenu->menuitems[itemOn].text); + + drawer + .xy(BASEVIDWIDTH - 8, 72) + .font(srb2::Draw::Font::kThin) + .align(srb2::Draw::Align::kRight) + .text(currentMenu->menuitems[itemOn].tooltip); + + drawer + .xy(BASEVIDWIDTH - lex, 154) + .font(srb2::Draw::Font::kFreeplay) + .align(srb2::Draw::Align::kCenter) + .text("(unlocks Addons/Online)"); + } + + // Un-highlighteds done this weird way because of GONERCHOICE_NONEBINEY + + if (itemOn != GONERCHOICE_TAILS) + { + drawer + .size(20, BASEVIDHEIGHT) + .fill(60); + + drawer + .xy(25, 39) + .font(srb2::Draw::Font::kFreeplay) + .align(srb2::Draw::Align::kLeft) + .text(currentMenu->menuitems[GONERCHOICE_TAILS].text); + + drawer + .xy(20 - 3 - (skullAnimCounter/5), 39+6) + .patch("CUPARROW"); + } + + if (itemOn != GONERCHOICE_EGGMAN) + { + drawer + .x(BASEVIDWIDTH - 20) + .size(20, BASEVIDHEIGHT) + .fill(44); + + drawer + .xy(BASEVIDWIDTH - 25, 39) + .font(srb2::Draw::Font::kFreeplay) + .align(srb2::Draw::Align::kRight) + .text(currentMenu->menuitems[GONERCHOICE_EGGMAN].text); + + drawer + .xy((BASEVIDWIDTH - 20 + 3) + (skullAnimCounter/5), 39+6) + .flags(V_FLIP) + .patch("CUPARROW"); + } +} + // --- void M_GonerProfile(INT32 choice) @@ -1245,19 +1410,16 @@ void M_GonerProfile(INT32 choice) M_GonerResetLooking(GDGONER_PROFILE); } -static void M_GonerSurveyResponse(INT32 ch) +static void M_GonerTutorialResponse(INT32 ch) { if (ch != MA_YES) return; - if (gamedata->gonerlevel < GDGONER_OUTRO) - gamedata->gonerlevel = GDGONER_OUTRO; + M_GonerTutorial(0); } -void M_GonerTutorial(INT32 choice) +void M_GonerChoice(INT32 choice) { - (void)choice; - if (cv_currprofile.value == -1) { const INT32 maxp = PR_GetNumProfiles(); @@ -1270,6 +1432,43 @@ void M_GonerTutorial(INT32 choice) PR_ApplyProfile(profilen, 0); } + if (gamedata->gonerlevel >= GDGONER_OUTRO) + { + M_StartMessage("First Boot Tutorial", + "You've already played the Tutorial! Do you want to see it again?", + &M_GonerTutorialResponse, MM_YESNO, "I'd love to", "Not right now"); + return; + } + + M_SetupNextMenu(&MAIN_GonerChoiceDef, false); +} + +static void M_GonerSurveyResponse(INT32 ch) +{ + if (ch != MA_YES) + return; + + if (gamedata->gonerlevel < GDGONER_OUTRO) + gamedata->gonerlevel = GDGONER_OUTRO; + + if (currentMenu == &MAIN_GonerChoiceDef) + M_GoBack(0); +} + +static void M_GonerSurvey(INT32 choice) +{ + (void)choice; + + // The game is incapable of progression, but I can't bring myself to put an I_Error here. + M_StartMessage("First Boot Error", + "YOU ACCEPT EVERYTHING THAT\nWILL HAPPEN FROM NOW ON.", + &M_GonerSurveyResponse, MM_YESNO, "I agree", "Cancel"); +} + +void M_GonerTutorial(INT32 choice) +{ + (void)choice; + // Please also see M_LevelSelectInit as called in extras-1.c levellist.netgame = false; levellist.canqueue = false; @@ -1279,19 +1478,58 @@ void M_GonerTutorial(INT32 choice) if (!M_LevelListFromGametype(GT_TUTORIAL) && gamedata->gonerlevel < GDGONER_OUTRO) { - // The game is incapable of progression, but I can't bring myself to put an I_Error here. - M_StartMessage("Agreement", - "YOU ACCEPT EVERYTHING THAT WILL HAPPEN FROM NOW ON.", - &M_GonerSurveyResponse, MM_YESNO, "I agree", "Cancel"); + M_GonerSurvey(0); + return; } } +void M_GonerPlayground(INT32 choice) +{ + (void)choice; + + UINT16 playgroundmap = NEXTMAP_INVALID; + if (tutorialplaygroundmap) + playgroundmap = G_MapNumber(tutorialplaygroundmap); + + if (playgroundmap >= nummapheaders) + { + M_GonerSurvey(0); + return; + } + + multiplayer = true; + + M_MenuToLevelPreamble(0, false); + + D_MapChange( + playgroundmap+1, + GT_TUTORIAL, + false, + true, + 0, + false, + false + ); + + M_ClearMenus(true); + restoreMenu = NULL; // Playground Hack + + // need to do all this here because it will skip returning to goner and there are circumstances (game close) where DoCompleted won't be called + M_GonerResetText(); + gamedata->gonerlevel = GDGONER_DONE; + gamedata->playgroundroute = true; + gamedata->deferredsave = true; +} + static void M_GonerConclude(INT32 choice) { (void)choice; gamedata->gonerlevel = GDGONER_DONE; + if (gamedata->chaokeys < 20) + gamedata->chaokeys = 20; + F_StartIntro(); M_ClearMenus(true); M_GonerResetText(); @@ -1380,3 +1618,13 @@ static boolean M_GonerInputs(INT32 ch) return false; } + +void M_GonerResetText(void) +{ + goner_typewriter.ClearText(); + LinesToDigest.clear(); + LinesOutput.clear(); + + goner_scroll = 0; + goner_scrollend = -1; +} From 757c40844bfd2ab12cc782c780feb9bed565ce9b Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Jul 2025 19:27:04 +0100 Subject: [PATCH 02/17] Add "_Profile" relevant skin option Uses profile skin/color for Tutorial courses --- src/p_setup.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 9111107ec..a51c33e2b 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -8046,7 +8046,7 @@ static void P_ShuffleTeams(void) static void P_InitPlayers(void) { - INT32 i, skin = -1, follower = -1; + INT32 i, skin = -1, follower = -1, col = -1; // Make sure objectplace is OFF when you first start the level! OP_ResetObjectplace(); @@ -8060,7 +8060,19 @@ static void P_InitPlayers(void) // Get skin from name. if (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->relevantskin[0]) { - skin = R_SkinAvailable(mapheaderinfo[gamemap-1]->relevantskin); + if (strcmp(mapheaderinfo[gamemap-1]->relevantskin, "_PROFILE") == 0) + { + profile_t *p = PR_GetProfile(cv_ttlprofilen.value); + if (p) + { + skin = R_SkinAvailable(p->skinname); + col = p->color; + } + } + else + { + skin = R_SkinAvailable(mapheaderinfo[gamemap-1]->relevantskin); + } } else { @@ -8097,7 +8109,7 @@ static void P_InitPlayers(void) if (skin != -1) { SetPlayerSkinByNum(i, skin); - players[i].skincolor = skins[skin].prefcolor; + players[i].skincolor = (col >= 0 && col < numskincolors) ? col : skins[skin].prefcolor; players[i].followerskin = follower; if (follower != -1) From 6ce6c0d6cf05a8d83667c7da3229dabcda7a952e Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Jul 2025 19:32:07 +0100 Subject: [PATCH 03/17] Two minor hacks for Playground that I can't be bothered to do properly, to match desired spec - Play intro when exiting goner Playground - Do not allow using menu to skip driving down that hill in goner Playground The reason these two are hacks is because a map is considered goner Playground if you use -warp at command line or map in console on title screen to get to a Tutorial course... but these are unimportant edge cases IMO --- src/g_game.c | 7 +++++++ src/menus/transient/pause-game.c | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index be9af9549..ab7df4954 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5130,6 +5130,13 @@ void G_EndGame(void) return; } + if (gametype == GT_TUTORIAL && M_GameTrulyStarted() && restoreMenu == NULL) + { + // Playground Hack + F_StartIntro(); + return; + } + // Time to return to the menu. Command_ExitGame_f(); } diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 00884cb4f..8ed0078ed 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -561,7 +561,8 @@ void M_EndGame(INT32 choice) if (!Playing()) return; - if (M_GameTrulyStarted() == false) + if (M_GameTrulyStarted() == false + || (gametype == GT_TUTORIAL && restoreMenu == NULL)) // Playground Hack { // No returning to the title screen. M_QuitSRB2(-1); From 638eadee2e87e7a4cbf541b76b86a84ca52664ec Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Jul 2025 19:32:33 +0100 Subject: [PATCH 04/17] "Proceed" is more source-accurate - resets game rather than skips tutorial --- src/m_pw.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/m_pw.cpp b/src/m_pw.cpp index 0808ff28c..e08d8ff74 100644 --- a/src/m_pw.cpp +++ b/src/m_pw.cpp @@ -28,6 +28,7 @@ #include "doomdef.h" #include "doomstat.h" #include "doomtype.h" +#include "f_finale.h" #include "g_game.h" #include "k_menu.h" #include "m_cheat.h" @@ -613,6 +614,7 @@ void f_devmode() void f_proceed() { +#if 0 gamedata->gonerlevel = GDGONER_DONE; gamedata->finishedtutorialchallenge = true; M_UpdateUnlockablesAndExtraEmblems(true, true); @@ -621,6 +623,11 @@ void f_proceed() S_StartSound(0, sfx_kc42); G_SaveGameData(); +#else + F_StartIntro(); + M_ClearMenus(true); + M_GonerResetText(); +#endif } }; // namespace From c5ab5ec71e58fdbd5a544f95f4f5111fee6f2837 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Jul 2025 19:34:42 +0100 Subject: [PATCH 05/17] Fix minor edge case with error message for Eggman's Way --- src/menus/main-goner.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 2e2a2f542..9bcee6b3b 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -1452,7 +1452,20 @@ static void M_GonerSurveyResponse(INT32 ch) gamedata->gonerlevel = GDGONER_OUTRO; if (currentMenu == &MAIN_GonerChoiceDef) + { + if (itemOn == GONERCHOICE_EGGMAN) + { + gamedata->playgroundroute = true; + gamedata->gonerlevel = GDGONER_DONE; + + F_StartIntro(); + M_ClearMenus(true); + M_GonerResetText(); + return; + } + M_GoBack(0); + } } static void M_GonerSurvey(INT32 choice) From db0dfe7be159f7dafd5f924a82daad96777b2ef9 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Jul 2025 19:35:38 +0100 Subject: [PATCH 06/17] Update year count in KKD screen from 11y to 12y --- src/f_finale.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/f_finale.c b/src/f_finale.c index fcb5821a8..411ddc621 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -504,7 +504,7 @@ static void F_IntroDrawScene(void) } // Joyeaux Anniversaire - V_DrawCenteredMenuString(BASEVIDWIDTH/2, 174 - (textoffs/FRACUNIT), (trans< Date: Wed, 23 Jul 2025 20:53:23 -0400 Subject: [PATCH 07/17] Bailcharge at full rate when airborne or in pain --- src/k_kart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index ecd1b7c6d..cd3b06da9 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -14125,7 +14125,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { // Set up bail charge, provided we have something to bail with (any rings or item resource). boolean grounded = P_IsObjectOnGround(player->mo); - onground && player->tumbleBounces == 0 ? player->bailcharge += 2 : player->bailcharge++; // charge twice as fast on the ground + // onground && player->tumbleBounces == 0 ? player->bailcharge += 2 : player->bailcharge++; // charge twice as fast on the ground + player->bailcharge += 2; if ((P_PlayerInPain(player) && player->bailcharge == 1) || (grounded && P_PlayerInPain(player) && player->bailcharge == 2)) // this is brittle .. { mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAILCHARGE); From 03bc701f7466055b62e2ee8dc6b52a0b61799430 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Thu, 24 Jul 2025 00:06:14 -0400 Subject: [PATCH 08/17] Fix animation --- src/k_kart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index cd3b06da9..d3d36125f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -14127,7 +14127,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) boolean grounded = P_IsObjectOnGround(player->mo); // onground && player->tumbleBounces == 0 ? player->bailcharge += 2 : player->bailcharge++; // charge twice as fast on the ground player->bailcharge += 2; - if ((P_PlayerInPain(player) && player->bailcharge == 1) || (grounded && P_PlayerInPain(player) && player->bailcharge == 2)) // this is brittle .. + // if ((P_PlayerInPain(player) && player->bailcharge == 1) || (grounded && P_PlayerInPain(player) && player->bailcharge == 2)) // this is brittle .. + if (player->bailcharge == 2) { mobj_t *bail = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_BAILCHARGE); S_StartSound(bail, sfx_gshb9); // I tried to use info.c, but you can't play sounds on mobjspawn via A_PlaySound From 1a57416b16396d3bc3d0c223252c2d3aaa328878 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 12:48:53 +0100 Subject: [PATCH 09/17] Minor adjustments for Goner Choice - "session" -> "tutorial" - make it clear that Addons and Online *will* eventually be unlocked in Tails' Way, just not immediately - Don't allow looping inputs (pressing right when Eggman's Way is selected and vicea versa) --- src/menus/main-goner.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 9bcee6b3b..97b4ad60d 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -90,7 +90,7 @@ menuitem_t MAIN_GonerChoice[] = "and pieces of an adventure he and Eggman went\n" "on while trying out their new Ring Racers.\n" "\n" - "This is a structured, back-to-basics session\n" + "This is a structured, back-to-basics tutorial\n" "that will likely take ""\x88""10-20 minutes""\x80"" of your time.", NULL, {.routine = M_GonerTutorial}, 0, 0}, @@ -113,7 +113,7 @@ menu_t MAIN_GonerChoiceDef = { MAIN_GonerChoice, 26, 160, 0, 0, - MBF_UD_LR_FLIPPED, + MBF_UD_LR_FLIPPED|MBF_NOLOOPENTRIES, "_GONER", 0, 0, M_GonerChoiceDrawer, @@ -1342,6 +1342,13 @@ static void M_GonerChoiceDrawer(void) .font(srb2::Draw::Font::kFreeplay) .align(srb2::Draw::Align::kCenter) .text("(unlocks Addons/Online)"); + + drawer + .xy(BASEVIDWIDTH - lex, 154+14) + .font(srb2::Draw::Font::kThin) + .align(srb2::Draw::Align::kCenter) + .flags(V_TRANSLUCENT) + .text("the other way has these too, just later"); } // Un-highlighteds done this weird way because of GONERCHOICE_NONEBINEY From 585683b9788d349a7afe1b4aff6dcf9c47d943cb Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 13:12:23 +0100 Subject: [PATCH 10/17] Fix Proceed password "fake crash" Didn't always properly reset text, now it does --- src/k_menu.h | 2 +- src/m_pw.cpp | 2 +- src/menus/main-goner.cpp | 20 ++++++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index b612151da..887db519f 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -234,7 +234,7 @@ void M_GonerTutorial(INT32 choice); void M_GonerPlayground(INT32 choice); void M_GonerResetLooking(int type); void M_GonerCheckLooking(void); -void M_GonerResetText(void); +void M_GonerResetText(boolean completely); void M_GonerGDQ(boolean opinion); boolean M_GonerMusicPlayable(void); diff --git a/src/m_pw.cpp b/src/m_pw.cpp index e08d8ff74..eb8b7706a 100644 --- a/src/m_pw.cpp +++ b/src/m_pw.cpp @@ -626,7 +626,7 @@ void f_proceed() #else F_StartIntro(); M_ClearMenus(true); - M_GonerResetText(); + M_GonerResetText(true); #endif } diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 97b4ad60d..9564529ba 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -887,16 +887,15 @@ void M_GonerTick(void) first = true; // a lie, but only slightly... // Handle rewinding if you clear your gamedata. - M_GonerResetText(); - goner_background = GonerBGData(); - - goner_levelworking = GDGONER_INIT; + M_GonerResetText(true); } M_GonerResetLooking(GDGONER_INIT); if (first) { + goner_background = GonerBGData(); + first = goner_gdq = false; MAIN_Goner[0] = @@ -1467,7 +1466,7 @@ static void M_GonerSurveyResponse(INT32 ch) F_StartIntro(); M_ClearMenus(true); - M_GonerResetText(); + M_GonerResetText(false); return; } @@ -1535,10 +1534,10 @@ void M_GonerPlayground(INT32 choice) restoreMenu = NULL; // Playground Hack // need to do all this here because it will skip returning to goner and there are circumstances (game close) where DoCompleted won't be called - M_GonerResetText(); gamedata->gonerlevel = GDGONER_DONE; gamedata->playgroundroute = true; gamedata->deferredsave = true; + M_GonerResetText(true); } static void M_GonerConclude(INT32 choice) @@ -1552,7 +1551,7 @@ static void M_GonerConclude(INT32 choice) F_StartIntro(); M_ClearMenus(true); - M_GonerResetText(); + M_GonerResetText(true); } void M_GonerGDQ(boolean opinion) @@ -1639,7 +1638,7 @@ static boolean M_GonerInputs(INT32 ch) return false; } -void M_GonerResetText(void) +void M_GonerResetText(boolean completely) { goner_typewriter.ClearText(); LinesToDigest.clear(); @@ -1647,4 +1646,9 @@ void M_GonerResetText(void) goner_scroll = 0; goner_scrollend = -1; + + if (!completely) + return; + + goner_levelworking = GDGONER_INIT; } From f0fbd07804e132b1fa73d678bca3d7f3039c50c5 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 13:28:59 +0100 Subject: [PATCH 11/17] Got better at working in CPP... we don't want a forward_list here, we want a deque! --- src/menus/main-goner.cpp | 210 ++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 111 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 9564529ba..148fad752 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -27,7 +27,7 @@ #include "../m_pw.h" #include "../z_zone.h" -#include +#include #include "../core/string.h" @@ -249,8 +249,8 @@ public: }; }; -std::forward_list LinesToDigest; -std::forward_list LinesOutput; +std::deque LinesToDigest; +std::deque LinesOutput; class GonerBGData { @@ -492,24 +492,13 @@ static void Initial_Control_Info(void) ) ); - if (LinesToDigest.empty()) - { - LinesToDigest.emplace_front(line); - return; - } - - LinesToDigest.emplace_after( - LinesToDigest.begin(), - line - ); + LinesToDigest.push_back(line); } void M_AddGonerLines(void) { SRB2_ASSERT(LinesToDigest.empty()); - auto _ = srb2::finally([]() { LinesToDigest.reverse(); }); - static bool leftoff = false; goner_delay = TICRATE; @@ -519,7 +508,7 @@ void M_AddGonerLines(void) { if (!MAIN_Goner[0].mvar2) { - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Metal Sonic. Are you online?"); } @@ -527,10 +516,10 @@ void M_AddGonerLines(void) if (leftoff) { - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "It must have run into some sort of error..."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Don't worry, your settings so far are saved. "\ "Let's pick up where we left off."); @@ -545,40 +534,40 @@ void M_AddGonerLines(void) { case GDGONER_VIDEO: { - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2, "Take a close look, Miles. Moments ago he was at my throat! "\ "Now he's docile as can be on that operating table."); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "I don't feel very safe!"); - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/4, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/4, "But its programming is definitely locked down..."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(0, Miles_Look_Electric); + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "You've given me quite the headache, Metal. "\ "Thankfully, Tails caught you in the act."); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/5, + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/5, "Wait, I'm getting weird readings over the network."); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "Metal Sonic is the unit labeled \"MS-1\", right?"); - LinesToDigest.emplace_front(0, Miles_Look_Electric); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, + LinesToDigest.emplace_back(0, Miles_Look_Electric); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE, "The ""\x87""viewport""\x80"" and ""\x87""audio""\x80"" "\ "config looks like it got messed up."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "So you're right. I wonder if it has anything to do with that outburst."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Alright, Metal! I don't remember your specifications offhand. "\ "First things first, go ahead and set up your "\ "\x87""Video Options""\x80"" yourself."); - LinesToDigest.emplace_front(0, Initial_Control_Info); + LinesToDigest.emplace_back(0, Initial_Control_Info); break; } @@ -586,101 +575,101 @@ void M_AddGonerLines(void) { if (!leftoff) { - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Ah, you can see us now. Good."); } - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Now, calibrate your ""\x87""Sound Options""\x80""."); - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "You always make your stuff so loud by default, Eggman. It might need a moment."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Not Metal! He always needed to be stealthy. But go on, set your sliders."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); + LinesToDigest.emplace_back(0, Miles_Look_Electric); break; } case GDGONER_PROFILE: { if (!leftoff) { - LinesToDigest.emplace_front(0, Miles_Look_Electric); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(0, Miles_Look_Electric); + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "Oh! Let's tell Metal about our project!"); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Of course. I and my lab assista-"); - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "Lab PARTNER."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(0, Miles_Look_Electric); + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Irrelevant!"); } - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/4, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/4, "We made a machine together, Tails and I. "\ "It's called a \"""\x82""Ring Racer""\x80""\"."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE, "At its core, it is designed to utilise the boundless potential "\ "of the ""\x83""High Voltage Ring""\x80""."); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE, "We made this special ""\x83""Ring""\x80"" by combining the power of tens of "\ "thousands of ordinary ""\x82""Rings""\x80""."); - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "We recorded some of our testing for you, MS-1. Maybe your neural "\ "network could train on some less violent data for once."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/4, + LinesToDigest.emplace_back(0, Miles_Look_Electric); + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/4, "While that's uploading, why don't you set up your ""\x87""Profile Card""\x80""?"); - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "Yes! That's one of my contributions."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "(I'm too used to my systems being designed for me alone...)"); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "Every racer carries one, to contain their personal settings."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Look_Electric); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "It helps get your ""\x87""controls""\x80"" set up nice and quickly, "\ "when starting your vehicle and navigating the menu."); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "And it helps track your wins, too."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); + LinesToDigest.emplace_back(0, Miles_Look_Electric); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5, "Bragging rights. My idea!"); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "You can make the ID and player tag on there anything you want."); - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "Mine says \"Nine Tails\". That's the name of my original character! "\ "He's like me if I never met my ""\x84""brother""\x80"". He'd use cool "\ "robotics, but be kind of mean to protect himself..."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5, "Mine says \"Robotnik\". You can't beat a classic."); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "And I'm not sure if you'll need it, but we always tell new drivers to "\ "look at the ""\x87""Accessibility""\x80"" settings. Often there's some "\ "feature they're not expecting. Maybe you'd be surprised too?"); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "So go on, do your ""\x87""Profile Setup""\x80""!"); break; @@ -689,42 +678,42 @@ void M_AddGonerLines(void) { if (!leftoff) { - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2, "Now that that's been set up, you can use your ""\x87""Profile controls""\x80"" on menus from here on out, too."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + LinesToDigest.emplace_back(0, Miles_Look_Electric); + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5, "Miles. How's the upload going?"); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "Just finished."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Perfect."); } - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "Now, Metal... it's important you pay attention."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5, "We have a ""\x88""choice""\x80"" ready for you."); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "You can play back our testing data as a sort of ""\x82""tutorial""\x80"\ " and learn the core parts of driving in a safe environment..."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5, "...or if you're too headstrong and want to figure things out"\ " for yourself, we can let you loose in our ""\x85""playground""\x80""!"); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2, "If you do run into trouble, the ""\x82""tutorial""\x80"" can"\ " always be found in the ""\x87""Extras""\x80"" menu later on."); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "Either way, MS-1. Even when you move on from this setup,"\ " you can always change your ""\x87""Options""\x80"" at any time."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); + LinesToDigest.emplace_back(0, Miles_Look_Electric); break; } @@ -732,35 +721,35 @@ void M_AddGonerLines(void) { if (!leftoff) { - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/3, "And... the training data is completed."); } - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "It's kind of funny, actually."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/3, "Oh? Care to elucidate, Prower?"); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "No matter how much time we took getting here, a machine like "\ "Metal can play it back in minutes."); - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "It could have been five days or five years of development on "\ "our ""\x82""Ring Racers""\x80"", and that would barely matter to it."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/4, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/4, "Ha! As if. I'd like to think our partnership hasn't felt "\ "particularly protracted."); - LinesToDigest.emplace_front(0, Miles_Look_Electric); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, + LinesToDigest.emplace_back(0, Miles_Look_Electric); + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2, "But yes. Perhaps now you have a better appreciation of what "\ "we're building here, Metal."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/5, "Now, I'm willing to let bygones be bygones."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2, "As long as you keep your violence to the track, I'll be "\ "giving you your autonomy back in a moment."); - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "We've kept the keys from you long enough!"); break; } @@ -768,7 +757,7 @@ void M_AddGonerLines(void) break; default: - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "I am error"); } @@ -1568,24 +1557,23 @@ void M_GonerGDQ(boolean opinion) if (opinion) // Save The Animals { - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, TICRATE/2, "Why wouldn't you save the frames..?"); - LinesToDigest.emplace_front(0, Miles_Look_Camera); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + LinesToDigest.emplace_back(0, Miles_Look_Camera); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, 0, "Don't mind him. Good luck on the run!"); - LinesToDigest.emplace_front(0, Miles_Look_Electric); + LinesToDigest.emplace_back(0, Miles_Look_Electric); } else // Save The Frames { - LinesToDigest.emplace_front(0, Miles_Electric_Lower); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + LinesToDigest.emplace_back(0, Miles_Electric_Lower); + LinesToDigest.emplace_back(GONERSPEAKER_TAILS, TICRATE/2, "But what about all the little animals..."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + LinesToDigest.emplace_back(GONERSPEAKER_EGGMAN, 0, "It's just logical. I know you'll conquer this run."); } - LinesToDigest.reverse(); if (gamedata->gonerlevel <= GDGONER_TUTORIAL) { From 340b1914d268725252d0d71314baeb5dd5760878 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 18:22:25 +0100 Subject: [PATCH 12/17] Add MBF_CANTRESTORE Replaces hardcoded exception to restoreMenu --- src/k_menu.h | 1 + src/k_menufunc.c | 2 +- src/menus/main-goner.cpp | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 887db519f..50bba91e5 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -140,6 +140,7 @@ typedef enum MBF_SOUNDLESS = 1<<1, // do not play base menu sounds MBF_NOLOOPENTRIES = 1<<2, // do not loop M_NextOpt/M_PrevOpt MBF_DRAWBGWHILEPLAYING = 1<<3, // run backroutine() outside of GS_MENU + MBF_CANTRESTORE = 1<<4, // Do not use in restoreMenu } menubehaviourflags_t; struct menuitem_t diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 64da0fecd..78842740f 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -662,7 +662,7 @@ boolean M_ConsiderSealedSwapAlert(void) void M_ValidateRestoreMenu(void) { - if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef) + if (restoreMenu == NULL || (restoreMenu->behaviourflags & MBF_CANTRESTORE)) restoreMenu = &MainDef; } diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 148fad752..a0e9675e3 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -72,7 +72,7 @@ menu_t MAIN_GonerDef = { MAIN_Goner, 26, 160, 0, sizeof (MAIN_Goner) / sizeof (menuitem_t), // extra2 is final width - MBF_UD_LR_FLIPPED, + MBF_CANTRESTORE|MBF_UD_LR_FLIPPED, "_GONER", 0, 0, M_GonerDrawer, @@ -83,7 +83,7 @@ menu_t MAIN_GonerDef = { M_GonerInputs, }; -menuitem_t MAIN_GonerChoice[] = +static menuitem_t MAIN_GonerChoice[] = { {IT_STRING | IT_CALL, "Tails' way", "As a child scientist, Tails has recorded bits\n" @@ -106,14 +106,14 @@ menuitem_t MAIN_GonerChoice[] = NULL, {.routine = M_GonerPlayground}, 0, 0}, }; -menu_t MAIN_GonerChoiceDef = { +static menu_t MAIN_GonerChoiceDef = { sizeof (MAIN_GonerChoice) / sizeof (menuitem_t), &MAIN_GonerDef, 0, MAIN_GonerChoice, 26, 160, 0, 0, - MBF_UD_LR_FLIPPED|MBF_NOLOOPENTRIES, + MBF_CANTRESTORE|MBF_UD_LR_FLIPPED|MBF_NOLOOPENTRIES, "_GONER", 0, 0, M_GonerChoiceDrawer, From 8a7df9678e2c20928204907d08426de0333008d5 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 19:28:27 +0100 Subject: [PATCH 13/17] Add photosensitivity warning to Goner Allows you to turn on reducevfx, screenshake, and screen tilting with one confirm input Shows up every time game boots just in case you get into Sunbeam and it's too much and need to restart (also dummied out Kickstart Accel option) --- src/k_menu.h | 2 +- src/k_menufunc.c | 2 +- src/menus/main-goner.cpp | 96 +++++++++++++++++++++++++++++++++- src/menus/options-profiles-1.c | 6 +++ 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 50bba91e5..3b1151aae 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -222,7 +222,7 @@ typedef enum quitkart } main_e; -extern menuitem_t MAIN_Goner[]; +extern menu_t MAIN_GonerAccessibilityDef; extern menu_t MAIN_GonerDef; void M_GonerTick(void); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 78842740f..be6dcc2a3 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -847,7 +847,7 @@ void M_StartControlPanel(void) // Are you ready for the First Boot Experience? M_ResetOptions(); - currentMenu = &MAIN_GonerDef; + currentMenu = &MAIN_GonerAccessibilityDef; restoreMenu = NULL; M_PlayMenuJam(); diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index a0e9675e3..16df4fe2e 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -36,9 +36,99 @@ static void M_GonerChoiceDrawer(void); static void M_GonerConclude(INT32 choice); static boolean M_GonerInputs(INT32 ch); -menuitem_t MAIN_Goner[] = +static menuitem_t MAIN_GonerAccessibility[] = { - {IT_STRING | IT_CALL, NULL, NULL, NULL, {.routine = M_QuitSRB2}, 0, 0}, // will be replaced + {IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, +}; + +static UINT32 goneraccessibilitytick = 0; + +//#define HANDSTRAIN + +#ifdef HANDSTRAIN +static void M_GonerHandStrain(INT32 ch) +{ + if (ch != MA_YES) + return; + + CV_StealthSet(&cv_kickstartaccel[0], "On"); +} +#endif + +static void M_GonerPhotosensitivity(INT32 ch) +{ + if (ch == MA_YES) + { + CV_StealthSet(&cv_reducevfx, "Yes"); + CV_StealthSet(&cv_screenshake, "Off"); + CV_StealthSet(&cv_tilting, "Off"); + } + +#ifdef HANDSTRAIN + M_StartMessage("Hand strain warning", + "You may be required to press many buttons\n" + "at once in order to control your Ring Racer.\n" + "\n" + "There is an option for your Accel input\n" + "to \"lock\" on after being held for 1 second.\n" + "Would you like to turn it on?\n" + , &M_GonerHandStrain, MM_YESNO, "Yes, I want Accel to \"lock\"", "No thanks"); +#endif +} + +static void M_GonerAccessibilityTick(void) +{ + if (goneraccessibilitytick) + { + if (!menumessage.active && !menutransition.dest) + { + M_SetupNextMenu(&MAIN_GonerDef, true); + } + + return; + } + + goneraccessibilitytick++; + + M_StartMessage("Photosensitivity warning", + "This game has ""\x87""flashing lights and high-contrast\n" + "patterns.""\x80"" Listen to your body, and\n" + "stop playing if you feel unwell.\n" + "\n" + "There is a ""\x88""special mode""\x80"" to reduce some\n" + "visual effects. Would you like to turn it on?\n" + , &M_GonerPhotosensitivity, MM_YESNO, "Yes, reduce effects", "No thanks"); + return; +} + +static void M_GonerAccessibilityDrawer(void) +{ + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); +} + +menu_t MAIN_GonerAccessibilityDef = { + sizeof (MAIN_GonerAccessibility) / sizeof (menuitem_t), + NULL, + 0, + MAIN_GonerAccessibility, + 26, 160, + 0, 0, + MBF_CANTRESTORE, + "_GONER", + 0, 0, + M_GonerAccessibilityDrawer, + NULL, + M_GonerAccessibilityTick, + NULL, + NULL, + NULL, +}; + +static menuitem_t MAIN_Goner[] = +{ + {IT_STRING | IT_CVAR | IT_CV_STRING, "PASSWORD", + "ATTEMPT ADMINISTRATOR ACCESS.", NULL, + {.cvar = &cv_dummyextraspassword}, 0, 0}, {IT_STRING | IT_CALL, "EXIT PROGRAM", "CONCLUDE OBSERVATIONS NOW.", NULL, @@ -887,10 +977,12 @@ void M_GonerTick(void) first = goner_gdq = false; +#if 0 MAIN_Goner[0] = {IT_STRING | IT_CVAR | IT_CV_STRING, "PASSWORD", "ATTEMPT ADMINISTRATOR ACCESS.", NULL, {.cvar = &cv_dummyextraspassword}, 0, 0}; +#endif if (gamedata->gonerlevel < GDGONER_INTRO) gamedata->gonerlevel = GDGONER_INTRO; diff --git a/src/menus/options-profiles-1.c b/src/menus/options-profiles-1.c index 8f61f01cd..85e1fd73d 100644 --- a/src/menus/options-profiles-1.c +++ b/src/menus/options-profiles-1.c @@ -83,6 +83,12 @@ void M_StartEditProfile(INT32 c) PR_InitNewProfile(); // initialize the new profile. optionsmenu.profile = PR_GetProfile(optionsmenu.profilen); + if (cv_kickstartaccel[0].value) + { + // Primarily for Goner but should help with standard set-up too + optionsmenu.profile->kickstartaccel = true; + } + // copy this profile's controls into optionsmenu so that we can edit controls without changing them directly. // we do this so that we don't edit a profile's controls in real-time and end up doing really weird shit. memcpy(&optionsmenu.tempcontrols, optionsmenu.profile->controls, sizeof(gamecontroldefault)); From 998823db32c82b987b475998a6c7d179f1196737 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 19:29:51 +0100 Subject: [PATCH 14/17] Fix "_Profile" relevantskin option - Gets closest statted character if yours is not yet unlocked - Accounts for default skin color (previously was mildly broken in this case) --- src/p_setup.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index a51c33e2b..a200652d2 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -120,6 +120,7 @@ #include "k_credits.h" #include "k_objects.h" #include "p_deepcopy.h" +#include "k_color.h" // K_ColorUsable // Replay names have time #if !defined (UNDER_CE) @@ -8046,7 +8047,7 @@ static void P_ShuffleTeams(void) static void P_InitPlayers(void) { - INT32 i, skin = -1, follower = -1, col = -1; + INT32 i, skin = -1, follower = -1, col = SKINCOLOR_NONE; // Make sure objectplace is OFF when you first start the level! OP_ResetObjectplace(); @@ -8063,10 +8064,19 @@ static void P_InitPlayers(void) if (strcmp(mapheaderinfo[gamemap-1]->relevantskin, "_PROFILE") == 0) { profile_t *p = PR_GetProfile(cv_ttlprofilen.value); - if (p) + if (p && !netgame) { skin = R_SkinAvailable(p->skinname); - col = p->color; + + if (!R_SkinUsable(g_localplayers[0], skin, false)) + { + skin = GetSkinNumClosestToStats(skins[skin].kartspeed, skins[skin].kartweight, skins[skin].flags, false); + } + + if (K_ColorUsable(static_cast(p->color), false, true) == true) + { + col = p->color; + } } } else @@ -8109,7 +8119,7 @@ static void P_InitPlayers(void) if (skin != -1) { SetPlayerSkinByNum(i, skin); - players[i].skincolor = (col >= 0 && col < numskincolors) ? col : skins[skin].prefcolor; + players[i].skincolor = (col != SKINCOLOR_NONE) ? col : skins[skin].prefcolor; players[i].followerskin = follower; if (follower != -1) From 43fef1a59bb9e24b77f59c4de9e3f377074cc8a8 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 19:36:24 +0100 Subject: [PATCH 15/17] Replace the Playground Hack with a specific fix via a case that needed its own function --- src/g_game.c | 2 +- src/k_menufunc.c | 13 +++---------- src/m_cond.c | 21 +++++++++++++++++++++ src/m_cond.h | 1 + src/menus/main-1.c | 2 +- src/menus/transient/pause-game.c | 2 +- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index ab7df4954..71e8918fa 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5130,7 +5130,7 @@ void G_EndGame(void) return; } - if (gametype == GT_TUTORIAL && M_GameTrulyStarted() && restoreMenu == NULL) + if (gametype == GT_TUTORIAL && M_GameAboutToStart() && restoreMenu == NULL) { // Playground Hack F_StartIntro(); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index be6dcc2a3..9c0ff5039 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -829,17 +829,10 @@ void M_StartControlPanel(void) if (gamedata != NULL && gamedata->gonerlevel < GDGONER_OUTRO - && gamestartchallenge < MAXUNLOCKABLES) + && M_GameAboutToStart()) { - // See M_GameTrulyStarted - if ( - gamedata->unlockpending[gamestartchallenge] - || gamedata->unlocked[gamestartchallenge] - ) - { - gamedata->gonerlevel = GDGONER_OUTRO; - M_GonerBGImplyPassageOfTime(); - } + gamedata->gonerlevel = GDGONER_OUTRO; + M_GonerBGImplyPassageOfTime(); } if (M_GameTrulyStarted() == false) diff --git a/src/m_cond.c b/src/m_cond.c index 81908d533..f113cda42 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -3475,6 +3475,27 @@ boolean M_GameTrulyStarted(void) return (gamedata->gonerlevel == GDGONER_DONE); } +boolean M_GameAboutToStart(void) +{ + // Fail safe + if (gamedata == NULL) + return false; + + // Not set + if (gamestartchallenge >= MAXUNLOCKABLES) + return true; + + // An unfortunate sidestep, but sync is important. + if (netgame) + return true; + + // Pending unlocked, but not unlocked + return ( + gamedata->unlockpending[gamestartchallenge] + && !gamedata->unlocked[gamestartchallenge] + ); +} + boolean M_CheckNetUnlockByID(UINT16 unlockid) { if (unlockid >= MAXUNLOCKABLES diff --git a/src/m_cond.h b/src/m_cond.h index f636b9515..09175e8b3 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -472,6 +472,7 @@ extern UINT16 gamestartchallenge; boolean M_CheckNetUnlockByID(UINT16 unlockid); boolean M_SecretUnlocked(INT32 type, boolean local); boolean M_GameTrulyStarted(void); +boolean M_GameAboutToStart(void); boolean M_CupLocked(cupheader_t *cup); boolean M_CupSecondRowLocked(void); boolean M_MapLocked(UINT16 mapnum); diff --git a/src/menus/main-1.c b/src/menus/main-1.c index f055358dc..3a913427e 100644 --- a/src/menus/main-1.c +++ b/src/menus/main-1.c @@ -101,7 +101,7 @@ void M_QuitSRB2(INT32 choice) (void)choice; - if (M_GameTrulyStarted()) + if (!M_GameAboutToStart() && M_GameTrulyStarted()) { INT32 mrand = M_RandomKey(sizeof(quitsounds) / sizeof(INT32)); if (quitsounds[mrand]) diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 8ed0078ed..c94be64a1 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -562,7 +562,7 @@ void M_EndGame(INT32 choice) return; if (M_GameTrulyStarted() == false - || (gametype == GT_TUTORIAL && restoreMenu == NULL)) // Playground Hack + || M_GameAboutToStart() == true) // Playground Hack { // No returning to the title screen. M_QuitSRB2(-1); From 56df65d18cac2c4fdca4740de89a20336077df21 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 19:52:02 +0100 Subject: [PATCH 16/17] Extra newline for "do you want to play the tutorial again" text --- src/menus/main-goner.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 16df4fe2e..3bcbb9269 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -1522,7 +1522,8 @@ void M_GonerChoice(INT32 choice) if (gamedata->gonerlevel >= GDGONER_OUTRO) { M_StartMessage("First Boot Tutorial", - "You've already played the Tutorial! Do you want to see it again?", + "You've already played the Tutorial!\n" + "Do you want to see it again?", &M_GonerTutorialResponse, MM_YESNO, "I'd love to", "Not right now"); return; } From f8aaa43f73b91d24fbceec36b58b1efb9a1f74ce Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 24 Jul 2025 19:56:18 +0100 Subject: [PATCH 17/17] Reset *all* First Boot specific state in M_ClearSecrets, not M_ClearStats Guarantees wiping gamedata will send you through goner again, since otherwise why are we wiping gonerlevel Also fixes restarting Goner's audio --- src/m_cond.c | 7 ++++--- src/menus/main-goner.cpp | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index f113cda42..c94a115ff 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -660,10 +660,7 @@ void M_ClearStats(void) gamedata->chaokeytutorial = false; gamedata->majorkeyskipattempted = false; gamedata->enteredtutorialchallenge = false; - gamedata->finishedtutorialchallenge = false; gamedata->sealedswapalerted = false; - gamedata->tutorialdone = false; - gamedata->playgroundroute = false; gamedata->musicstate = GDMUSIC_NONE; gamedata->importprofilewins = false; @@ -778,6 +775,10 @@ void M_ClearSecrets(void) gamedata->chaokeys = GDINIT_CHAOKEYS; gamedata->prisoneggstothispickup = GDINIT_PRISONSTOPRIZE; + gamedata->tutorialdone = false; + gamedata->playgroundroute = false; + gamedata->finishedtutorialchallenge = false; + gamedata->gonerlevel = GDGONER_INIT; } diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 3bcbb9269..0dd64e811 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -83,6 +83,7 @@ static void M_GonerAccessibilityTick(void) if (!menumessage.active && !menutransition.dest) { M_SetupNextMenu(&MAIN_GonerDef, true); + M_GonerTick(); // tick once, for safety } return; @@ -984,6 +985,8 @@ void M_GonerTick(void) {.cvar = &cv_dummyextraspassword}, 0, 0}; #endif + MAIN_Goner[0].mvar2 = 0; + if (gamedata->gonerlevel < GDGONER_INTRO) gamedata->gonerlevel = GDGONER_INTRO;