From fcb696d1ad2c4db758ef405d9e6ab207ee6e842f Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 27 Nov 2023 23:52:14 +0000 Subject: [PATCH 01/64] First pass at the "Game Truly Starting" apparatus - Unlock Ring Cup to get the real Title Screen, demos and all. - Allows M_InterruptMenuWithChallenges. --- src/deh_soc.c | 8 ++++ src/f_finale.c | 81 +++++++++++++++++++++-------------- src/m_cond.c | 24 +++++++++++ src/m_cond.h | 3 ++ src/menus/extras-challenges.c | 8 ++-- 5 files changed, 87 insertions(+), 37 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 974532c24..94ae9b764 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3539,6 +3539,14 @@ void readmaincfg(MYFILE *f, boolean mainfile) Z_Free(tutorialchallengemap); tutorialchallengemap = Z_StrDup(word2); } + else if (fastcmp(word, "GAMESTARTCHALLENGE")) + { + INT32 val = get_number(word2) - 1; + if (val < 0 || val >= MAXUNLOCKABLES) + gamestartchallenge = MAXUNLOCKABLES; + else + gamestartchallenge = (UINT16)val; + } else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "TITLEPICSHIDE")) { hidetitlepics = (boolean)(value != 0 || word2[0] == 'T' || word2[0] == 'Y'); diff --git a/src/f_finale.c b/src/f_finale.c index e751084bc..2e56f3c1d 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1470,6 +1470,8 @@ static void F_CacheTitleScreen(void) } } +static boolean cache_gametrulystarted = false; + void F_StartTitleScreen(void) { INT32 titleMapNum; @@ -1486,7 +1488,10 @@ void F_StartTitleScreen(void) else wipegamestate = GS_TITLESCREEN; - if (titlemap + cache_gametrulystarted = M_GameTrulyStarted(); + + if (cache_gametrulystarted == true + && titlemap && ((titleMapNum = G_MapNumber(titlemap)) < nummapheaders) && mapheaderinfo[titleMapNum] && mapheaderinfo[titleMapNum]->lumpnum != LUMPERROR) @@ -1598,16 +1603,19 @@ void F_TitleScreenDrawer(void) { boolean hidepics = false; -#if 0 - if (modeattacking) - return; // We likely came here from retrying. Don't do a damn thing. -#endif - // Draw that sky! - if (curbgcolor >= 0) + if (cache_gametrulystarted == false) + { + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + } + else if (curbgcolor >= 0) + { V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor); + } else if (!curbghide || !titlemapinaction || gamestate == GS_WAITINGPLAYERS) + { F_SkyScroll(curbgxspeed, curbgyspeed, curbgname); + } // Don't draw outside of the title screen, or if the patch isn't there. if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS) @@ -1629,35 +1637,42 @@ void F_TitleScreenDrawer(void) case TTMODE_RINGRACERS: { - const char *eggName = "eggman"; - INT32 eggSkin = R_SkinAvailable(eggName); - skincolornum_t eggColor = SKINCOLOR_RED; - UINT8 *eggColormap = NULL; - - const char *tailsName = "tails"; - INT32 tailsSkin = R_SkinAvailable(tailsName); - skincolornum_t tailsColor = SKINCOLOR_ORANGE; - UINT8 *tailsColormap = NULL; - - if (eggSkin != -1) + if (cache_gametrulystarted == true) { - eggColor = skins[eggSkin].prefcolor; - } - eggColormap = R_GetTranslationColormap(TC_DEFAULT, eggColor, GTC_MENUCACHE); + const char *eggName = "eggman"; + INT32 eggSkin = R_SkinAvailable(eggName); + skincolornum_t eggColor = SKINCOLOR_RED; + UINT8 *eggColormap = NULL; - if (tailsSkin != -1) + const char *tailsName = "tails"; + INT32 tailsSkin = R_SkinAvailable(tailsName); + skincolornum_t tailsColor = SKINCOLOR_ORANGE; + UINT8 *tailsColormap = NULL; + + if (eggSkin != -1) + { + eggColor = skins[eggSkin].prefcolor; + } + eggColormap = R_GetTranslationColormap(TC_DEFAULT, eggColor, GTC_MENUCACHE); + + if (tailsSkin != -1) + { + tailsColor = skins[tailsSkin].prefcolor; + } + tailsColormap = R_GetTranslationColormap(TC_DEFAULT, tailsColor, GTC_MENUCACHE); + + V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_tails_tails, tailsColormap); + V_DrawFixedPatch(0, 0, FRACUNIT, V_ADD, kts_electricity[finalecount % 6], NULL); + + V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_eggman, eggColormap); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_tails, tailsColormap); + + V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_bumper, NULL); + } + else { - tailsColor = skins[tailsSkin].prefcolor; + V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2) - 4, 0, "Press any button/key to continue"); } - tailsColormap = R_GetTranslationColormap(TC_DEFAULT, tailsColor, GTC_MENUCACHE); - - V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_tails_tails, tailsColormap); - V_DrawFixedPatch(0, 0, FRACUNIT, V_ADD, kts_electricity[finalecount % 6], NULL); - - V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_eggman, eggColormap); - V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_tails, tailsColormap); - - V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_bumper, NULL); V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_copyright, NULL); break; @@ -1796,7 +1811,7 @@ void F_TitleScreenTicker(boolean run) } // no demos to play? or, are they disabled? - if (!cv_rollingdemos.value) + if (!cv_rollingdemos.value || cache_gametrulystarted == false) return; #if defined (TESTERS) diff --git a/src/m_cond.c b/src/m_cond.c index 7815eae6f..b4f5a0ba6 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -48,6 +48,9 @@ unlockable_t unlockables[MAXUNLOCKABLES]; // Number of emblems INT32 numemblems = 0; +// The challenge that will truly let the games begin. +UINT16 gamestartchallenge = 600; // 601 + // Create a new gamedata_t, for start-up void M_NewGameDataStruct(void) { @@ -3200,6 +3203,27 @@ UINT16 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separ // Quick unlock checks // ------------------- +boolean M_GameTrulyStarted(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; + + // Okay, we can check to see if this challenge has been achieved. + 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 e79a28676..87c38060e 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -435,9 +435,12 @@ void M_UpdateNextPrisonEggPickup(void); UINT16 M_CheckLevelEmblems(void); UINT16 M_CompletionEmblems(void); +extern UINT16 gamestartchallenge; + // Checking unlockable status boolean M_CheckNetUnlockByID(UINT16 unlockid); boolean M_SecretUnlocked(INT32 type, boolean local); +boolean M_GameTrulyStarted(void); boolean M_CupLocked(cupheader_t *cup); boolean M_CupSecondRowLocked(void); boolean M_MapLocked(UINT16 mapnum); diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index da5dc2bd1..fcfd72fe5 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -291,7 +291,8 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu) { UINT16 newunlock; - if (Playing()) + if (Playing() == true + || M_GameTrulyStarted() == false) return desiredmenu; M_UpdateUnlockablesAndExtraEmblems(false, true); @@ -376,9 +377,8 @@ boolean M_CanKeyHiliTile(void) UINT16 i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy; - // Not a hinted tile OR a fresh board. - if (!(challengesmenu.extradata[i].flags & CHE_HINT) - && (challengesmenu.unlockcount[CMC_UNLOCKED] > 0)) + // Not a hinted tile. + if (!(challengesmenu.extradata[i].flags & CHE_HINT)) return false; // Marked as major? From 3c19c80842db3171cf6174bb4750dfa9a40db32b Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 28 Nov 2023 00:51:34 +0000 Subject: [PATCH 02/64] Intro changes Multi-stage swagscreens. Also fixes some issues with using playintro under New Menu conditions --- src/d_main.cpp | 9 ++- src/f_finale.c | 190 +++++++++++++++++++++++++++++-------------------- 2 files changed, 120 insertions(+), 79 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 18b2e3709..56fab25d3 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -396,13 +396,16 @@ static bool D_Display(void) // save the current screen if about to wipe wipe = (gamestate != wipegamestate); - if (wipe && wipetypepre != INT16_MAX) + if (wipe) { - // set for all later + // MUST be set for all later wipedefindex = gamestate; // wipe_xxx_toblack if (gamestate == GS_TITLESCREEN && wipegamestate != GS_INTRO) wipedefindex = wipe_titlescreen_toblack; + } + if (wipe && wipetypepre != INT16_MAX) + { if (wipetypepre < 0 || !F_WipeExists(wipetypepre)) wipetypepre = wipedefs[wipedefindex]; @@ -1113,7 +1116,7 @@ void D_StartTitle(void) void D_SetDeferredStartTitle(boolean deferred) { - g_deferredtitle = true; + g_deferredtitle = deferred; } boolean D_IsDeferredStartTitle(void) diff --git a/src/f_finale.c b/src/f_finale.c index 2e56f3c1d..48d98e6d0 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -284,7 +284,8 @@ static void F_TitleBGScroll(INT32 scrollspeed) // ============= // INTRO SCENE // ============= -#define NUMINTROSCENES 1 +#define NUMINTROSCENES 5 +#define INTROSCENE_KREW 2 // first scene with Kart Krew Dev INT32 intro_scenenum = 0; INT32 intro_curtime = 0; @@ -292,7 +293,11 @@ const char *introtext[NUMINTROSCENES]; static tic_t introscenetime[NUMINTROSCENES] = { - 4*TICRATE, // KART KR(eW + 2*TICRATE, // OUR SRB2 ASSOCIATES + TICRATE, // Listen to Funtown USA by tv room + (3*TICRATE)/2, // KKD + (2*TICRATE)/3, // S&K + TICRATE + (TICRATE/3), // Get ready !! }; // custom intros @@ -306,9 +311,15 @@ void F_StartIntro(void) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); F_WipeEndScreen(); F_RunWipe(wipe_intro_toblack, wipedefs[wipe_intro_toblack], false, "FADEMAP0", false, false); + + D_ClearState(); } + M_ClearMenus(false); + D_SetDeferredStartTitle(false); + Music_StopAll(); + Music_Stop("title"); S_StopSounds(); if (introtoplay) @@ -323,16 +334,15 @@ void F_StartIntro(void) introtext[0] = " #"; G_SetGamestate(GS_INTRO); + wipegamestate = gamestate; gameaction = ga_nothing; paused = false; CON_ToggleOff(); - F_NewCutscene(introtext[0]); + //F_NewCutscene(introtext[0]); intro_scenenum = 0; finalecount = animtimer = skullAnimCounter = stoptimer = 0; timetonext = introscenetime[intro_scenenum]; - - Music_StopAll(); } // @@ -340,31 +350,80 @@ void F_StartIntro(void) // static void F_IntroDrawScene(void) { - boolean highres = true; - INT32 cx = 8, cy = 128; - patch_t *background = NULL; + INT32 cx = 68*FRACUNIT, cy = 20*FRACUNIT; + INT32 jitterx = 0, jittery = 0; INT32 bgxoffs = 0; + patch_t *logoparts[5]; + UINT8 bgcol = 31; - // DRAW A FULL PIC INSTEAD OF FLAT! - if (intro_scenenum == 0) + if (intro_scenenum < INTROSCENE_KREW) { - background = W_CachePatchName("KARTKREW", PU_CACHE); - highres = true; + logoparts[0] = NULL; } - - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0); - - if (background) + else if (intro_scenenum == INTROSCENE_KREW) { - if (highres) - V_DrawSmallScaledPatch(bgxoffs, 0, 0, background); + logoparts[0] = W_CachePatchName("KKLOGO_C", PU_CACHE); + logoparts[1] = W_CachePatchName("KKTEXT_C", PU_CACHE); + logoparts[2] = NULL; + + bgcol = 0; + } + else + { + logoparts[0] = W_CachePatchName("KKLOGO_A", PU_CACHE); + logoparts[1] = W_CachePatchName("KKLOGO_B", PU_CACHE); + logoparts[2] = W_CachePatchName("KKTEXT_A", PU_CACHE); + logoparts[3] = W_CachePatchName("KKTEXT_B", PU_CACHE); + logoparts[4] = NULL; + + if (intro_scenenum == INTROSCENE_KREW+1) + { + bgxoffs = 1 + P_RandomKey(PR_INTERPHUDRANDOM, 8); + + bgcol -= (timetonext * 31) / introscenetime[intro_scenenum]; + + const angle_t fa = (FixedAngle(intro_curtime*FRACUNIT) >> ANGLETOFINESHIFT) & FINEMASK; + + jitterx = FINECOSINE(fa); + jittery = FINESINE(fa); + + if (finalecount & 1) + { + jitterx = -jitterx; + jittery = -jittery; + bgxoffs = -bgxoffs; + } + } else - V_DrawScaledPatch(bgxoffs, 0, 0, background); + { + bgxoffs = (1 + 8) + ((1 + intro_curtime) * 24); + } } - W_UnlockCachedPatch(background); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, bgcol); - V_DrawString(cx, cy, 0, cutscene_disptext); + UINT8 i; + for (i = 0; logoparts[i]; i++) + { + V_DrawFixedPatch( + cx + jitterx + (bgxoffs * FRACUNIT), + cy + jittery, + FRACUNIT, + 0, + logoparts[i], + NULL + ); + + bgxoffs = -bgxoffs; + + if (i == 1) + { + jitterx = -jitterx; + jittery = -jittery; + } + } + + //V_DrawString(cx, cy, 0, cutscene_disptext); } // @@ -372,7 +431,21 @@ static void F_IntroDrawScene(void) // void F_IntroDrawer(void) { - // Used to be this whole thing, but now... + if (intro_scenenum == 0) + { + if (intro_curtime <= 5) + { + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 157); + return; + } + + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0); + V_DrawScaledPatch(0, 0, 0, + W_CachePatchName("STARTUP", PU_CACHE)); + + return; + } + F_IntroDrawScene(); } @@ -392,66 +465,28 @@ void F_IntroTicker(void) return; } - if (intro_scenenum == 0) + if (timetonext <= 0) { - if (timetonext <= 0) + intro_scenenum++; + if (intro_scenenum == NUMINTROSCENES) { -#if 0 // The necessary apparatus for constructing more elaborate intros... - intro_scenenum++; - F_NewCutscene(introtext[intro_scenenum]); - timetonext = introscenetime[intro_scenenum]; - wipegamestate = -1; - animtimer = stoptimer = 0; -#endif - if (rendermode != render_none) - { - F_WipeStartScreen(); - F_WipeColorFill(31); - F_WipeEndScreen(); - F_RunWipe(wipe_intro_toblack, 99, true, "FADEMAP0", false, false); - } - - // Stay on black for a bit. =) - { - tic_t nowtime, quittime, lasttime; - nowtime = lasttime = I_GetTime(); - quittime = nowtime + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds - while (quittime > nowtime) - { - while (!((nowtime = I_GetTime()) - lasttime)) - { - I_Sleep(cv_sleep.value); - I_UpdateTime(cv_timescale.value); - } - lasttime = nowtime; - - I_OsPolling(); - I_UpdateNoBlit(); -#ifdef HAVE_THREADS - I_lock_mutex(&k_menu_mutex); -#endif - M_Drawer(); // menu is drawn even on top of wipes -#ifdef HAVE_THREADS - I_unlock_mutex(k_menu_mutex); -#endif - I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001 - -#ifdef HWRENDER - if (moviemode && rendermode == render_opengl) // make sure we save frames for the white hold too - M_LegacySaveFrame(); - else -#endif - if (moviemode && rendermode != render_none) - I_CaptureVideoFrame(); - } - } - D_StartTitle(); + // Custom built fade to skip the to-black + //wipetypepre = INT16_MAX; -- however, this breaks the title screen cacheing and I don't know why and I'm tired of fighting it. return; } - if (finalecount == 8) + //F_NewCutscene(introtext[intro_scenenum]); + timetonext = introscenetime[intro_scenenum]; + animtimer = stoptimer = 0; + if (intro_scenenum == INTROSCENE_KREW) + wipegamestate = -1; + } + + if (intro_scenenum == INTROSCENE_KREW) + { + if (timetonext == 5) S_StartSound(NULL, sfx_vroom); - else if (finalecount == 47) + else if (timetonext == 24) { // Need to use M_Random otherwise it always uses the same sound UINT32 rskin = R_GetLocalRandomSkin(); @@ -1553,6 +1588,9 @@ void F_StartTitleScreen(void) F_InitMenuPresValues(); F_CacheTitleScreen(); + + if (menumessage.active && !menumessage.closing) + menumessage.fadetimer = 1; } void F_VersionDrawer(void) From 050bf43a87563d5df1e74f1c98c692e13898c016 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 28 Nov 2023 20:43:14 +0000 Subject: [PATCH 03/64] New intro: Fix other aspect ratio border around STARTUP --- 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 48d98e6d0..4df44d2d5 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -439,7 +439,7 @@ void F_IntroDrawer(void) return; } - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); V_DrawScaledPatch(0, 0, 0, W_CachePatchName("STARTUP", PU_CACHE)); From d41188ded372cc7c6e7960361842352dfb4974ec Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 29 Nov 2023 18:51:52 +0000 Subject: [PATCH 04/64] Permit re-entering the Tutorial Challenge if the full game is accessible to you --- src/g_game.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index b5b7df905..73db462bf 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4331,7 +4331,11 @@ void G_NextLevel(void) if ( gametype == GT_TUTORIAL && nextmap == NEXTMAP_TUTORIALCHALLENGE - && !(gamedata && gamedata->enteredtutorialchallenge) + && ( + !gamedata + || gamedata->enteredtutorialchallenge == false + || M_GameTrulyStarted() == true + ) ) { nextmap = G_MapNumber(tutorialchallengemap); From ada8b362f19dfe7907d2a9ae2ba0669ee56fd661 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 29 Nov 2023 18:58:33 +0000 Subject: [PATCH 05/64] M_LevelListFromGametype: Auto-select the first Tutorial Mode course if there's only one available --- src/menus/transient/level-select.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index c01f4c65f..c60cd9695 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -569,8 +569,15 @@ boolean M_LevelListFromGametype(INT16 gt) if (gt != -1) { - PLAY_LevelSelectDef.prevMenu = currentMenu; - M_SetupNextMenu(&PLAY_LevelSelectDef, false); + if (levellist.levelsearch.tutorial && levellist.mapcount == 1) + { + M_LevelSelected(0); // Skip the list! + } + else + { + PLAY_LevelSelectDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_LevelSelectDef, false); + } } return true; From cff207e7aa250f9727323975da994f769190521f Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 29 Nov 2023 19:03:11 +0000 Subject: [PATCH 06/64] Don't update menujam before game has truly started --- src/p_setup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_setup.c b/src/p_setup.c index 1f1e371af..913158baf 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7956,7 +7956,7 @@ static void P_InitGametype(void) G_RecordDemo(buf); } - if (gamestate != GS_TITLESCREEN) + if (gamestate != GS_TITLESCREEN && M_GameTrulyStarted()) { // Started a game? Move on to the next jam when you go back to the title screen // this permits all but titlescreen, instead of only GS_LEVEL From fd0466a8dfa5c928d06c35bb99a4536196126b62 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 29 Nov 2023 19:36:17 +0000 Subject: [PATCH 07/64] M_OptionsTick: Instant transmission If a menu fade has occurred, perform the relevant offsetting imemdiately, no smooth animation. --- src/menus/options-1.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/menus/options-1.c b/src/menus/options-1.c index cfd7339e3..5d5ff5660 100644 --- a/src/menus/options-1.c +++ b/src/menus/options-1.c @@ -135,16 +135,22 @@ boolean M_OptionsQuit(void) void M_OptionsTick(void) { - optionsmenu.offset /= 2; + boolean instanttransmission = optionsmenu.ticker == 0 && menuwipe; + optionsmenu.ticker++; - optionsmenu.optx += (optionsmenu.toptx - optionsmenu.optx)/2; - optionsmenu.opty += (optionsmenu.topty - optionsmenu.opty)/2; - - if (abs(optionsmenu.optx - optionsmenu.opty) < 2) + if (!instanttransmission) { - optionsmenu.optx = optionsmenu.toptx; - optionsmenu.opty = optionsmenu.topty; // Avoid awkward 1 px errors. + optionsmenu.offset /= 2; + + optionsmenu.optx += (optionsmenu.toptx - optionsmenu.optx)/2; + optionsmenu.opty += (optionsmenu.topty - optionsmenu.opty)/2; + + if (abs(optionsmenu.optx - optionsmenu.opty) < 2) + { + optionsmenu.optx = optionsmenu.toptx; + optionsmenu.opty = optionsmenu.topty; // Avoid awkward 1 px errors. + } } // Move the button for cool animations @@ -177,8 +183,21 @@ void M_OptionsTick(void) optionsmenu.fade--; // change the colour if we aren't matching the current menu colour - if (optionsmenu.currcolour != currentMenu->extra1) - M_OptionsChangeBGColour(currentMenu->extra1); + if (instanttransmission) + { + optionsmenu.currcolour = currentMenu->extra1; + optionsmenu.offset = optionsmenu.fade = 0; + + optionsmenu.optx = optionsmenu.toptx; + optionsmenu.opty = optionsmenu.topty; + } + else + { + if (optionsmenu.fade) + optionsmenu.fade--; + if (optionsmenu.currcolour != currentMenu->extra1) + M_OptionsChangeBGColour(currentMenu->extra1); + } // And one last giggle... if (shitsfree) From 0f687be7b5ea4513f2e775b11ef14eb76abe8ead Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 29 Nov 2023 19:37:05 +0000 Subject: [PATCH 08/64] The Goner Setup The simplest passable version of the first boot experience. - The minimal set of options you will want are available right in front of you. - Quit - Password - Video Options - Sound Options - Profile Setup - Begin Tutorial - Will not start any menujams while this is in progress. --- src/k_menu.h | 12 +++ src/k_menufunc.c | 21 +++++- src/menus/CMakeLists.txt | 1 + src/menus/main-goner.c | 111 ++++++++++++++++++++++++++++ src/menus/options-1.c | 23 +++++- src/menus/options-profiles-1.c | 3 +- src/menus/options-profiles-edit-1.c | 15 +++- src/menus/transient/level-select.c | 6 +- 8 files changed, 181 insertions(+), 11 deletions(-) create mode 100644 src/menus/main-goner.c diff --git a/src/k_menu.h b/src/k_menu.h index cbc3c899a..eec2a59c1 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -200,6 +200,13 @@ typedef enum quitkart } main_e; +extern menuitem_t MAIN_Goner[]; +extern menu_t MAIN_GonerDef; + +void M_GonerTick(void); +void M_GonerProfile(INT32 choice); +void M_GonerTutorial(INT32 choice); + extern menuitem_t PLAY_CharSelect[]; extern menu_t PLAY_CharSelectDef; @@ -331,6 +338,8 @@ typedef enum extern menuitem_t OPTIONS_EditProfile[]; extern menu_t OPTIONS_EditProfileDef; +void M_StartEditProfile(INT32 c); + extern menuitem_t OPTIONS_ProfileControls[]; extern menu_t OPTIONS_ProfileControlsDef; @@ -990,6 +999,9 @@ boolean M_OptionsInputs(INT32 ch); boolean M_OptionsQuit(void); // resets buttons when you quit the options. void M_OptionsChangeBGColour(INT16 newcolour); // changes the background colour for options +void M_VideoOptions(INT32 choice); +void M_SoundOptions(INT32 choice); + void M_HandleItemToggles(INT32 choice); // For item toggling void M_EraseData(INT32 choice); // For data erasing void M_CheckProfileData(INT32 choice); // check if we have profiles. diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 8fbc292d1..ef6863594 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -357,6 +357,11 @@ void M_PlayMenuJam(void) menu_t *refMenu = (menuactive ? currentMenu : restoreMenu); static boolean musicstatepermitted = false; + if (M_GameTrulyStarted() == false) + { + return; + } + if (challengesmenu.pending) { Music_StopAll(); @@ -558,7 +563,16 @@ void M_StartControlPanel(void) Music_Stop("title"); - if (cv_currprofile.value == -1) // Only ask once per session. + if (M_GameTrulyStarted() == false) + { + // Are you ready for the First Boot Experience? + M_ResetOptions(); + currentMenu = &MAIN_GonerDef; + currentMenu->lastOn = 0; + Music_Remap("menu_nocred", "_GONER"); + Music_Play("menu_nocred"); + } + else if (cv_currprofile.value == -1) // Only ask once per session. { // Make sure the profile data is ready now since we need to select a profile. M_ResetOptions(); @@ -570,8 +584,9 @@ void M_StartControlPanel(void) // options don't need initializing here. // make sure we don't overstep that. - if (optionsmenu.profilen > PR_GetNumProfiles()) - optionsmenu.profilen = PR_GetNumProfiles(); + const INT32 maxp = PR_GetNumProfiles(); + if (optionsmenu.profilen > maxp) + optionsmenu.profilen = maxp; else if (optionsmenu.profilen < 0) optionsmenu.profilen = 0; diff --git a/src/menus/CMakeLists.txt b/src/menus/CMakeLists.txt index 2fdbb8719..b3549df12 100644 --- a/src/menus/CMakeLists.txt +++ b/src/menus/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(SRB2SDL2 PRIVATE extras-statistics.c extras-wrong.c main-1.c + main-goner.c main-profile-select.c options-1.c options-data-1.c diff --git a/src/menus/main-goner.c b/src/menus/main-goner.c new file mode 100644 index 000000000..ad0ffd3d4 --- /dev/null +++ b/src/menus/main-goner.c @@ -0,0 +1,111 @@ +/// \file menus/main-goner.c +/// \brief The Goner Setup. + +#include "../k_menu.h" + +menuitem_t MAIN_Goner[] = +{ + {IT_STRING | IT_CALL, "Quit", + "CONCLUDE OBSERVATIONS NOW.", NULL, + {.routine = M_QuitSRB2}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Password", "ATTEMPT ADMINISTRATOR ACCESS.", + NULL, {.cvar = &cv_dummyextraspassword}, 0, 0}, + + {IT_STRING | IT_CALL, "Video Options", "CONFIGURE OCULAR PATHWAYS.", + NULL, {.routine = M_VideoOptions}, 0, 0}, + + {IT_STRING | IT_CALL, "Sound Options", "CALIBRATE AURAL DATASTREAM.", + NULL, {.routine = M_SoundOptions}, 0, 0}, + + {IT_STRING | IT_CALL, "Profile Setup", + "ASSIGN VEHICLE INPUTS.", NULL, + {.routine = M_GonerProfile}, 0, 0}, + + {IT_STRING | IT_CALL, "Begin Tutorial", + "PREPARE FOR INTEGRATION.", NULL, + {.routine = M_GonerTutorial}, 0, 0}, +}; + +menu_t MAIN_GonerDef = { + sizeof (MAIN_Goner) / sizeof (menuitem_t), + NULL, + 0, + MAIN_Goner, + 32, 80, + 0, 0, + 0, + "_OCEAN", + 0, 0, + M_DrawGenericMenu, + M_GonerTick, + NULL, + NULL, + NULL, +}; + +void M_GonerTick(void) +{ + if (menutyping.active == false && cv_dummyextraspassword.string[0] != '\0') + { + // Challenges are not interpreted at this stage. + // See M_ExtraTick for the full behaviour. + + cht_Interpret(cv_dummyextraspassword.string); + CV_StealthSet(&cv_dummyextraspassword, ""); + } +} + +void M_GonerProfile(INT32 choice) +{ + (void)choice; + + optionsmenu.profilen = cv_ttlprofilen.value; + + const INT32 maxp = PR_GetNumProfiles(); + if (optionsmenu.profilen > maxp) + optionsmenu.profilen = maxp; + else if (optionsmenu.profilen < 1) + { + // Assume the first one is what we want..?? + CV_StealthSetValue(&cv_ttlprofilen, 1); + optionsmenu.profilen = 1; + } + + M_ResetOptions(); + + // This will create a new profile if necessary. + M_StartEditProfile(MA_YES); + PR_ApplyProfilePretend(optionsmenu.profilen, 0); +} + +void M_GonerTutorial(INT32 choice) +{ + (void)choice; + + if (cv_currprofile.value == -1) + { + const INT32 maxp = PR_GetNumProfiles(); + INT32 profilen = cv_ttlprofilen.value; + if (profilen >= maxp) + profilen = maxp-1; + else if (profilen < 1) + profilen = 1; + + PR_ApplyProfile(profilen, 0); + } + + // Please also see M_LevelSelectInit as called in extras-1.c + levellist.netgame = false; + levellist.levelsearch.checklocked = true; + cupgrid.grandprix = false; + levellist.levelsearch.timeattack = false; + + if (!M_LevelListFromGametype(GT_TUTORIAL)) + { + // The game is incapable of progression, but I can't bring myself to put an I_Error here. + M_StartMessage("SURVEY_PROGRAM", + "YOU ACCEPT EVERYTHING THAT WILL HAPPEN FROM NOW ON.", + &M_QuitResponse, MM_YESNO, "I agree", "Cancel"); + } +} diff --git a/src/menus/options-1.c b/src/menus/options-1.c index 5d5ff5660..9f43cc6aa 100644 --- a/src/menus/options-1.c +++ b/src/menus/options-1.c @@ -13,11 +13,11 @@ menuitem_t OPTIONS_Main[] = {IT_STRING | IT_CALL, "Profile Setup", "Remap keys & buttons to your likings.", NULL, {.routine = M_ProfileSelectInit}, 0, 0}, - {IT_STRING | IT_SUBMENU, "Video Options", "Change video settings such as the resolution.", - NULL, {.submenu = &OPTIONS_VideoDef}, 0, 0}, + {IT_STRING | IT_CALL, "Video Options", "Change video settings such as the resolution.", + NULL, {.routine = M_VideoOptions}, 0, 0}, {IT_STRING | IT_SUBMENU, "Sound Options", "Adjust various sound settings such as the volume.", - NULL, {.submenu = &OPTIONS_SoundDef}, 0, 0}, + NULL, {.routine = M_SoundOptions}, 0, 0}, {IT_STRING | IT_SUBMENU, "HUD Options", "Options related to the Heads-Up Display.", NULL, {.submenu = &OPTIONS_HUDDef}, 0, 0}, @@ -203,6 +203,23 @@ void M_OptionsTick(void) if (shitsfree) shitsfree--; } +static void M_OptionsMenuGoto(menu_t *assignment) +{ + assignment->prevMenu = currentMenu; + M_SetupNextMenu(assignment, false); +} + +void M_VideoOptions(INT32 choice) +{ + (void)choice; + M_OptionsMenuGoto(&OPTIONS_VideoDef); +} + +void M_SoundOptions(INT32 choice) +{ + (void)choice; + M_OptionsMenuGoto(&OPTIONS_SoundDef); +} boolean M_OptionsInputs(INT32 ch) { diff --git a/src/menus/options-profiles-1.c b/src/menus/options-profiles-1.c index bb194a2ee..9a697e1b6 100644 --- a/src/menus/options-profiles-1.c +++ b/src/menus/options-profiles-1.c @@ -61,7 +61,7 @@ void M_FirstPickProfile(INT32 c) } // Start menu edition. Call this with MA_YES if not used with a textbox. -static void M_StartEditProfile(INT32 c) +void M_StartEditProfile(INT32 c) { const INT32 maxp = PR_GetNumProfiles(); @@ -114,6 +114,7 @@ static void M_StartEditProfile(INT32 c) OPTIONS_EditProfile[popt_char].status |= IT_TRANSTEXT; } + OPTIONS_EditProfileDef.prevMenu = currentMenu; M_SetupNextMenu(&OPTIONS_EditProfileDef, false); return; } diff --git a/src/menus/options-profiles-edit-1.c b/src/menus/options-profiles-edit-1.c index ba57a2419..04ee24990 100644 --- a/src/menus/options-profiles-edit-1.c +++ b/src/menus/options-profiles-edit-1.c @@ -3,6 +3,7 @@ #include "../k_menu.h" #include "../s_sound.h" +#include "../m_cond.h" menuitem_t OPTIONS_EditProfile[] = { {IT_STRING | IT_CVAR | IT_CV_STRING, "Profile Name", "6-character long name to identify this Profile.", @@ -76,9 +77,17 @@ static boolean M_ProfileEditEnd(const UINT8 pid) static void M_ProfileEditExit(void) { - optionsmenu.toptx = 160; - optionsmenu.topty = 35; - optionsmenu.resetprofile = true; // Reset profile after the transition is done. + if (M_GameTrulyStarted() == true) + { + optionsmenu.toptx = 160; + optionsmenu.topty = 35; + optionsmenu.resetprofile = true; // Reset profile after the transition is done. + } + else + { + M_ResetOptions(); // Reset all options variables otherwise things are gonna go reaaal bad lol. + optionsmenu.profile = NULL; // Make sure to get rid of that, too. + } PR_SaveProfiles(); // save profiles after we do that. } diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index c60cd9695..9b22207e0 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -714,7 +714,11 @@ void M_LevelSelected(INT16 add) D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - if (levellist.levelsearch.tutorial) + if (M_GameTrulyStarted() == false) + { + // No restoreMenu set. + } + else if (levellist.levelsearch.tutorial) { restoreMenu = currentMenu; } From 4c284680ff92a0e5f1894608403c7827bff7a628 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 30 Nov 2023 00:11:37 +0000 Subject: [PATCH 09/64] Without access to the full game, trying to back out of the Goner Setup/Tutorial will give you a quit prompt instead --- src/menus/main-goner.c | 19 ++++++++++++++++++- src/menus/transient/pause-game.c | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/menus/main-goner.c b/src/menus/main-goner.c index ad0ffd3d4..d5531d65d 100644 --- a/src/menus/main-goner.c +++ b/src/menus/main-goner.c @@ -27,6 +27,8 @@ menuitem_t MAIN_Goner[] = {.routine = M_GonerTutorial}, 0, 0}, }; +static boolean M_GonerInputs(INT32 ch); + menu_t MAIN_GonerDef = { sizeof (MAIN_Goner) / sizeof (menuitem_t), NULL, @@ -41,7 +43,7 @@ menu_t MAIN_GonerDef = { M_GonerTick, NULL, NULL, - NULL, + M_GonerInputs, }; void M_GonerTick(void) @@ -109,3 +111,18 @@ void M_GonerTutorial(INT32 choice) &M_QuitResponse, MM_YESNO, "I agree", "Cancel"); } } + +static boolean M_GonerInputs(INT32 ch) +{ + const UINT8 pid = 0; + (void)ch; + + if (M_MenuBackPressed(pid)) + { + // No returning to the title screen. + M_QuitSRB2(-1); + return true; + } + + return false; +} diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 4afe00d2e..eac5240eb 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -499,5 +499,12 @@ void M_EndGame(INT32 choice) if (!Playing()) return; + if (M_GameTrulyStarted() == false) + { + // No returning to the title screen. + M_QuitSRB2(-1); + return; + } + M_StartMessage("Return to Menu", M_GetText("Are you sure you want to\nreturn to the menu?\n"), &M_ExitGameResponse, MM_YESNO, NULL, NULL); } From 42f37e7dedfb58102490a04aea08df2246b10e2d Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 30 Nov 2023 15:03:48 +0000 Subject: [PATCH 10/64] Small improvements to Dialogue internals - Guarantee not-visible dialogue on map transition/invalidation - Don't grab a portrait for skins that don't have portraits --- src/k_dialogue.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index 7f3a03b18..76f575700 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -70,15 +70,20 @@ void Dialogue::SetSpeaker(std::string skinName, int portraitID) if (sprdef->numframes > 0) { portraitID %= sprdef->numframes; + + const spriteframe_t *sprframe = &sprdef->spriteframes[portraitID]; + + portrait = static_cast( W_CachePatchNum(sprframe->lumppat[0], PU_CACHE) ); + portraitColormap = R_GetTranslationColormap(skinID, static_cast(skin->prefcolor), GTC_CACHE); + } + else + { + portrait = nullptr; + portraitColormap = nullptr; } - const spriteframe_t *sprframe = &sprdef->spriteframes[portraitID]; - speaker = skin->realname; - portrait = static_cast( W_CachePatchNum(sprframe->lumppat[0], PU_CACHE) ); - portraitColormap = R_GetTranslationColormap(skinID, static_cast(skin->prefcolor), GTC_CACHE); - voiceSfx = skin->soundsid[ S_sfx[sfx_ktalk].skinsound ]; } else @@ -333,6 +338,7 @@ void Dialogue::Unset(void) { Dismiss(); SetSpeaker(); + slide = 0; } /* From 426e9001a54bc340f9b0b0ffa5178ebcf69be731 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 30 Nov 2023 17:53:26 +0000 Subject: [PATCH 11/64] Dualogue::Draw -- split into multiple segment patches - Changes the length of dark colour underneath the speaker's name - Optionally hides the speaker's face if no portrait is provided - Frosted glass background when player has control --- src/k_dialogue.cpp | 103 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index 76f575700..9666e8017 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -27,6 +27,7 @@ #include "s_sound.h" #include "z_zone.h" #include "k_hud.h" +#include "p_tick.h" // P_LevelIsFrozen #include "v_draw.hpp" @@ -284,25 +285,119 @@ void Dialogue::Draw(void) return; } + const UINT8 bgcol = 1, darkcol = 235; + + INT32 speakernameedge = 6; + srb2::Draw drawer = srb2::Draw( 0, FixedToFloat(Easing_OutCubic(slide, -78 * FRACUNIT, 0)) ).flags(V_SNAPTOTOP); - drawer.patch("TUTDIAG1"); + // TODO -- hack, change when dialogue is made per-player/netsynced + UINT32 speakerbgflags = (players[consoleplayer].nocontrol == 0 && P_LevelIsFrozen() == false) + ? (V_ADD|V_30TRANS) + : 0; + + drawer + .flags(speakerbgflags) + .patch("TUTDIAGA"); + + drawer.patch("TUTDIAGB"); if (portrait != nullptr) { + drawer.patch("TUTDIAGC"); + drawer .xy(10, 41) .colormap(portraitColormap) .patch(portrait); + + speakernameedge += 39; // 45 + } + + const char *speakername = speaker.c_str(); + + INT32 rightmostedge = 142; + const INT32 arrowstep = 8; // width of TUTDIAGD + + if (speakername && speaker[0]) + { + INT32 speakernamewidth = V_StringWidth(speakername, 0); + INT32 existingborder = (portrait == nullptr ? -4 : 3); + + const INT32 speakernamewidthoffset = (speakernamewidth + (arrowstep - existingborder) - 1) % arrowstep; + if (speakernamewidthoffset) + { + speakernamewidth += (arrowstep - speakernamewidthoffset); + } + + if (portrait == nullptr) + { + speakernameedge += 3; + speakernamewidth += 3; + existingborder += 2; + drawer + .xy(speakernameedge - 2, 36) + .width(2) + .height(3+11) + .fill(bgcol); + } + + if (speakernamewidth > existingborder) + { + drawer + .x(speakernameedge + existingborder) + .width(speakernamewidth - existingborder) + .y(36) + .height(3) + .fill(bgcol); + + drawer + .x(speakernameedge + existingborder) + .width(speakernamewidth - existingborder) + .y(38) + .height(11) + .fill(darkcol); + } + + drawer + .xy(speakernameedge, 39) + .font(srb2::Draw::Font::kConsole) + .text(speakername); + + speakernameedge += speakernamewidth; + + drawer + .xy(speakernameedge + 5, 36) + .patch("TUTDIAGD"); + + drawer + .xy(speakernameedge, 36) + .width(5) + .height(3+11) + .fill(bgcol); + + drawer + .xy(speakernameedge, 36) + .patch("TUTDIAGF"); + + speakernameedge += (5+arrowstep); + } + + while (speakernameedge < rightmostedge) + { + drawer + .xy(speakernameedge, 36) + .patch("TUTDIAGD"); + + speakernameedge += arrowstep; } drawer - .xy(45, 39) - .font(srb2::Draw::Font::kConsole) - .text( speaker.c_str() ); + .xy(speakernameedge, 36) + .patch("TUTDIAGE"); drawer .xy(10, 3) From f01e3bce9bd6ec14474029c0977fa51039a0928b Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 30 Nov 2023 17:54:51 +0000 Subject: [PATCH 12/64] CallFunc_DialogueSetCustomSpeaker: Support empty string --- src/acs/call-funcs.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index d87f2756e..f7354723d 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -2082,13 +2082,18 @@ bool CallFunc_DialogueSetCustomSpeaker(ACSVM::Thread *thread, const ACSVM::Word patchStr = map->getString(argV[1]); patchName = patchStr->str; - patch = static_cast( W_CachePatchName(patchName, PU_CACHE) ); colorStr = map->getString(argV[2]); colorName = colorStr->str; - if (ACS_GetColorFromString(colorName, &colorID) == true) + + if (patchName && patchName[0]) { - colormap = R_GetTranslationColormap(TC_DEFAULT, colorID, GTC_CACHE); + patch = static_cast( W_CachePatchName(patchName, PU_CACHE) ); + + if (ACS_GetColorFromString(colorName, &colorID) == true) + { + colormap = R_GetTranslationColormap(TC_DEFAULT, colorID, GTC_CACHE); + } } voiceStr = map->getString(argV[3]); From b2315484d483221886a225004b8f3c7da6fe6794 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 30 Nov 2023 23:35:16 +0000 Subject: [PATCH 13/64] Tutorial gametype: No sidebar or timer --- src/k_hud.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/k_hud.c b/src/k_hud.c index b450c3e81..6575bd6c8 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5462,6 +5462,10 @@ void K_drawKartHUD(void) if (demo.title) ; + else if (gametype == GT_TUTORIAL) + { + islonesome = true; + } else if (!r_splitscreen) { // Draw the timestamp From 00fdae8b521f11a99a066a44ca375dc262efa74f Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 1 Dec 2023 15:56:08 +0000 Subject: [PATCH 14/64] srb2::Draw::Chain: Make the button function name different between public and private contexts The author of this commit's compiler was overzealous and kept on matching the wrong one --- src/v_draw.cpp | 2 +- src/v_draw.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v_draw.cpp b/src/v_draw.cpp index 9ad6193ce..17ed5bdae 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -118,7 +118,7 @@ patch_t** get_button_patch(Draw::Button type, int ver) }; // namespace -void Chain::button(Button type, int ver, std::optional press) const +void Chain::button_(Button type, int ver, std::optional press) const { const auto _ = Clipper(*this); diff --git a/src/v_draw.hpp b/src/v_draw.hpp index bc57241c0..c75f3816e 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -163,8 +163,8 @@ public: void fill(UINT8 color) const; - void button(Button type, std::optional press = {}) const { button(type, 0, press); } - void small_button(Button type, std::optional press = {}) const { button(type, 1, press); } + void button(Button type, std::optional press = {}) const { button_(type, 0, press); } + void small_button(Button type, std::optional press = {}) const { button_(type, 1, press); } private: constexpr Chain() {} @@ -198,7 +198,7 @@ public: const UINT8* colormap_ = nullptr; void string(const char* str, INT32 flags, Font font) const; - void button(Button type, int ver, std::optional press = {}) const; + void button_(Button type, int ver, std::optional press = {}) const; friend Draw; }; From d0117bc22204763b37f016040f37db7769da2f5f Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 1 Dec 2023 16:44:11 +0000 Subject: [PATCH 15/64] Bottom-mounted Dialogue Makes the drawer more complicated, but the benefit for most circumstances is significant. In non-splitscreen contexts, pushes V_SLIDEIN|V_SNAPTOBOTTOM HUD elements upwards when the Dialogue is open. --- src/k_dialogue.cpp | 97 ++++++++++++++++++++++++++++------------------ src/k_dialogue.h | 1 + src/k_dialogue.hpp | 2 + src/v_video.cpp | 21 +++++++--- 4 files changed, 77 insertions(+), 44 deletions(-) diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index 9666e8017..0d53c6ca1 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -278,6 +278,15 @@ void Dialogue::Tick(void) } } +INT32 Dialogue::SlideAmount(fixed_t multiplier) +{ + if (slide == 0) + return 0; + if (slide == FRACUNIT) + return multiplier; + return Easing_OutCubic(slide, 0, multiplier); +} + void Dialogue::Draw(void) { if (slide == 0) @@ -287,12 +296,14 @@ void Dialogue::Draw(void) const UINT8 bgcol = 1, darkcol = 235; - INT32 speakernameedge = 6; + const fixed_t height = 78 * FRACUNIT; + + INT32 speakernameedge = -6; srb2::Draw drawer = srb2::Draw( - 0, FixedToFloat(Easing_OutCubic(slide, -78 * FRACUNIT, 0)) - ).flags(V_SNAPTOTOP); + BASEVIDWIDTH, BASEVIDHEIGHT - FixedToFloat(SlideAmount(height) - height) + ).flags(V_SNAPTOBOTTOM); // TODO -- hack, change when dialogue is made per-player/netsynced UINT32 speakerbgflags = (players[consoleplayer].nocontrol == 0 && P_LevelIsFrozen() == false) @@ -300,26 +311,29 @@ void Dialogue::Draw(void) : 0; drawer - .flags(speakerbgflags) + .flags(speakerbgflags|V_VFLIP|V_FLIP) .patch("TUTDIAGA"); - drawer.patch("TUTDIAGB"); + drawer + .flags(V_VFLIP|V_FLIP) + .patch("TUTDIAGB"); if (portrait != nullptr) { - drawer.patch("TUTDIAGC"); + drawer + .flags(V_VFLIP|V_FLIP) + .patch("TUTDIAGC"); drawer - .xy(10, 41) + .xy(-10-32, -41-32) .colormap(portraitColormap) .patch(portrait); - speakernameedge += 39; // 45 + speakernameedge -= 39; // -45 } const char *speakername = speaker.c_str(); - INT32 rightmostedge = 142; const INT32 arrowstep = 8; // width of TUTDIAGD if (speakername && speaker[0]) @@ -327,19 +341,20 @@ void Dialogue::Draw(void) INT32 speakernamewidth = V_StringWidth(speakername, 0); INT32 existingborder = (portrait == nullptr ? -4 : 3); - const INT32 speakernamewidthoffset = (speakernamewidth + (arrowstep - existingborder) - 1) % arrowstep; + INT32 speakernamewidthoffset = (speakernamewidth + (arrowstep - existingborder) - 1) % arrowstep; if (speakernamewidthoffset) { - speakernamewidth += (arrowstep - speakernamewidthoffset); + speakernamewidthoffset = (arrowstep - speakernamewidthoffset); + speakernamewidth += speakernamewidthoffset; } if (portrait == nullptr) { - speakernameedge += 3; + speakernameedge -= 3; speakernamewidth += 3; existingborder += 2; drawer - .xy(speakernameedge - 2, 36) + .xy(speakernameedge, -36) .width(2) .height(3+11) .fill(bgcol); @@ -348,59 +363,63 @@ void Dialogue::Draw(void) if (speakernamewidth > existingborder) { drawer - .x(speakernameedge + existingborder) + .x(speakernameedge - speakernamewidth) .width(speakernamewidth - existingborder) - .y(36) + .y(-36-3) .height(3) .fill(bgcol); drawer - .x(speakernameedge + existingborder) + .x(speakernameedge - speakernamewidth) .width(speakernamewidth - existingborder) - .y(38) + .y(-38-11) .height(11) .fill(darkcol); } + speakernameedge -= speakernamewidth; + drawer - .xy(speakernameedge, 39) + .xy(speakernamewidthoffset + speakernameedge, -39-9) .font(srb2::Draw::Font::kConsole) .text(speakername); - speakernameedge += speakernamewidth; + speakernameedge -= 5; drawer - .xy(speakernameedge + 5, 36) + .xy(speakernameedge, -36) + .flags(V_VFLIP|V_FLIP) .patch("TUTDIAGD"); drawer - .xy(speakernameedge, 36) + .xy(speakernameedge, -36-3-11) .width(5) .height(3+11) .fill(bgcol); drawer - .xy(speakernameedge, 36) + .xy(speakernameedge + 5, -36) + .flags(V_VFLIP|V_FLIP) .patch("TUTDIAGF"); - - speakernameedge += (5+arrowstep); } - while (speakernameedge < rightmostedge) + while (speakernameedge > -142) // the left-most edge { - drawer - .xy(speakernameedge, 36) - .patch("TUTDIAGD"); + speakernameedge -= arrowstep; - speakernameedge += arrowstep; + drawer + .xy(speakernameedge, -36) + .flags(V_VFLIP|V_FLIP) + .patch("TUTDIAGD"); } drawer - .xy(speakernameedge, 36) + .xy(speakernameedge - arrowstep, -36) + .flags(V_VFLIP|V_FLIP) .patch("TUTDIAGE"); drawer - .xy(10, 3) + .xy(10 - BASEVIDWIDTH, -3-32) .font(srb2::Draw::Font::kConsole) .text( text.c_str() ); @@ -409,16 +428,13 @@ void Dialogue::Draw(void) if (TextDone()) { drawer - .xy(304, 7) + .xy(-14, -7-5) .patch("TUTDIAG2"); } - K_drawButton( - FloatToFixed(drawer.x() + 303), - FloatToFixed(drawer.y() + 39), - V_SNAPTOTOP, - kp_button_z[0], Held() - ); + drawer + .xy(17-14 - BASEVIDWIDTH, -39-16) + .button(srb2::Draw::Button::z, Held()); } } @@ -459,3 +475,8 @@ void K_TickDialogue(void) { g_dialogue.Tick(); } + +INT32 K_GetDialogueSlide(fixed_t multiplier) +{ + return g_dialogue.SlideAmount(multiplier); +} diff --git a/src/k_dialogue.h b/src/k_dialogue.h index 4d6c05c79..8b8ba8381 100644 --- a/src/k_dialogue.h +++ b/src/k_dialogue.h @@ -26,6 +26,7 @@ void K_DrawDialogue(void); void K_TickDialogue(void); boolean K_DialogueFreeze(void); +INT32 K_GetDialogueSlide(fixed_t multiplier); #ifdef __cplusplus } // extern "C" diff --git a/src/k_dialogue.hpp b/src/k_dialogue.hpp index 8639524fc..d29a6b475 100644 --- a/src/k_dialogue.hpp +++ b/src/k_dialogue.hpp @@ -78,6 +78,8 @@ public: void Tick(void); void Draw(void); + INT32 SlideAmount(fixed_t multiplier); + void Dismiss(void); void Unset(void); }; diff --git a/src/v_video.cpp b/src/v_video.cpp index 2aa4067a6..4d4984a38 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -28,6 +28,7 @@ #include "r_draw.h" #include "console.h" #include "r_fps.h" +#include "k_dialogue.h" // K_GetDialogueSlide #include "i_video.h" // rendermode #include "z_zone.h" @@ -516,18 +517,26 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du INT32 baseheight = BASEVIDHEIGHT * dupy; SINT8 player = R_GetViewNumber(); - if (options & V_SPLITSCREEN) + if (r_splitscreen > 0) { - if (r_splitscreen > 0) + if (options & V_SPLITSCREEN) { screenheight /= 2; baseheight /= 2; - } - if (r_splitscreen > 1) + if (r_splitscreen > 1) + { + screenwidth /= 2; + basewidth /= 2; + } + } + } + else if ((options & (V_SLIDEIN|V_SNAPTOBOTTOM)) == (V_SLIDEIN|V_SNAPTOBOTTOM)) + { + INT32 slide = K_GetDialogueSlide(51 * FRACUNIT); + if (slide) { - screenwidth /= 2; - basewidth /= 2; + *y -= FixedMul(slide, dupy); } } From 30383ba82fc32a11010356b434b07df22940b715 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 1 Dec 2023 17:03:37 +0000 Subject: [PATCH 16/64] Replace a whole bunch of copypasted boilerplate with direct call to Command_ExitGame_f Fixes not going back to the Title Screen at the cusp of the game's true beginning. Also generally cleaner code --- src/d_clisrv.c | 60 ++++++++++---------------------------------------- src/g_game.c | 8 ++----- 2 files changed, 14 insertions(+), 54 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 1fa557757..1d1b2b0ba 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1751,10 +1751,7 @@ static boolean CL_FinishedFileList(void) } else if (i == 3) // too many files { - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Connection Failure", M_GetText( "You have too many WAD files loaded\n" @@ -1765,10 +1762,7 @@ static boolean CL_FinishedFileList(void) } else if (i == 2) // cannot join for some reason { - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Connection Failure", M_GetText( "You have the wrong addons loaded.\n\n" @@ -1804,10 +1798,7 @@ static boolean CL_FinishedFileList(void) { if (!CL_CheckDownloadable()) // nope! { - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Connection Failure", M_GetText( "An error occured when trying to\n" @@ -1931,10 +1922,7 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) serverlist[i].info.commit[n]); } - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Connection Failure", va( @@ -2084,10 +2072,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic { CONS_Printf(M_GetText("Legacy downloader request packet failed.\n")); CONS_Printf(M_GetText("Network game synchronization aborted.\n")); - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Connection Failure", M_GetText( "The direct download encountered an error.\n" @@ -2113,10 +2098,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic { CONS_Printf(M_GetText("5 minute wait time exceeded.\n")); CONS_Printf(M_GetText("Network game synchronization aborted.\n")); - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Connection Failure", M_GetText( "5 minute wait time exceeded.\n" @@ -2226,10 +2208,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic CONS_Printf(M_GetText("Network game synchronization aborted.\n")); // M_StartMessage("Server Connection", M_GetText("Network game synchronization aborted.\n"), NULL, MM_NOTHING, NULL, "Back to Menu"); - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); return false; } @@ -3125,10 +3104,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) #endif LUA_HookBool(false, HOOK(GameQuit)); //Lua hooks handled differently now - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); if (msg == KICK_MSG_CON_FAIL) M_StartMessage("Server Disconnected", M_GetText("Server closed connection\n(Synch failure)\n"), NULL, MM_NOTHING, NULL, "Back to Menu"); @@ -4326,10 +4302,7 @@ static void HandleShutdown(SINT8 node) { (void)node; LUA_HookBool(false, HOOK(GameQuit)); - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Disconnected", M_GetText("Server has shutdown\n"), NULL, MM_NOTHING, NULL, "Back to Menu"); } @@ -4342,10 +4315,7 @@ static void HandleTimeout(SINT8 node) { (void)node; LUA_HookBool(false, HOOK(GameQuit)); - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Disconnected", M_GetText("Server Timeout\n"), NULL, MM_NOTHING, NULL, "Back to Menu"); } @@ -4359,10 +4329,7 @@ void HandleSigfail(const char *string) } LUA_HookBool(false, HOOK(GameQuit)); - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); M_StartMessage("Server Disconnected", va(M_GetText("Signature check failed.\n(%s)\n"), string), NULL, MM_NOTHING, NULL, "Back to Menu"); } @@ -4561,10 +4528,7 @@ static void HandlePacketFromAwayNode(SINT8 node) break; } - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); if (reason[1] == '|') { diff --git a/src/g_game.c b/src/g_game.c index 73db462bf..c22bd031e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3000,10 +3000,7 @@ void G_FinishExitLevel(void) { // Back to the menu with you. G_HandleSaveLevel(true); - D_QuitNetGame(); - CL_Reset(); - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); return; } } @@ -4511,8 +4508,7 @@ void G_EndGame(void) } // Time to return to the menu. - D_ClearState(); - M_StartControlPanel(); + Command_ExitGame_f(); } // From df85b5d625773802a166f791dd380c7bf5594d28 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 1 Dec 2023 22:50:03 +0000 Subject: [PATCH 17/64] First pass horizontal menu for Goner Setup --- src/k_menu.h | 1 + src/k_menudraw.c | 54 ++++++++++++++++++++++++++++++++++++++++++ src/menus/main-goner.c | 25 ++++++++++--------- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index eec2a59c1..f5e414930 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1168,6 +1168,7 @@ void M_DrawMenuForeground(void); void M_Drawer(void); void M_DrawGenericMenu(void); void M_DrawKartGamemodeMenu(void); +void M_DrawHorizontalMenu(void); void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines); void M_DrawMessageMenu(void); void M_DrawImageDef(void); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9a5a6b035..558e016c3 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1116,6 +1116,60 @@ void M_DrawKartGamemodeMenu(void) } } +void M_DrawHorizontalMenu(void) +{ + INT32 x = BASEVIDWIDTH/2, y = currentMenu->y, i; + + const INT32 width = 80; + + i = itemOn; + + do + { + if (i == 0) + break; + i--; + x -= width; + } + while (x > -width/2); + + while (x < BASEVIDWIDTH + (width/2)) + { + V_DrawCenteredThinString( + x, y, + (i == itemOn) ? highlightflags : 0, + currentMenu->menuitems[i].text + ); + + if (++i == currentMenu->numitems) + break; + x += width; + } + + y++; // thin string means better to bottom-align these + + if (itemOn != 0) + V_DrawCharacter((BASEVIDWIDTH - width)/2 + 3 - (skullAnimCounter/5), y, + '\x1C' | highlightflags, false); // left arrow + + if (itemOn != currentMenu->numitems-1) + V_DrawCharacter((BASEVIDWIDTH + width)/2 - 10 + (skullAnimCounter/5), y, + '\x1D' | highlightflags, false); // right arrow + + x = (BASEVIDWIDTH - 8*(currentMenu->numitems-1))/2; + for (i = 0; i < currentMenu->numitems; i++, x += 8) + { + if (i == itemOn) + { + V_DrawFill(x-2, y + 15, 4, 4, 0); + } + else + { + V_DrawFill(x-1, y + 16, 2, 2, 16); + } + } +} + #define MAXMSGLINELEN 256 // diff --git a/src/menus/main-goner.c b/src/menus/main-goner.c index d5531d65d..581be98f9 100644 --- a/src/menus/main-goner.c +++ b/src/menus/main-goner.c @@ -5,18 +5,21 @@ 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, "Quit", "CONCLUDE OBSERVATIONS NOW.", NULL, {.routine = M_QuitSRB2}, 0, 0}, - {IT_STRING | IT_CVAR | IT_CV_STRING, "Password", "ATTEMPT ADMINISTRATOR ACCESS.", - NULL, {.cvar = &cv_dummyextraspassword}, 0, 0}, + {IT_STRING | IT_CALL, "Video Options", + "CONFIGURE OCULAR PATHWAYS.", NULL, + ={.routine = M_VideoOptions}, 0, 0}, - {IT_STRING | IT_CALL, "Video Options", "CONFIGURE OCULAR PATHWAYS.", - NULL, {.routine = M_VideoOptions}, 0, 0}, - - {IT_STRING | IT_CALL, "Sound Options", "CALIBRATE AURAL DATASTREAM.", - NULL, {.routine = M_SoundOptions}, 0, 0}, + {IT_STRING | IT_CALL, "Sound Options", + "CALIBRATE AURAL DATASTREAM.", NULL, + {.routine = M_SoundOptions}, 0, 0}, {IT_STRING | IT_CALL, "Profile Setup", "ASSIGN VEHICLE INPUTS.", NULL, @@ -34,12 +37,12 @@ menu_t MAIN_GonerDef = { NULL, 0, MAIN_Goner, - 32, 80, + 32, 160, 0, 0, - 0, - "_OCEAN", + MBF_UD_LR_FLIPPED, + "_GONER", 0, 0, - M_DrawGenericMenu, + M_DrawHorizontalMenu, M_GonerTick, NULL, NULL, From 299e0c3b71bdb81a775430a38d7f8c7e28fdc52e Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 2 Dec 2023 15:40:20 +0000 Subject: [PATCH 18/64] main-goner.cpp: convert from C sourcefile Also fixes sleepily-commited compilation-breaking typo --- src/menus/CMakeLists.txt | 2 +- src/menus/{main-goner.c => main-goner.cpp} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/menus/{main-goner.c => main-goner.cpp} (97%) diff --git a/src/menus/CMakeLists.txt b/src/menus/CMakeLists.txt index b3549df12..4e024fc55 100644 --- a/src/menus/CMakeLists.txt +++ b/src/menus/CMakeLists.txt @@ -6,7 +6,7 @@ target_sources(SRB2SDL2 PRIVATE extras-statistics.c extras-wrong.c main-1.c - main-goner.c + main-goner.cpp main-profile-select.c options-1.c options-data-1.c diff --git a/src/menus/main-goner.c b/src/menus/main-goner.cpp similarity index 97% rename from src/menus/main-goner.c rename to src/menus/main-goner.cpp index 581be98f9..37b5aa951 100644 --- a/src/menus/main-goner.c +++ b/src/menus/main-goner.cpp @@ -1,4 +1,4 @@ -/// \file menus/main-goner.c +/// \file menus/main-goner.cpp /// \brief The Goner Setup. #include "../k_menu.h" @@ -15,7 +15,7 @@ menuitem_t MAIN_Goner[] = {IT_STRING | IT_CALL, "Video Options", "CONFIGURE OCULAR PATHWAYS.", NULL, - ={.routine = M_VideoOptions}, 0, 0}, + {.routine = M_VideoOptions}, 0, 0}, {IT_STRING | IT_CALL, "Sound Options", "CALIBRATE AURAL DATASTREAM.", NULL, From 1cf14f29dce746e4ac15c2d47f908a9078f9484f Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 2 Dec 2023 16:50:42 +0000 Subject: [PATCH 19/64] k_dialogue: Seperate srb2::Dialogue::Typewriter out from srb2::Dialogue This is the minimal subset of properties useful for various non-popup Dialoglogs, such as the impending Goner Setup. --- src/k_dialogue.cpp | 264 ++++++++++++++++++++++++--------------------- src/k_dialogue.hpp | 80 ++++++++------ 2 files changed, 187 insertions(+), 157 deletions(-) diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index 0d53c6ca1..6d2286289 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -35,96 +35,16 @@ using srb2::Dialogue; -void Dialogue::Init(void) +// Dialogue::Typewriter + +void Dialogue::Typewriter::ClearText(void) { - active = true; - syllable = true; + text.clear(); + textDest.clear(); } -void Dialogue::SetSpeaker(void) +void Dialogue::Typewriter::NewText(std::string newText) { - // Unset speaker - speaker.clear(); - - portrait = nullptr; - portraitColormap = nullptr; - - voiceSfx = sfx_ktalk; -} - -void Dialogue::SetSpeaker(std::string skinName, int portraitID) -{ - Init(); - - // Set speaker based on a skin - int skinID = -1; - if (!skinName.empty()) - { - skinID = R_SkinAvailable(skinName.c_str()); - } - - if (skinID >= 0 && skinID < numskins) - { - const skin_t *skin = &skins[skinID]; - const spritedef_t *sprdef = &skin->sprites[SPR2_TALK]; - - if (sprdef->numframes > 0) - { - portraitID %= sprdef->numframes; - - const spriteframe_t *sprframe = &sprdef->spriteframes[portraitID]; - - portrait = static_cast( W_CachePatchNum(sprframe->lumppat[0], PU_CACHE) ); - portraitColormap = R_GetTranslationColormap(skinID, static_cast(skin->prefcolor), GTC_CACHE); - } - else - { - portrait = nullptr; - portraitColormap = nullptr; - } - - speaker = skin->realname; - - voiceSfx = skin->soundsid[ S_sfx[sfx_ktalk].skinsound ]; - } - else - { - SetSpeaker(); - } -} - -void Dialogue::SetSpeaker(std::string name, patch_t *patch, UINT8 *colormap, sfxenum_t voice) -{ - Init(); - - // Set custom speaker - speaker = name; - - if (speaker.empty()) - { - portrait = nullptr; - portraitColormap = nullptr; - voiceSfx = sfx_ktalk; - return; - } - - portrait = patch; - portraitColormap = colormap; - - voiceSfx = voice; -} - -void Dialogue::NewText(std::string newText) -{ - Init(); - - newText = V_ScaledWordWrap( - 290 << FRACBITS, - FRACUNIT, FRACUNIT, FRACUNIT, - 0, HU_FONT, - newText.c_str() - ); - text.clear(); textDest = newText; @@ -133,30 +53,15 @@ void Dialogue::NewText(std::string newText) textTimer = kTextPunctPause; textSpeed = kTextSpeedDefault; textDone = false; + + syllable = true; } -bool Dialogue::Active(void) +void Dialogue::Typewriter::WriteText(void) { - return active; -} + if (textDone) + return; -bool Dialogue::TextDone(void) -{ - return textDone; -} - -bool Dialogue::Dismissable(void) -{ - return dismissable; -} - -void Dialogue::SetDismissable(bool value) -{ - dismissable = value; -} - -void Dialogue::WriteText(void) -{ bool voicePlayed = false; textTimer -= textSpeed; @@ -206,6 +111,132 @@ void Dialogue::WriteText(void) textDone = (textTimer <= 0 && textDest.empty()); } +void Dialogue::Typewriter::CompleteText(void) +{ + while (!textDest.empty()) + { + text.push_back( textDest.back() ); + textDest.pop_back(); + } + + textTimer = 0; + textDone = true; +} + +// Dialogue + +void Dialogue::Init(void) +{ + active = true; +} + +void Dialogue::SetSpeaker(void) +{ + // Unset speaker + speaker.clear(); + + portrait = nullptr; + portraitColormap = nullptr; + + typewriter.voiceSfx = sfx_ktalk; +} + +void Dialogue::SetSpeaker(std::string skinName, int portraitID) +{ + Init(); + + // Set speaker based on a skin + int skinID = -1; + if (!skinName.empty()) + { + skinID = R_SkinAvailable(skinName.c_str()); + } + + if (skinID >= 0 && skinID < numskins) + { + const skin_t *skin = &skins[skinID]; + const spritedef_t *sprdef = &skin->sprites[SPR2_TALK]; + + if (sprdef->numframes > 0) + { + portraitID %= sprdef->numframes; + + const spriteframe_t *sprframe = &sprdef->spriteframes[portraitID]; + + portrait = static_cast( W_CachePatchNum(sprframe->lumppat[0], PU_CACHE) ); + portraitColormap = R_GetTranslationColormap(skinID, static_cast(skin->prefcolor), GTC_CACHE); + } + else + { + portrait = nullptr; + portraitColormap = nullptr; + } + + speaker = skin->realname; + + typewriter.voiceSfx = skin->soundsid[ S_sfx[sfx_ktalk].skinsound ]; + } + else + { + SetSpeaker(); + } +} + +void Dialogue::SetSpeaker(std::string name, patch_t *patch, UINT8 *colormap, sfxenum_t voice) +{ + Init(); + + // Set custom speaker + speaker = name; + + if (speaker.empty()) + { + portrait = nullptr; + portraitColormap = nullptr; + typewriter.voiceSfx = sfx_ktalk; + return; + } + + portrait = patch; + portraitColormap = colormap; + + typewriter.voiceSfx = voice; +} + +void Dialogue::NewText(std::string newText) +{ + Init(); + + newText = V_ScaledWordWrap( + 290 << FRACBITS, + FRACUNIT, FRACUNIT, FRACUNIT, + 0, HU_FONT, + newText.c_str() + ); + + typewriter.NewText(newText); +} + +bool Dialogue::Active(void) +{ + return active; +} + +bool Dialogue::TextDone(void) +{ + return typewriter.textDone; +} + +bool Dialogue::Dismissable(void) +{ + return dismissable; +} + +void Dialogue::SetDismissable(bool value) +{ + dismissable = value; +} + bool Dialogue::Held(void) { return ((players[serverplayer].cmd.buttons & BT_VOTE) == BT_VOTE); @@ -219,18 +250,6 @@ bool Dialogue::Pressed(void) ); } -void Dialogue::CompleteText(void) -{ - while (!textDest.empty()) - { - text.push_back( textDest.back() ); - textDest.pop_back(); - } - - textTimer = 0; - textDone = true; -} - void Dialogue::Tick(void) { if (Active()) @@ -260,7 +279,7 @@ void Dialogue::Tick(void) return; } - WriteText(); + typewriter.WriteText(); if (Dismissable() == true) { @@ -272,7 +291,7 @@ void Dialogue::Tick(void) } else { - CompleteText(); + typewriter.CompleteText(); } } } @@ -421,7 +440,7 @@ void Dialogue::Draw(void) drawer .xy(10 - BASEVIDWIDTH, -3-32) .font(srb2::Draw::Font::kConsole) - .text( text.c_str() ); + .text( typewriter.text.c_str() ); if (Dismissable()) { @@ -441,8 +460,7 @@ void Dialogue::Draw(void) void Dialogue::Dismiss(void) { active = false; - text.clear(); - textDest.clear(); + typewriter.ClearText(); } void Dialogue::Unset(void) diff --git a/src/k_dialogue.hpp b/src/k_dialogue.hpp index d29a6b475..1fa96de13 100644 --- a/src/k_dialogue.hpp +++ b/src/k_dialogue.hpp @@ -27,41 +27,7 @@ namespace srb2 class Dialogue { -private: - patch_t *bgPatch; - patch_t *confirmPatch; - - std::string speaker; - patch_t *portrait; - UINT8 *portraitColormap; - sfxenum_t voiceSfx; - - std::string text; - std::string textDest; - - bool active; - fixed_t slide; - - fixed_t textTimer; - fixed_t textSpeed; - bool textDone; - bool syllable; - - bool dismissable; - bool freeze; - - void Init(void); - //void Unset(void); - - void WriteText(void); - void CompleteText(void); - - bool Pressed(void); - bool Held(void); - public: - static constexpr fixed_t kTextSpeedDefault = FRACUNIT; - static constexpr fixed_t kTextPunctPause = (FRACUNIT * TICRATE * 2) / 5; static constexpr fixed_t kSlideSpeed = FRACUNIT / (TICRATE / 5); void SetSpeaker(void); @@ -82,6 +48,52 @@ public: void Dismiss(void); void Unset(void); + + class Typewriter + { + public: + static constexpr fixed_t kTextSpeedDefault = FRACUNIT; + static constexpr fixed_t kTextPunctPause = (FRACUNIT * TICRATE * 2) / 5; + + std::string text; + std::string textDest; + + fixed_t textTimer; + fixed_t textSpeed; + bool textDone; + + sfxenum_t voiceSfx; + bool syllable; + + void NewText(std::string newText); + void ClearText(void); + + void WriteText(void); + void CompleteText(void); + }; + +private: + Typewriter typewriter; + + patch_t *bgPatch; + patch_t *confirmPatch; + + std::string speaker; + patch_t *portrait; + UINT8 *portraitColormap; + + bool active; + fixed_t slide; + + bool dismissable; + bool freeze; + + void Init(void); + //void Unset(void); + + bool Pressed(void); + bool Held(void); + }; }; // namespace srb2 From ffb34204f53d6f2b45bc1188523e1195d04260dc Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 2 Dec 2023 23:40:02 +0000 Subject: [PATCH 20/64] Goner setup: cook - Build list of incoming text - First pass of very first section of dialogue - Use srb2::Dialogue::Typewriter class to handle ticking through dialogue - Store history log as incoming text is processed (possible todo: scrolling?) --- src/k_dialogue.cpp | 11 +- src/k_dialogue.hpp | 1 + src/menus/main-goner.cpp | 231 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 241 insertions(+), 2 deletions(-) diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index 6d2286289..43d772ad8 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -53,6 +53,7 @@ void Dialogue::Typewriter::NewText(std::string newText) textTimer = kTextPunctPause; textSpeed = kTextSpeedDefault; textDone = false; + textLines = 1; syllable = true; } @@ -79,6 +80,9 @@ void Dialogue::Typewriter::WriteText(void) continue; } + if (c == '\n') + textLines++; + if (!textDest.empty()) nextc = textDest.back(); @@ -115,7 +119,12 @@ void Dialogue::Typewriter::CompleteText(void) { while (!textDest.empty()) { - text.push_back( textDest.back() ); + char c = textDest.back(); + + if (c == '\n') + textLines++; + + text.push_back( c ); textDest.pop_back(); } diff --git a/src/k_dialogue.hpp b/src/k_dialogue.hpp index 1fa96de13..adce8fa84 100644 --- a/src/k_dialogue.hpp +++ b/src/k_dialogue.hpp @@ -61,6 +61,7 @@ public: fixed_t textTimer; fixed_t textSpeed; bool textDone; + int textLines; sfxenum_t voiceSfx; bool syllable; diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 37b5aa951..eee4605ba 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -2,6 +2,12 @@ /// \brief The Goner Setup. #include "../k_menu.h" +#include "../r_skins.h" +#include "../st_stuff.h" // faceprefix +#include "../v_draw.hpp" +#include "../k_dialogue.hpp" + +#include menuitem_t MAIN_Goner[] = { @@ -31,6 +37,7 @@ menuitem_t MAIN_Goner[] = }; static boolean M_GonerInputs(INT32 ch); +static void M_GonerDrawer(void); menu_t MAIN_GonerDef = { sizeof (MAIN_Goner) / sizeof (menuitem_t), @@ -42,15 +49,156 @@ menu_t MAIN_GonerDef = { MBF_UD_LR_FLIPPED, "_GONER", 0, 0, - M_DrawHorizontalMenu, + M_GonerDrawer, M_GonerTick, NULL, NULL, M_GonerInputs, }; +// --- + +typedef enum +{ + GONERSPEAKER_EGGMAN = 0, + GONERSPEAKER_TAILS, + MAXGONERSPEAKERS +} gonerspeakers_t; + +class GonerSpeaker +{ +public: + float offset; + + GonerSpeaker(std::string skinName, float offset) + { + if (!skinName.empty()) + { + this->skinID = R_SkinAvailable(skinName.c_str()); + } + + this->offset = offset; + }; + + sfxenum_t TalkSound(void) + { + if (!ValidID()) + return sfx_ktalk; + + return skins[ skinID ] + .soundsid[ S_sfx[sfx_ktalk].skinsound ]; + }; + + int GetSkinID(void) + { + if (!ValidID()) + return -1; + + return skinID; + }; + +private: + int skinID = -1; + bool ValidID(void) + { + return (skinID >= 0 && skinID < numskins); + }; +}; + +std::array, MAXGONERSPEAKERS> goner_speakers = {}; + +srb2::Dialogue::Typewriter goner_typewriter; + +int goner_delay; + +class GonerChatLine +{ +public: + gonerspeakers_t speaker; + std::string dialogue; + int value; // Mutlipurpose. + + GonerChatLine(gonerspeakers_t speaker, int delay, std::string dialogue) + { + this->speaker = speaker; + this->dialogue = V_ScaledWordWrap( + (BASEVIDWIDTH/2 + 12) << FRACBITS, + FRACUNIT, FRACUNIT, FRACUNIT, + 0, TINY_FONT, + dialogue.c_str() + ); + this->value = delay; + }; + + // Returns true if line is text + bool Handle(void) + { + if (speaker >= MAXGONERSPEAKERS) + return false; + + goner_typewriter.voiceSfx = sfx_ktalk; + if (goner_speakers[speaker]) + { + goner_typewriter.voiceSfx = (*goner_speakers[speaker]).TalkSound(); + } + + goner_typewriter.NewText(dialogue); + + goner_delay = value; + + value = 1; // this is now repurposed as the number of lines visible + + return true; + }; +}; + +std::forward_list LinesToDigest; +std::forward_list LinesOutput; + +// --- + void M_GonerTick(void) { + static bool speakersinit = false; + if (!speakersinit) + { + goner_delay = TICRATE; + + goner_speakers[GONERSPEAKER_EGGMAN] = GonerSpeaker("eggman", 0); + goner_speakers[GONERSPEAKER_TAILS] = GonerSpeaker("tails", 12); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE, + "Metal Sonic. Are you online?"); + LinesToDigest.emplace_front(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(GONERSPEAKER_TAILS, 0, + "I don't feel very safe!"); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/4, + "But its programming is definitely locked down..."); + + LinesToDigest.emplace_front(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, + "Wait, I'm getting weird readings over the network."); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Metal Sonic is the unit labeled \"MS1\", right?"); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, + "The ""\x87""viewport""\x80"" and ""\x87""audio""\x80"" config looks like it got messed up."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "So you're right. I wonder if it has anything to do with that outburst."); + LinesToDigest.emplace_front(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.reverse(); + + speakersinit = true; + } + if (menutyping.active == false && cv_dummyextraspassword.string[0] != '\0') { // Challenges are not interpreted at this stage. @@ -59,8 +207,89 @@ void M_GonerTick(void) cht_Interpret(cv_dummyextraspassword.string); CV_StealthSet(&cv_dummyextraspassword, ""); } + + goner_typewriter.WriteText(); + + if (goner_typewriter.textDone) + { + if (goner_delay > 0) + goner_delay--; + else if (!LinesToDigest.empty()) + { + if (!LinesOutput.empty()) + LinesOutput.front().value = goner_typewriter.textLines; + + auto line = LinesToDigest.front(); + if (line.Handle()) + LinesOutput.push_front(line); + LinesToDigest.pop_front(); + } + } } +static void M_GonerDrawer(void) +{ + srb2::Draw drawer = srb2::Draw(); + + drawer + .width(BASEVIDWIDTH) + .height(BASEVIDHEIGHT) + .fill(31); + + drawer = drawer.x(BASEVIDWIDTH/4); + + float newy = BASEVIDHEIGHT/2 + (3*12); + boolean first = true; + + for (auto & element : LinesOutput) + { + INT32 flags = V_TRANSLUCENT; + std::string text; + if (first) + { + text = goner_typewriter.text; + newy -= goner_typewriter.textLines*12; + flags = 0; + first = false; + } + else + { + text = element.dialogue; + newy -= element.value*12; + } + + if (newy < 0) break; + + //if (newy > BASEVIDHEIGHT) continue; -- not needed yet + + if (!goner_speakers[element.speaker]) + continue; + + auto speaker = *goner_speakers[element.speaker]; + + srb2::Draw line = drawer + .xy(speaker.offset, newy) + .flags(flags); + + int skinID = speaker.GetSkinID(); + if (skinID != -1) + { + line + .xy(-16, -2) + .colormap(skinID, static_cast(skins[skinID].prefcolor)) + .patch(faceprefix[skinID][FACE_MINIMAP]); + } + + line + .font(srb2::Draw::Font::kThin) + .text( text.c_str() ); + } + + M_DrawHorizontalMenu(); +} + +// --- + void M_GonerProfile(INT32 choice) { (void)choice; From 40566274d9a186682c3d4ff31b84e522d78cbd09 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 3 Dec 2023 13:01:33 +0000 Subject: [PATCH 21/64] Jart DM assistance - anonymous namespace for filescope variables, funcs - Step away from std::optional, use anonymous initialiser instead --- src/menus/main-goner.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index eee4605ba..cd2c201ef 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -56,7 +56,8 @@ menu_t MAIN_GonerDef = { M_GonerInputs, }; -// --- +namespace +{ typedef enum { @@ -68,7 +69,7 @@ typedef enum class GonerSpeaker { public: - float offset; + float offset = 0; GonerSpeaker(std::string skinName, float offset) { @@ -80,6 +81,8 @@ public: this->offset = offset; }; + GonerSpeaker() {}; + sfxenum_t TalkSound(void) { if (!ValidID()) @@ -105,7 +108,7 @@ private: }; }; -std::array, MAXGONERSPEAKERS> goner_speakers = {}; +std::array goner_speakers = {}; srb2::Dialogue::Typewriter goner_typewriter; @@ -136,11 +139,7 @@ public: if (speaker >= MAXGONERSPEAKERS) return false; - goner_typewriter.voiceSfx = sfx_ktalk; - if (goner_speakers[speaker]) - { - goner_typewriter.voiceSfx = (*goner_speakers[speaker]).TalkSound(); - } + goner_typewriter.voiceSfx = goner_speakers[speaker].TalkSound(); goner_typewriter.NewText(dialogue); @@ -155,7 +154,7 @@ public: std::forward_list LinesToDigest; std::forward_list LinesOutput; -// --- +}; // namespace void M_GonerTick(void) { @@ -262,10 +261,7 @@ static void M_GonerDrawer(void) //if (newy > BASEVIDHEIGHT) continue; -- not needed yet - if (!goner_speakers[element.speaker]) - continue; - - auto speaker = *goner_speakers[element.speaker]; + auto speaker = goner_speakers[element.speaker]; srb2::Draw line = drawer .xy(speaker.offset, newy) From 7fba9321d182651e1b0fe61353889e68c66dc06f Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 3 Dec 2023 21:51:49 +0000 Subject: [PATCH 22/64] M_HandleProfileEdit: Tidy --- src/menus/options-profiles-edit-1.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/menus/options-profiles-edit-1.c b/src/menus/options-profiles-edit-1.c index 04ee24990..d81e82212 100644 --- a/src/menus/options-profiles-edit-1.c +++ b/src/menus/options-profiles-edit-1.c @@ -125,10 +125,10 @@ boolean M_ProfileEditInputs(INT32 ch) void M_HandleProfileEdit(void) { // Always copy the profile name and player name in the profile. - if (optionsmenu.profile) + if (optionsmenu.profile && !menutyping.active) { // Copy the first 6 chars for profile name - if (strlen(cv_dummyprofilename.string)) + if (cv_dummyprofilename.string[0]) { char *s; // convert dummyprofilename to uppercase @@ -141,8 +141,10 @@ void M_HandleProfileEdit(void) } } - if (strlen(cv_dummyprofileplayername.string)) + if (cv_dummyprofileplayername.string[0]) + { strncpy(optionsmenu.profile->playername, cv_dummyprofileplayername.string, MAXPLAYERNAME); + } } M_OptionsTick(); // Has to be afterwards because this can unset optionsmenu.profile From f5566ff8c4a0dd5b94f4ffd350f44fb545451279 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 3 Dec 2023 21:55:54 +0000 Subject: [PATCH 23/64] Typewriter::WriteText: If dialogue ends with a hyphen, cut down the time to textDone massively --- src/k_dialogue.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index 43d772ad8..c95dc8d23 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -64,15 +64,17 @@ void Dialogue::Typewriter::WriteText(void) return; bool voicePlayed = false; + bool empty = textDest.empty(); textTimer -= textSpeed; - while (textTimer <= 0 && !textDest.empty()) + while (textTimer <= 0 && !empty) { char c = textDest.back(), nextc = '\n'; text.push_back(c); textDest.pop_back(); + empty = textDest.empty(); if (c & 0x80) { @@ -83,7 +85,7 @@ void Dialogue::Typewriter::WriteText(void) if (c == '\n') textLines++; - if (!textDest.empty()) + if (!empty) nextc = textDest.back(); if (voicePlayed == false @@ -100,7 +102,11 @@ void Dialogue::Typewriter::WriteText(void) voicePlayed = true; } - if (std::ispunct(c) + if (c == '-' && empty) + { + textTimer += textSpeed; + } + else if (std::ispunct(c) && std::isspace(nextc)) { // slow down for punctuation @@ -112,7 +118,7 @@ void Dialogue::Typewriter::WriteText(void) } } - textDone = (textTimer <= 0 && textDest.empty()); + textDone = (textTimer <= 0 && empty); } void Dialogue::Typewriter::CompleteText(void) From e91115a8fae93f4fe92a967f2562c23f41dc3984 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 3 Dec 2023 22:31:45 +0000 Subject: [PATCH 24/64] Goner Setup: Cook 2 - More dialogue - Restrict access to your choices based on your progress through the Goner Setup. - Save progress through the setup to gamedata - Resets to beginning when Challenges is cleared - Passwords - Only permit the password entry in like the first two seconds - Preemptively disarms potentially annoying people in stream chats - Make wrongwarp also quit the game on exit instead of returning to the title screen - Add GDQ cheats - `savetheanimals` - `savetheframes` - Both do practically the same thing, with slightly modified dialogue. - Skips all of the normal dialogue. - Performing a social experiment on anyone who might stream this game at a charity event. :) --- src/g_game.c | 6 +- src/k_menu.h | 3 + src/k_menudraw.c | 8 +- src/k_menufunc.c | 4 +- src/m_cheat.c | 26 +++ src/m_cond.c | 2 + src/m_cond.h | 12 ++ src/menus/main-goner.cpp | 434 ++++++++++++++++++++++++++++++++------- src/menus/options-1.c | 4 + 9 files changed, 423 insertions(+), 76 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index c22bd031e..36b7a56e6 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4553,6 +4553,8 @@ typedef enum GDEVER_KEYMAJORSKIP = 1<<5, GDEVER_TUTORIALSKIP = 1<<6, GDEVER_ENTERTUTSKIP = 1<<7, + // --- Free up to 1<<23 --- // + GDEVER_GONERSHIFT = 24, // nothing above this } gdeverdone_t; static const char *G_GameDataFolder(void) @@ -4694,6 +4696,8 @@ void G_LoadGameData(void) gamedata->majorkeyskipattempted = !!(everflags & GDEVER_KEYMAJORSKIP); gamedata->finishedtutorialchallenge = !!(everflags & GDEVER_TUTORIALSKIP); gamedata->enteredtutorialchallenge = !!(everflags & GDEVER_ENTERTUTSKIP); + + gamedata->gonerlevel = everflags>>GDEVER_GONERSHIFT; } else { @@ -5371,7 +5375,7 @@ void G_SaveGameData(void) WRITEUINT16(save.p, gamedata->chaokeys); // 2 { - UINT32 everflags = 0; + UINT32 everflags = (gamedata->gonerlevel<everloadedaddon) everflags |= GDEVER_ADDON; diff --git a/src/k_menu.h b/src/k_menu.h index f5e414930..09fc6807f 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -206,6 +206,9 @@ extern menu_t MAIN_GonerDef; void M_GonerTick(void); void M_GonerProfile(INT32 choice); void M_GonerTutorial(INT32 choice); +void M_GonerResetLooking(int type); +void M_GonerCheckLooking(void); +void M_GonerGDQ(boolean opinion); extern menuitem_t PLAY_CharSelect[]; extern menu_t PLAY_CharSelectDef; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 558e016c3..95c3a0b3c 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1156,8 +1156,8 @@ void M_DrawHorizontalMenu(void) V_DrawCharacter((BASEVIDWIDTH + width)/2 - 10 + (skullAnimCounter/5), y, '\x1D' | highlightflags, false); // right arrow - x = (BASEVIDWIDTH - 8*(currentMenu->numitems-1))/2; - for (i = 0; i < currentMenu->numitems; i++, x += 8) + x = (BASEVIDWIDTH - 8*(currentMenu->extra2-1))/2; + for (i = 0; i < currentMenu->extra2; i++, x += 8) { if (i == itemOn) { @@ -1165,7 +1165,9 @@ void M_DrawHorizontalMenu(void) } else { - V_DrawFill(x-1, y + 16, 2, 2, 16); + V_DrawFill(x-1, y + 16, 2, 2, + (i >= currentMenu->numitems) ? 20 : 10 + ); } } } diff --git a/src/k_menufunc.c b/src/k_menufunc.c index ef6863594..c6ebeb401 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -741,8 +741,10 @@ void M_GoBack(INT32 choice) M_SetupNextMenu(currentMenu->prevMenu, false); } - else + else if (M_GameTrulyStarted()) M_ClearMenus(true); + else // No returning to the title screen. + M_QuitSRB2(-1); S_StartSound(NULL, sfx_s3k5b); } diff --git a/src/m_cheat.c b/src/m_cheat.c index 39ccfd16b..175acc7e5 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -134,6 +134,20 @@ static UINT8 cheatf_wrongwarp(void) return 1; } +static UINT8 cheatf_savetheanimals(void) +{ + M_GonerGDQ(true); + + return 1; +} + +static UINT8 cheatf_savetheframes(void) +{ + M_GonerGDQ(false); + + return 1; +} + #ifdef DEVELOP static UINT8 cheatf_devmode(void) { @@ -179,6 +193,16 @@ static cheatseq_t cheat_wrongwarp = { (UINT8[]){ SCRAMBLE('b'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('a'), 0xff } }; +static cheatseq_t cheat_savetheanimals = { + NULL, cheatf_savetheanimals, + (UINT8[]){ SCRAMBLE('s'), SCRAMBLE('a'), SCRAMBLE('v'), SCRAMBLE('e'), SCRAMBLE('t'), SCRAMBLE('h'), SCRAMBLE('e'), SCRAMBLE('a'), SCRAMBLE('n'), SCRAMBLE('i'), SCRAMBLE('m'), SCRAMBLE('a'), SCRAMBLE('l'), SCRAMBLE('s'), 0xff } +}; + +static cheatseq_t cheat_savetheframes = { + NULL, cheatf_savetheframes, + (UINT8[]){ SCRAMBLE('s'), SCRAMBLE('a'), SCRAMBLE('v'), SCRAMBLE('e'), SCRAMBLE('t'), SCRAMBLE('h'), SCRAMBLE('e'), SCRAMBLE('f'), SCRAMBLE('r'), SCRAMBLE('a'), SCRAMBLE('m'), SCRAMBLE('e'), SCRAMBLE('s'), 0xff } +}; + #ifdef DEVELOP static cheatseq_t cheat_devmode = { NULL, cheatf_devmode, @@ -190,6 +214,8 @@ cheatseq_t *cheatseqlist[] = { &cheat_warp, &cheat_wrongwarp, + &cheat_savetheanimals, + &cheat_savetheframes, #ifdef DEVELOP &cheat_devmode, #endif diff --git a/src/m_cond.c b/src/m_cond.c index b4f5a0ba6..353f19bc0 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -730,6 +730,8 @@ void M_ClearSecrets(void) gamedata->chaokeys = GDINIT_CHAOKEYS; gamedata->prisoneggstothispickup = GDINIT_PRISONSTOPRIZE; + + gamedata->gonerlevel = GDGONER_INIT; } // For lack of a better idea on where to put this diff --git a/src/m_cond.h b/src/m_cond.h index 87c38060e..4bccce9b1 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -289,6 +289,16 @@ typedef enum { #define GDINIT_CHAOKEYS 3 // Start with 3 Chao Keys !! #define GDINIT_PRISONSTOPRIZE 30 // 30 Prison Eggs to your [Wild Prize] !! +typedef enum { + GDGONER_INIT = 0, + GDGONER_INTRO, + GDGONER_VIDEO, + GDGONER_SOUND, + GDGONER_PROFILE, + GDGONER_TUTORIAL, + GDGONER_DONE, +} gdgoner_t; + typedef enum { GDGT_RACE, GDGT_BATTLE, @@ -370,6 +380,8 @@ struct gamedata_t boolean finishedtutorialchallenge; gdmusic_t musicstate; + UINT8 gonerlevel; + // BACKWARDS COMPAT ASSIST boolean importprofilewins; }; diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index cd2c201ef..b0ab6a38a 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -2,7 +2,10 @@ /// \brief The Goner Setup. #include "../k_menu.h" +#include "../m_cond.h" #include "../r_skins.h" +#include "../s_sound.h" +#include "../p_local.h" // P_AutoPause #include "../st_stuff.h" // faceprefix #include "../v_draw.hpp" #include "../k_dialogue.hpp" @@ -11,41 +14,36 @@ menuitem_t MAIN_Goner[] = { - {IT_STRING | IT_CVAR | IT_CV_STRING, "Password", + {IT_STRING | IT_CVAR | IT_CV_STRING, ". . .", "ATTEMPT ADMINISTRATOR ACCESS.", NULL, {.cvar = &cv_dummyextraspassword}, 0, 0}, - {IT_STRING | IT_CALL, "Quit", - "CONCLUDE OBSERVATIONS NOW.", NULL, - {.routine = M_QuitSRB2}, 0, 0}, - - {IT_STRING | IT_CALL, "Video Options", + {IT_STRING | IT_CALL, "VIDEO OPTIONS", "CONFIGURE OCULAR PATHWAYS.", NULL, {.routine = M_VideoOptions}, 0, 0}, - {IT_STRING | IT_CALL, "Sound Options", + {IT_STRING | IT_CALL, "SOUND OPTIONS", "CALIBRATE AURAL DATASTREAM.", NULL, {.routine = M_SoundOptions}, 0, 0}, - {IT_STRING | IT_CALL, "Profile Setup", + {IT_STRING | IT_CALL, "PROFILE SETUP", "ASSIGN VEHICLE INPUTS.", NULL, {.routine = M_GonerProfile}, 0, 0}, - {IT_STRING | IT_CALL, "Begin Tutorial", + {IT_STRING | IT_CALL, "BEGIN TUTORIAL", "PREPARE FOR INTEGRATION.", NULL, {.routine = M_GonerTutorial}, 0, 0}, }; -static boolean M_GonerInputs(INT32 ch); static void M_GonerDrawer(void); menu_t MAIN_GonerDef = { - sizeof (MAIN_Goner) / sizeof (menuitem_t), + 1, // Intentionally not the sizeof calc NULL, 0, MAIN_Goner, - 32, 160, - 0, 0, + 26, 160, + 0, sizeof (MAIN_Goner) / sizeof (menuitem_t), // extra2 is final width MBF_UD_LR_FLIPPED, "_GONER", 0, 0, @@ -53,7 +51,7 @@ menu_t MAIN_GonerDef = { M_GonerTick, NULL, NULL, - M_GonerInputs, + NULL, }; namespace @@ -154,63 +152,310 @@ public: std::forward_list LinesToDigest; std::forward_list LinesOutput; +int goner_levelworking = GDGONER_INIT; +bool goner_nicetry = false; +bool goner_gdq = false; + +void M_AddGonerLines(void) +{ + SRB2_ASSERT(LinesToDigest.empty()); + + auto _ = srb2::finally([]() { LinesToDigest.reverse(); }); + + static bool leftoff = false; + + goner_delay = TICRATE; + + // This one always plays, so it checks the levelworking instead of gamedata. + if (goner_levelworking == GDGONER_INTRO) + { + if (!goner_nicetry) + { + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "Metal Sonic. Are you online?"); + } + + leftoff = (goner_levelworking < gamedata->gonerlevel-1); + + if (leftoff) + { + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "It must have run into some sort of error..."); + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "Don't worry, your settings so far are saved. "\ + "Let's pick up where we left off."); + + // the -1 guarantees that one will be (re)played + goner_levelworking = gamedata->gonerlevel-1; + } + + return; + } + + switch (gamedata->gonerlevel) + { + case GDGONER_VIDEO: + { + LinesToDigest.emplace_front(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(GONERSPEAKER_TAILS, 0, + "I don't feel very safe!"); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/4, + "But its programming is definitely locked down..."); + + LinesToDigest.emplace_front(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, + "Wait, I'm getting weird readings over the network."); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Metal Sonic is the unit labeled \"MS-1\", right?"); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, + "The ""\x87""viewport""\x80"" and ""\x87""audio""\x80"" "\ + "config looks like it got messed up."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "So you're right. I wonder if it has anything to do with that outburst."); + LinesToDigest.emplace_front(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."); + break; + } + case GDGONER_SOUND: + { + if (!leftoff) + { + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "Ah, you can see us now. Good."); + } + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "Now, calibrate your ""\x87""Sound Options""\x80""."); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "You always make your stuff so loud by default, Eggman. It might need a moment."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "Not Metal! He always needed to be stealthy. But go on, set your sliders."); + break; + } + case GDGONER_PROFILE: + { + if (!leftoff) + { + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + "Oh! Let's tell Metal about our project!"); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "Of course. I and my lab assista-"); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Lab PARTNER."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "Irrelevant!"); + } + LinesToDigest.emplace_front(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/2, + "At its core, it is designed to utilise the boundless potential "\ + "of the ""\x83""High Voltage Ring""\x80""."); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, + "We made this special Ring by combining the power of tens of "\ + "thousands of ordinary ""\x82""Rings""\x80""."); + LinesToDigest.emplace_front(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(GONERSPEAKER_EGGMAN, TICRATE/4, + "While that's uploading, why don't you set up your ""\x87""Profile Card""\x80""?"); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Yes! That's one of my contributions."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "(I'm too used to my systems being designed for me alone...)"); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Every racer carries one, to contain their personal settings."); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "It helps get your ""\x87""controls""\x80"" set up nice and quickly, "\ + "when starting your vehicle."); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "And it helps track your wins, too."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + "Bragging rights. My idea!"); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + "You can make the name on there anything you want."); + LinesToDigest.emplace_front(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 have to become stronger "\ + "with robotics, and kind of mean and cool to protect himself..."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + "Mine says \"Robotnik\". You can't beat a classic."); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Go on, do your ""\x87""Profile Setup""\x80""!"); + break; + } + case GDGONER_TUTORIAL: + case GDGONER_DONE: // maybe we could do something different for this eventually + { + if (!leftoff) + { + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, + "...right, now that that's sorted. How's the upload going?"); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Just finished."); + + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "Perfect."); + } + + 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""!"); + + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "That's a lot to get through. Good luck, MS-1!"); + + break; + } + default: + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "I am error"); + } + + leftoff = false; +} + +gdgoner_t goner_lasttypelooking = GDGONER_INIT; +tic_t goner_youactuallylooked = 0; + +void M_GonerRailroad(bool set) +{ + INT16 destsize = std::min( + static_cast( + (set ? 2 : 1) // Quit + options + maybe 1 for extra access + + std::max(0, gamedata->gonerlevel - GDGONER_VIDEO) + ), + currentMenu->extra2); + currentMenu->numitems = destsize; + + if (!set) + return; + + itemOn = destsize-1; + S_StartSound(NULL, sfx_s3k5b); +} + +void M_GonerHidePassword(void) +{ + if (currentMenu->menuitems[0].mvar2) + return; + + currentMenu->menuitems[0] = + {IT_STRING | IT_CALL, "EXIT PROGRAM", + "CONCLUDE OBSERVATIONS NOW.", NULL, + {.routine = M_QuitSRB2}, 0, 1}; +} + }; // namespace +void M_GonerResetLooking(int type) +{ + if (goner_youactuallylooked > 2*TICRATE + && goner_lasttypelooking == gamedata->gonerlevel) + { + if (goner_levelworking > gamedata->gonerlevel) + goner_levelworking--; + gamedata->gonerlevel++; + LinesToDigest.clear(); + } + + goner_lasttypelooking = static_cast(type); + goner_youactuallylooked = 0; +} + +void M_GonerCheckLooking(void) +{ + if (goner_lasttypelooking != gamedata->gonerlevel) + return; + goner_youactuallylooked++; +} + void M_GonerTick(void) { - static bool speakersinit = false; - if (!speakersinit) - { - goner_delay = TICRATE; + static bool first = true; + if (goner_levelworking == GDGONER_INIT) + { + first = true; + + // Init. goner_speakers[GONERSPEAKER_EGGMAN] = GonerSpeaker("eggman", 0); goner_speakers[GONERSPEAKER_TAILS] = GonerSpeaker("tails", 12); + } + else if (gamedata->gonerlevel == GDGONER_INIT) + { + first = true; // a lie, but only slightly... - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE, - "Metal Sonic. Are you online?"); - LinesToDigest.emplace_front(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."); + // Handle rewinding if you clear your gamedata. + goner_typewriter.ClearText(); + LinesToDigest.clear(); + LinesOutput.clear(); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, - "I don't feel very safe!"); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/4, - "But its programming is definitely locked down..."); - - LinesToDigest.emplace_front(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, - "Wait, I'm getting weird readings over the network."); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, - "Metal Sonic is the unit labeled \"MS1\", right?"); - LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, - "The ""\x87""viewport""\x80"" and ""\x87""audio""\x80"" config looks like it got messed up."); - - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, - "So you're right. I wonder if it has anything to do with that outburst."); - LinesToDigest.emplace_front(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.reverse(); - - speakersinit = true; + goner_levelworking = GDGONER_INIT; } - if (menutyping.active == false && cv_dummyextraspassword.string[0] != '\0') - { - // Challenges are not interpreted at this stage. - // See M_ExtraTick for the full behaviour. + M_GonerResetLooking(GDGONER_INIT); - cht_Interpret(cv_dummyextraspassword.string); - CV_StealthSet(&cv_dummyextraspassword, ""); + if (first) + { + first = false; + + if (gamedata->gonerlevel < GDGONER_INTRO) + gamedata->gonerlevel = GDGONER_INTRO; + + M_GonerRailroad(false); } goner_typewriter.WriteText(); + if (menutyping.active || menumessage.active || P_AutoPause()) + return; + + if (cv_dummyextraspassword.string[0] != '\0') + { + // Challenges are not interpreted at this stage. + // See M_ExtraTick for the full behaviour. + + if (!cht_Interpret(cv_dummyextraspassword.string)) + { + goner_delay = 0; + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE, + "Aha! Nice try. You're tricky enough WITHOUT admin access, thank you."); + M_GonerHidePassword(); + goner_nicetry = true; + } + + CV_StealthSet(&cv_dummyextraspassword, ""); + } + if (goner_typewriter.textDone) { + if (!LinesOutput.empty()) + M_GonerHidePassword(); if (goner_delay > 0) goner_delay--; else if (!LinesToDigest.empty()) @@ -223,6 +468,21 @@ void M_GonerTick(void) LinesOutput.push_front(line); LinesToDigest.pop_front(); } + else if (goner_levelworking <= gamedata->gonerlevel) + { + if (goner_levelworking == GDGONER_INTRO && gamedata->gonerlevel < GDGONER_VIDEO) + gamedata->gonerlevel = GDGONER_VIDEO; + + if (++goner_levelworking > gamedata->gonerlevel) + { + // We've reached the end of the goner text for now. + M_GonerRailroad(true); + } + else if (!goner_gdq) + { + M_AddGonerLines(); + } + } } } @@ -240,10 +500,15 @@ static void M_GonerDrawer(void) float newy = BASEVIDHEIGHT/2 + (3*12); boolean first = true; + int lastspeaker = MAXGONERSPEAKERS; + for (auto & element : LinesOutput) { INT32 flags = V_TRANSLUCENT; std::string text; + + if (newy < 0) break; + if (first) { text = goner_typewriter.text; @@ -255,9 +520,12 @@ static void M_GonerDrawer(void) { text = element.dialogue; newy -= element.value*12; + + if (lastspeaker != element.speaker) + newy -= 2; } - if (newy < 0) break; + lastspeaker = element.speaker; //if (newy > BASEVIDHEIGHT) continue; -- not needed yet @@ -267,18 +535,21 @@ static void M_GonerDrawer(void) .xy(speaker.offset, newy) .flags(flags); - int skinID = speaker.GetSkinID(); - if (skinID != -1) - { - line - .xy(-16, -2) - .colormap(skinID, static_cast(skins[skinID].prefcolor)) - .patch(faceprefix[skinID][FACE_MINIMAP]); - } - line .font(srb2::Draw::Font::kThin) .text( text.c_str() ); + + int skinID = speaker.GetSkinID(); + if (skinID != -1) + { + line = line + .xy(-16, -2) + .colormap(skinID, static_cast(skins[skinID].prefcolor)); + if (gamedata->gonerlevel > GDGONER_VIDEO) + line.patch(faceprefix[skinID][FACE_MINIMAP]); + else + line.patch("HUHMAP"); + } } M_DrawHorizontalMenu(); @@ -307,6 +578,8 @@ void M_GonerProfile(INT32 choice) // This will create a new profile if necessary. M_StartEditProfile(MA_YES); PR_ApplyProfilePretend(optionsmenu.profilen, 0); + + M_GonerResetLooking(GDGONER_PROFILE); } void M_GonerTutorial(INT32 choice) @@ -338,19 +611,38 @@ void M_GonerTutorial(INT32 choice) "YOU ACCEPT EVERYTHING THAT WILL HAPPEN FROM NOW ON.", &M_QuitResponse, MM_YESNO, "I agree", "Cancel"); } + + gamedata->gonerlevel = GDGONER_DONE; } -static boolean M_GonerInputs(INT32 ch) +void M_GonerGDQ(boolean opinion) { - const UINT8 pid = 0; - (void)ch; + if (currentMenu != &MAIN_GonerDef) + return; - if (M_MenuBackPressed(pid)) + LinesToDigest.clear(); + goner_delay = TICRATE/2; + goner_gdq = true; + + // The mapping of true/false to each opinion is completely + // arbitrary, and is not a comment on the nature of the Metroid run. + + if (opinion) // Save The Animals { - // No returning to the title screen. - M_QuitSRB2(-1); - return true; + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, + "Why wouldn't you save the frames..?"); + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, + "Don't mind him. Good luck on the run!"); } + else // Save The Frames + { + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + "But what about all the little animals..."); + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, + "It's just logical. I know you'll conquer this run."); + } + LinesToDigest.reverse(); - return false; + goner_levelworking = GDGONER_TUTORIAL; + gamedata->gonerlevel = GDGONER_DONE; } diff --git a/src/menus/options-1.c b/src/menus/options-1.c index 9f43cc6aa..faba2786f 100644 --- a/src/menus/options-1.c +++ b/src/menus/options-1.c @@ -197,6 +197,8 @@ void M_OptionsTick(void) optionsmenu.fade--; if (optionsmenu.currcolour != currentMenu->extra1) M_OptionsChangeBGColour(currentMenu->extra1); + + M_GonerCheckLooking(); } // And one last giggle... @@ -213,12 +215,14 @@ void M_VideoOptions(INT32 choice) { (void)choice; M_OptionsMenuGoto(&OPTIONS_VideoDef); + M_GonerResetLooking(GDGONER_VIDEO); } void M_SoundOptions(INT32 choice) { (void)choice; M_OptionsMenuGoto(&OPTIONS_SoundDef); + M_GonerResetLooking(GDGONER_SOUND); } boolean M_OptionsInputs(INT32 ch) From 36c3dbfc0e950be31dc7c1f85764c3ce4184b48b Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 4 Dec 2023 12:09:22 +0000 Subject: [PATCH 25/64] Goner setup polish - Checkpoint sound for reaching a new option - Fix header text for Video and Sound menus - Reset password field access when menu is reset by gamedata wipe --- src/menus/main-goner.cpp | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index b0ab6a38a..ddccfbed4 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -14,9 +14,7 @@ menuitem_t MAIN_Goner[] = { - {IT_STRING | IT_CVAR | IT_CV_STRING, ". . .", - "ATTEMPT ADMINISTRATOR ACCESS.", NULL, - {.cvar = &cv_dummyextraspassword}, 0, 0}, + {IT_STRING | IT_CALL, NULL, NULL, NULL, {.routine = M_QuitSRB2}, 0, 0}, // will be replaced {IT_STRING | IT_CALL, "VIDEO OPTIONS", "CONFIGURE OCULAR PATHWAYS.", NULL, @@ -153,7 +151,6 @@ std::forward_list LinesToDigest; std::forward_list LinesOutput; int goner_levelworking = GDGONER_INIT; -bool goner_nicetry = false; bool goner_gdq = false; void M_AddGonerLines(void) @@ -169,7 +166,7 @@ void M_AddGonerLines(void) // This one always plays, so it checks the levelworking instead of gamedata. if (goner_levelworking == GDGONER_INTRO) { - if (!goner_nicetry) + if (!currentMenu->menuitems[0].mvar2) { LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Metal Sonic. Are you online?"); @@ -261,12 +258,12 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(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/2, + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE, "At its core, it is designed to utilise the boundless potential "\ "of the ""\x83""High Voltage Ring""\x80""."); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE, - "We made this special Ring by combining the power of tens of "\ + "We made this special ""\x83""Ring""\x80"" by combining the power of tens of "\ "thousands of ordinary ""\x82""Rings""\x80""."); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, "We recorded some of our testing for you, MS-1. Maybe your neural "\ @@ -293,11 +290,11 @@ void M_AddGonerLines(void) "Bragging rights. My idea!"); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, - "You can make the name on there anything you want."); + "You can make the ID and player tag on there anything you want."); LinesToDigest.emplace_front(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 have to become stronger "\ - "with robotics, and kind of mean and cool to protect himself..."); + "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, "Mine says \"Robotnik\". You can't beat a classic."); @@ -356,7 +353,7 @@ void M_GonerRailroad(bool set) return; itemOn = destsize-1; - S_StartSound(NULL, sfx_s3k5b); + S_StartSound(NULL, sfx_s3k63); } void M_GonerHidePassword(void) @@ -368,6 +365,8 @@ void M_GonerHidePassword(void) {IT_STRING | IT_CALL, "EXIT PROGRAM", "CONCLUDE OBSERVATIONS NOW.", NULL, {.routine = M_QuitSRB2}, 0, 1}; + + S_StartSound(NULL, sfx_s3k5b); } }; // namespace @@ -385,6 +384,12 @@ void M_GonerResetLooking(int type) goner_lasttypelooking = static_cast(type); goner_youactuallylooked = 0; + if (type == GDGONER_VIDEO) + OPTIONS_MainDef.lastOn = mopt_video; + else if (type == GDGONER_SOUND) + OPTIONS_MainDef.lastOn = mopt_sound; + else + OPTIONS_MainDef.lastOn = mopt_profiles; } void M_GonerCheckLooking(void) @@ -422,7 +427,12 @@ void M_GonerTick(void) if (first) { - first = false; + first = goner_gdq = false; + + currentMenu->menuitems[0] = + {IT_STRING | IT_CVAR | IT_CV_STRING, ". . .", + "ATTEMPT ADMINISTRATOR ACCESS.", NULL, + {.cvar = &cv_dummyextraspassword}, 0, 0}; if (gamedata->gonerlevel < GDGONER_INTRO) gamedata->gonerlevel = GDGONER_INTRO; @@ -446,7 +456,6 @@ void M_GonerTick(void) LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE, "Aha! Nice try. You're tricky enough WITHOUT admin access, thank you."); M_GonerHidePassword(); - goner_nicetry = true; } CV_StealthSet(&cv_dummyextraspassword, ""); @@ -612,7 +621,7 @@ void M_GonerTutorial(INT32 choice) &M_QuitResponse, MM_YESNO, "I agree", "Cancel"); } - gamedata->gonerlevel = GDGONER_DONE; + goner_levelworking = gamedata->gonerlevel = GDGONER_DONE; } void M_GonerGDQ(boolean opinion) From 17c0ceba23fa9c72935136c27ba9e5a543846242 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 4 Dec 2023 13:27:47 +0000 Subject: [PATCH 26/64] Profile edit polish - Rename the Name fields to "Profile ID" and "Player Tag" respectively, for clarity - Re-order and re-align, so the given area on the Profile Card is lined up for readability - Use the smaller version of the gamemode font, to have the screen less crammed full --- src/k_menu.h | 4 +-- src/k_menudraw.c | 47 +++++++++++++++-------------- src/menus/options-profiles-edit-1.c | 22 ++++++++------ 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 09fc6807f..a663c4a2f 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -332,9 +332,9 @@ extern menu_t MAIN_ProfilesDef; typedef enum { popt_profilename = 0, - popt_profilepname, - popt_char, popt_controls, + popt_char, + popt_profilepname, popt_confirm, } popt_e; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 95c3a0b3c..dea01d1d0 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4026,7 +4026,7 @@ void M_DrawEditProfile(void) { INT32 y = 34; - INT32 x = 145; + INT32 x = (145 + (menutransition.tics*32)); INT32 i; M_DrawOptionsCogs(); @@ -4046,33 +4046,34 @@ void M_DrawEditProfile(void) UINT8 *colormap = NULL; INT32 tflag = (currentMenu->menuitems[i].status & IT_TRANSTEXT) ? V_TRANSLUCENT : 0; + y = currentMenu->menuitems[i].mvar2; + + // Background -- 169 is the plague colourization + V_DrawFill(0, y, 400 - (menutransition.tics*64), 10, itemOn == i ? 169 : 30); + if (i == itemOn) + { colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); - // Background - V_DrawFill(0, y, 400 - (menutransition.tics*64), 24, itemOn == i ? 169 : 30); // 169 is the plague colourization + V_DrawCharacter(x - 10 - (skullAnimCounter/5), y+1, + '\x1C' | highlightflags, false); // left arrow + } + // Text - V_DrawGamemodeString(x + (menutransition.tics*32), y - 6, tflag, colormap, currentMenu->menuitems[i].text); + //V_DrawGamemodeString(x, y - 6, tflag, colormap, currentMenu->menuitems[i].text); + V_DrawStringScaled( + x * FRACUNIT, + (y - 3) * FRACUNIT, + FRACUNIT, + FRACUNIT, + FRACUNIT, + tflag, + colormap, + KART_FONT, + currentMenu->menuitems[i].text + ); - // Cvar specific handling - /*switch (currentMenu->menuitems[i].status & IT_TYPE) - { - case IT_CVAR: - { - consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; - switch (currentMenu->menuitems[i].status & IT_CVARTYPE) - { - case IT_CV_STRING: - V_DrawFill(0, y+24, 400 - (menutransition.tics*64), 16, itemOn == i ? 169 : 30); // 169 is the plague colourization - V_DrawString(x + 8, y + 29, 0, cv->string); - if (skullAnimCounter < 4 && i == itemOn) - V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 29, '_', false); - y += 16; - } - } - }*/ - - y += 34; + //y += 32 + 2; } // Finally, draw the card ontop diff --git a/src/menus/options-profiles-edit-1.c b/src/menus/options-profiles-edit-1.c index d81e82212..edb34eead 100644 --- a/src/menus/options-profiles-edit-1.c +++ b/src/menus/options-profiles-edit-1.c @@ -5,21 +5,23 @@ #include "../s_sound.h" #include "../m_cond.h" +// These are placed in descending order next to the things they modify, for clarity. +// Try to keep the mvar2 in order, if you add new profile info!! menuitem_t OPTIONS_EditProfile[] = { - {IT_STRING | IT_CVAR | IT_CV_STRING, "Profile Name", "6-character long name to identify this Profile.", - NULL, {.cvar = &cv_dummyprofilename}, 0, 0}, - - {IT_STRING | IT_CVAR | IT_CV_STRING, "Player Name", "Name displayed online when using this Profile.", - NULL, {.cvar = &cv_dummyprofileplayername}, 0, 0}, - - {IT_STRING | IT_CALL, "Character", "Default character and color for this Profile.", - NULL, {.routine = M_CharacterSelect}, 0, 0}, + {IT_STRING | IT_CVAR | IT_CV_STRING, "Profile ID", "6-character long name to identify this Profile.", + NULL, {.cvar = &cv_dummyprofilename}, 0, 41}, {IT_STRING | IT_CALL, "Controls", "Select the button mappings for this Profile.", - NULL, {.routine = M_ProfileDeviceSelect}, 0, 0}, + NULL, {.routine = M_ProfileDeviceSelect}, 0, 81}, + + {IT_STRING | IT_CALL, "Character", "Default character and color for this Profile.", + NULL, {.routine = M_CharacterSelect}, 0, 101}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Player Tag", "Name displayed online when using this Profile.", + NULL, {.cvar = &cv_dummyprofileplayername}, 0, 141}, {IT_STRING | IT_CALL, "Confirm", "Confirm changes.", - NULL, {.routine = M_ConfirmProfile}, 0, 0}, + NULL, {.routine = M_ConfirmProfile}, 0, 171}, }; From 792fcf2ad8747d7ecb7188a998e4266d11cea898 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 4 Dec 2023 13:44:15 +0000 Subject: [PATCH 27/64] Profile edit character select: Get transition to work --- src/k_menu.h | 1 + src/k_menudraw.c | 19 ++++++++++++++----- src/menus/play-char-select.c | 7 +++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index a663c4a2f..caa7012d8 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1201,6 +1201,7 @@ void M_DrawKickHandler(void); void M_DrawPlaybackMenu(void); // Options menus: +void M_DrawOptionsCogs(void); void M_DrawOptionsMovingButton(void); // for sick transitions... void M_DrawOptions(void); void M_DrawGenericOptions(void); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index dea01d1d0..dff884702 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2151,22 +2151,31 @@ void M_DrawCharacterSelect(void) UINT8 priority = 0; INT16 quadx, quady; INT16 skin; - INT32 basex = optionsmenu.profile != NULL ? 64 : 0; + INT32 basex = 0; boolean forceskin = (Playing() && K_CanChangeRules(true) == true) && (cv_forceskin.value != -1); + if (optionsmenu.profile) + { + basex = (64 + (menutransition.tics*32)); + M_DrawOptionsCogs(); + } + if (setup_numplayers > 0) { priority = setup_animcounter % setup_numplayers; } { - const int kLeft = 80; + const int kLeft = 76; const int kTop = 6; const int kButtonWidth = 16; INT32 x = basex + kLeft; - K_drawButton((x += 18) * FRACUNIT, (kTop - 3) * FRACUNIT, 0, kp_button_r, M_MenuButtonPressed(pid, MBT_R)); - V_DrawThinString((x += kButtonWidth), kTop, 0, "Info"); + if (!optionsmenu.profile) // Does nothing on this screen + { + K_drawButton((x += 22) * FRACUNIT, (kTop - 3) * FRACUNIT, 0, kp_button_r, M_MenuButtonPressed(pid, MBT_R)); + V_DrawThinString((x += kButtonWidth), kTop, 0, "Info"); + } K_drawButton((x += 58) * FRACUNIT, (kTop - 1) * FRACUNIT, 0, kp_button_c[1], M_MenuButtonPressed(pid, MBT_C)); V_DrawThinString((x += kButtonWidth), kTop, 0, "Default"); @@ -3730,7 +3739,7 @@ void M_DrawMPServerBrowser(void) // OPTIONS MENU // Draws the cogs and also the options background! -static void M_DrawOptionsCogs(void) +void M_DrawOptionsCogs(void) { // the background isn't drawn outside of being in the main menu state. if (gamestate == GS_MENU) diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index e3424d96d..0eb457ef9 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -22,10 +22,10 @@ menu_t PLAY_CharSelectDef = { 0, PLAY_CharSelect, 0, 0, - 0, 0, + SKINCOLOR_ULTRAMARINE, 0, 0, NULL, - 0, 0, + 2, 5, // matches OPTIONS_EditProfileDef M_DrawCharacterSelect, M_CharacterSelectTick, M_CharacterSelectInit, @@ -1409,6 +1409,9 @@ void M_CharacterSelectTick(void) M_MPConfirmCharacterSelection(); } } + + if (optionsmenu.profile) + M_OptionsTick(); } boolean M_CharacterSelectQuit(void) From 85d8cfdf85be2b242336894d011fb73a107ae1d3 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 4 Dec 2023 20:10:03 +0000 Subject: [PATCH 28/64] menu_t: Add bgroutine All backgrounds are now handled by a seperate routine. This will allow more fine handling without a ton of special cases everywhere. Creates an absolutely nasty diff because all menu_t have been updated in one blast :face_holding_back_tears: For the things that DO need to draw outside of GS_MENU (M_DrawOptionsCogs, the Wrongwarp), the behaviourflag MBF_DRAWBGWHILEPLAYING has also been added. --- src/k_menu.h | 6 + src/k_menudraw.c | 152 ++++----------------- src/menus/extras-1.c | 1 + src/menus/extras-addons.c | 1 + src/menus/extras-challenges.c | 2 + src/menus/extras-egg-tv.cpp | 1 + src/menus/extras-wrong.c | 86 +++++++++++- src/menus/main-goner.cpp | 9 +- src/menus/main-profile-select.c | 3 +- src/menus/options-1.c | 3 +- src/menus/options-data-1.c | 3 +- src/menus/options-data-addons.c | 3 +- src/menus/options-data-discord.c | 3 +- src/menus/options-data-erase-1.c | 3 +- src/menus/options-data-erase-profile.c | 3 +- src/menus/options-data-replays.c | 3 +- src/menus/options-data-screenshots.c | 3 +- src/menus/options-gameplay-1.c | 3 +- src/menus/options-gameplay-item-toggles.c | 3 +- src/menus/options-hud-1.c | 3 +- src/menus/options-hud-online.c | 3 +- src/menus/options-profiles-1.c | 3 +- src/menus/options-profiles-edit-1.c | 3 +- src/menus/options-profiles-edit-controls.c | 3 +- src/menus/options-server-1.c | 3 +- src/menus/options-server-advanced.c | 3 +- src/menus/options-sound.c | 3 +- src/menus/options-video-1.c | 3 +- src/menus/options-video-gl.c | 3 +- src/menus/options-video-modes.c | 3 +- src/menus/play-char-select.c | 13 ++ src/menus/play-local-race-difficulty.c | 1 + src/menus/play-local-race-time-attack.c | 4 + src/menus/play-online-1.c | 1 + src/menus/play-online-host.c | 1 + src/menus/play-online-join-ip.c | 1 + src/menus/play-online-room-select.c | 1 + src/menus/play-online-server-browser.c | 1 + src/menus/transient/cup-select.c | 1 + src/menus/transient/discord-requests.c | 1 + src/menus/transient/level-select.c | 12 ++ src/menus/transient/pause-cheats.cpp | 1 + src/menus/transient/pause-game.c | 1 + src/menus/transient/pause-kick.c | 1 + src/menus/transient/pause-replay.c | 1 + src/menus/transient/sound-test.c | 1 + 46 files changed, 213 insertions(+), 153 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index caa7012d8..3165de130 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -136,6 +136,7 @@ typedef enum MBF_UD_LR_FLIPPED = 1, // flip up-down and left-right axes 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 } menubehaviourflags_t; struct menuitem_t @@ -170,6 +171,7 @@ struct menu_t INT16 transitionTics; // tics for transitions out void (*drawroutine)(void); // draw routine + void (*bgroutine)(void); // draw routine, but, like, for the background void (*tickroutine)(void); // ticker routine void (*initroutine)(void); // called when starting a new menu boolean (*quitroutine)(void); // called before quit a menu return true if we can @@ -1214,6 +1216,7 @@ void M_DrawProfileErase(void); extern tic_t shitsfree; // Extras menu: +void M_DrawExtrasBack(void); void M_DrawExtrasMovingButton(void); void M_DrawExtras(void); @@ -1392,6 +1395,7 @@ const char *M_GetDiscordName(discordRequest_t *r); NULL,\ NULL,\ NULL,\ + NULL,\ NULL\ } @@ -1411,6 +1415,7 @@ const char *M_GetDiscordName(discordRequest_t *r); NULL,\ NULL,\ NULL,\ + NULL,\ NULL\ } @@ -1429,6 +1434,7 @@ const char *M_GetDiscordName(discordRequest_t *r); NULL,\ NULL,\ NULL,\ + NULL,\ NULL\ } diff --git a/src/k_menudraw.c b/src/k_menudraw.c index dff884702..9c9f67ead 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -231,6 +231,12 @@ void M_DrawMenuBackground(void) } } +void M_DrawExtrasBack(void) +{ + patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); +} + UINT16 M_GetCvPlayerColor(UINT8 pnum) { if (pnum >= MAXSPLITSCREENPLAYERS) @@ -778,14 +784,28 @@ void M_Drawer(void) // background layer if (menuactive) { + boolean drawbgroutine = false; if (gamestate == GS_MENU) { - M_DrawMenuBackground(); + if (currentMenu->bgroutine) + drawbgroutine = true; + else + M_DrawMenuBackground(); } - else if (!WipeInAction && currentMenu != &PAUSE_PlaybackMenuDef) + else { - V_DrawFadeScreen(122, 3); + if (currentMenu->bgroutine + && (currentMenu->behaviourflags & MBF_DRAWBGWHILEPLAYING)) + drawbgroutine = true; + + if (!WipeInAction && currentMenu != &PAUSE_PlaybackMenuDef) + { + V_DrawFadeScreen(122, 3); + } } + + if (drawbgroutine) + currentMenu->bgroutine(); } // draw pause pic @@ -2151,15 +2171,9 @@ void M_DrawCharacterSelect(void) UINT8 priority = 0; INT16 quadx, quady; INT16 skin; - INT32 basex = 0; + INT32 basex = optionsmenu.profile ? (64 + (menutransition.tics*32)) : 0; boolean forceskin = (Playing() && K_CanChangeRules(true) == true) && (cv_forceskin.value != -1); - if (optionsmenu.profile) - { - basex = (64 + (menutransition.tics*32)); - M_DrawOptionsCogs(); - } - if (setup_numplayers > 0) { priority = setup_animcounter % setup_numplayers; @@ -3034,12 +3048,6 @@ void M_DrawLevelSelect(void) INT16 y = 80 - (12 * levellist.y); boolean tatransition = ((menutransition.startmenu == &PLAY_TimeAttackDef || menutransition.endmenu == &PLAY_TimeAttackDef) && menutransition.tics); - if (levellist.levelsearch.tutorial) - { - patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); - V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); - } - if (tatransition) { t = -t; @@ -3298,7 +3306,6 @@ void M_DrawEggaChannel(void) // Multiplayer mode option select void M_DrawMPOptSelect(void) { - M_DrawEggaChannel(); M_DrawMenuTooltips(); M_MPOptDrawer(&PLAY_MP_OptSelectDef, mpmenu.modewinextend); M_DrawMasterServerReminder(); @@ -3308,7 +3315,6 @@ void M_DrawMPOptSelect(void) // A rehash of the generic menu drawer adapted to fit into that cramped space. ...A small sacrifice for utility void M_DrawMPHost(void) { - patch_t *gobutt = W_CachePatchName("M_BUTTGO", PU_CACHE); // I'm very mature INT32 xp = 40, yp = 64, i = 0, w = 0; // Starting position for the text drawing. @@ -3413,7 +3419,6 @@ void M_DrawMPHost(void) // (I don't like duplicating code but I rather this than some horrible all-in-one function with too many options...) void M_DrawMPJoinIP(void) { - //patch_t *minibutt = W_CachePatchName("M_SBUTT", PU_CACHE); // There is no such things as mini butts, only thick thighs to rest your head on. //patch_t *minigo = W_CachePatchName("M_SGO", PU_CACHE); @@ -3786,8 +3791,6 @@ void M_DrawOptions(void) UINT8 *c = NULL; - M_DrawOptionsCogs(); - for (i=0; i < currentMenu->numitems; i++) { INT32 py = y - (itemOn*48); @@ -3823,7 +3826,6 @@ void M_DrawGenericOptions(void) { INT32 x = currentMenu->x - menutransition.tics*48, y = currentMenu->y, w, i, cursory = 0; - M_DrawOptionsCogs(); M_DrawMenuTooltips(); M_DrawOptionsMovingButton(); @@ -3973,7 +3975,6 @@ void M_DrawProfileErase(void) INT32 x = currentMenu->x - menutransition.tics*48, y = currentMenu->y-SMALLLINEHEIGHT, i, cursory = 0; UINT8 np = PR_GetNumProfiles(); - M_DrawOptionsCogs(); M_DrawMenuTooltips(); M_DrawOptionsMovingButton(); @@ -4005,7 +4006,6 @@ void M_DrawProfileSelect(void) INT32 x = 160 - optionsmenu.profilen*(128 + 128/8) + optionsmenu.offset; INT32 y = 35 + menutransition.tics*32; - M_DrawOptionsCogs(); M_DrawMenuTooltips(); // This shouldn't be drawn when a profile is selected as optx/opty are used to move the card. @@ -4038,8 +4038,6 @@ void M_DrawEditProfile(void) INT32 x = (145 + (menutransition.tics*32)); INT32 i; - M_DrawOptionsCogs(); - // Tooltip // The text is slightly shifted hence why we don't just use M_DrawMenuTooltips() V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL); @@ -4138,8 +4136,6 @@ void M_DrawProfileControls(void) INT32 i, j, k; const UINT8 pid = 0; - M_DrawOptionsCogs(); - V_DrawScaledPatch(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 -optionsmenu.conty, 0, W_CachePatchName("PR_CONT", PU_CACHE)); // Draw button presses... @@ -4368,7 +4364,6 @@ void M_DrawVideoModes(void) { INT32 i, j, row, col; - M_DrawOptionsCogs(); M_DrawMenuTooltips(); M_DrawOptionsMovingButton(); @@ -4452,7 +4447,6 @@ void M_DrawItemToggles(void) consvar_t *cv; INT32 i, translucent, drawnum; - M_DrawOptionsCogs(); M_DrawMenuTooltips(); M_DrawOptionsMovingButton(); @@ -4595,12 +4589,9 @@ void M_DrawExtras(void) INT32 x = 140 - (48*itemOn) + extrasmenu.offset; INT32 y = 70 + extrasmenu.offset; patch_t *buttback = W_CachePatchName("OPT_BUTT", PU_CACHE); - patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); UINT8 *c = NULL; - V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); - for (i=0; i < currentMenu->numitems; i++) { INT32 py = y - (itemOn*48); @@ -5115,14 +5106,6 @@ void M_DrawAddons(void) M_CacheAddonPatches(); - // hack: If we're calling this from GS_MENU, that means we're in the extras menu! - // so draw the apropriate background - if (gamestate == GS_MENU) - { - patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); - V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); - } - if (Playing()) V_DrawCenteredString(BASEVIDWIDTH/2, 4, warningflags, "Adding files mid-game may cause problems."); else @@ -6939,11 +6922,6 @@ void M_DrawStatistics(void) char beststr[256]; tic_t besttime = 0; - { - patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); - V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); - } - { const char *pagename = NULL; INT32 pagenamewidth = 0; @@ -7060,28 +7038,6 @@ void M_DrawStatistics(void) V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 50, 0, beststr); } -static INT32 M_WrongWarpFallingHelper(INT32 y, INT32 falltime) -{ - if (wrongwarp.ticker < falltime) - { - return y; - } - - if (wrongwarp.ticker > falltime + 2*TICRATE) - { - return INT32_MAX; - } - - if (wrongwarp.ticker < falltime + TICRATE) - { - y += + ((wrongwarp.ticker - falltime) & 1 ? 1 : -1); - return y; - } - - y += floor(pow(1.5, (double)(wrongwarp.ticker - (falltime + TICRATE)))); - return y; -} - static void M_DrawWrongPlayer(UINT8 i) { #define wrongpl wrongwarp.wrongplayers[i] @@ -7107,60 +7063,6 @@ void M_DrawWrongWarp(void) INT32 titleoffset = 0, titlewidth, x, y; const char *titletext = "WRONG GAME? WRONG GAME! "; - if (gamestate == GS_MENU) - { - patch_t *pat, *pat2; - INT32 animtimer, anim2 = 0; - - pat = W_CachePatchName("TITLEBG1", PU_CACHE); - pat2 = W_CachePatchName("TITLEBG2", PU_CACHE); - - animtimer = ((wrongwarp.ticker*5)/16) % SHORT(pat->width); - anim2 = SHORT(pat2->width) - (((wrongwarp.ticker*5)/16) % SHORT(pat2->width)); - - // SRB2Kart: F_DrawPatchCol is over-engineered; recoded to be less shitty and error-prone - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0); - - x = -((INT32)animtimer); - y = 0; - while (x < BASEVIDWIDTH) - { - V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, pat, NULL); - x += SHORT(pat->width); - } - - x = -anim2; - y = BASEVIDHEIGHT - SHORT(pat2->height); - while (x < BASEVIDWIDTH) - { - V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, pat2, NULL); - x += SHORT(pat2->width); - } - } - - { - patch_t *ttcheckers = W_CachePatchName("TTCHECK", PU_CACHE); - - y = FixedMul(40< falltime + 2*TICRATE) + { + return INT32_MAX; + } + + if (wrongwarp.ticker < falltime + TICRATE) + { + y += + ((wrongwarp.ticker - falltime) & 1 ? 1 : -1); + return y; + } + + y += floor(pow(1.5, (double)(wrongwarp.ticker - (falltime + TICRATE)))); + return y; +} + +static void M_DrawWrongWarpBack(void) +{ + INT32 x, y; + + if (gamestate == GS_MENU) + { + patch_t *pat, *pat2; + INT32 animtimer, anim2 = 0; + + pat = W_CachePatchName("TITLEBG1", PU_CACHE); + pat2 = W_CachePatchName("TITLEBG2", PU_CACHE); + + animtimer = ((wrongwarp.ticker*5)/16) % SHORT(pat->width); + anim2 = SHORT(pat2->width) - (((wrongwarp.ticker*5)/16) % SHORT(pat2->width)); + + // SRB2Kart: F_DrawPatchCol is over-engineered; recoded to be less shitty and error-prone + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0); + + x = -((INT32)animtimer); + y = 0; + while (x < BASEVIDWIDTH) + { + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, pat, NULL); + x += SHORT(pat->width); + } + + x = -anim2; + y = BASEVIDHEIGHT - SHORT(pat2->height); + while (x < BASEVIDWIDTH) + { + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, pat2, NULL); + x += SHORT(pat2->width); + } + } + + { + patch_t *ttcheckers = W_CachePatchName("TTCHECK", PU_CACHE); + + y = FixedMul(40< Date: Mon, 4 Dec 2023 20:36:58 +0000 Subject: [PATCH 29/64] Cheat updates - The devmode cheat returns to title screen - The warp cheat unlocks all hidden levels --- src/m_cheat.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/m_cheat.c b/src/m_cheat.c index 175acc7e5..7ef09b6d0 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -73,13 +73,24 @@ static UINT8 cheatf_warp(void) { if (!unlockables[i].conditionset) continue; - if (!gamedata->unlocked[i]) - { - gamedata->unlocked[i] = true; - success = true; - } + if (gamedata->unlocked[i]) + continue; + + gamedata->unlocked[i] = true; + success = true; } + // Unlock all hidden levels. +#define GD_MV_SET (MV_VISITED|MV_BEATEN) + for (i = 0; i < nummapheaders; i++) + { + if ((mapheaderinfo[i]->records.mapvisited & GD_MV_SET) == GD_MV_SET) + continue; + mapheaderinfo[i]->records.mapvisited |= GD_MV_SET; + success = true; + } +#undef GD_MV_SET + // Goofy, but this call needs to be before M_ClearMenus because that path // calls G_LoadLevel, which will trigger a gamedata save. Garbage factory if (success) @@ -170,6 +181,8 @@ static UINT8 cheatf_devmode(void) mapheaderinfo[i]->records.mapvisited = MV_MAX; } + M_ClearMenus(true); + // This is a developer feature, you know how to delete ringdata // G_SetUsedCheats(); S_StartSound(0, sfx_kc42); From 9d24a4cb6d13e0720574e264fa62203f73405acb Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 4 Dec 2023 21:03:51 +0000 Subject: [PATCH 30/64] Fix some oversights caused by the Goner Setup's access to various Options menus --- src/menus/main-goner.cpp | 14 +++++++------- src/menus/options-1.c | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 36fa019e9..89786d9cd 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -375,7 +375,13 @@ void M_GonerHidePassword(void) void M_GonerResetLooking(int type) { - if (goner_youactuallylooked > 2*TICRATE + if (type == GDGONER_VIDEO) + OPTIONS_MainDef.lastOn = mopt_video; + else if (type == GDGONER_SOUND) + OPTIONS_MainDef.lastOn = mopt_sound; + else if (type == GDGONER_PROFILE) + OPTIONS_MainDef.lastOn = mopt_profiles; + else if (goner_youactuallylooked > 2*TICRATE && goner_lasttypelooking == gamedata->gonerlevel) { if (goner_levelworking > gamedata->gonerlevel) @@ -386,12 +392,6 @@ void M_GonerResetLooking(int type) goner_lasttypelooking = static_cast(type); goner_youactuallylooked = 0; - if (type == GDGONER_VIDEO) - OPTIONS_MainDef.lastOn = mopt_video; - else if (type == GDGONER_SOUND) - OPTIONS_MainDef.lastOn = mopt_sound; - else - OPTIONS_MainDef.lastOn = mopt_profiles; } void M_GonerCheckLooking(void) diff --git a/src/menus/options-1.c b/src/menus/options-1.c index a314d446b..d60942d9e 100644 --- a/src/menus/options-1.c +++ b/src/menus/options-1.c @@ -16,7 +16,7 @@ menuitem_t OPTIONS_Main[] = {IT_STRING | IT_CALL, "Video Options", "Change video settings such as the resolution.", NULL, {.routine = M_VideoOptions}, 0, 0}, - {IT_STRING | IT_SUBMENU, "Sound Options", "Adjust various sound settings such as the volume.", + {IT_STRING | IT_CALL, "Sound Options", "Adjust various sound settings such as the volume.", NULL, {.routine = M_SoundOptions}, 0, 0}, {IT_STRING | IT_SUBMENU, "HUD Options", "Options related to the Heads-Up Display.", From 60e6445fd2c57196b2025ba00a1550085e86cf12 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 5 Dec 2023 21:23:58 +0000 Subject: [PATCH 31/64] Special Mode Time Attack swag to unwind - Play O_SSTAR3 - Special darkened background - Imagery to replace the minimap, since Special Mode maps generally don't have them as a rule - Sealed Stars: - Star Within The Seal, from the standard Evaluation - Versus: - The DeviantArt SignatuRes - Fix messed-up transitions between Time Attack main menu and Replay, Guests, and Ghosts submenus - Special Mode specifically: - Forced unexpected wipes - All modes: - Don't slide away the minimap (or replacement imagery) when going between these menus - Fix music not being correctly attached - Correctly center minimaps in Time Attack view --- src/k_menu.h | 3 + src/k_menudraw.c | 140 +++++++++++++++++++++--- src/menus/play-local-race-time-attack.c | 8 +- src/menus/transient/cup-select.c | 4 +- src/menus/transient/level-select.c | 60 ++++++---- 5 files changed, 175 insertions(+), 40 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 3165de130..5dea1c177 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -259,6 +259,8 @@ typedef enum ta_start, } ta_e; +// If you add another Time Attach submenu, remember to catch level-select.c's music/bgroutine update + extern menuitem_t PLAY_TAReplay[]; extern menu_t PLAY_TAReplayDef; @@ -1184,6 +1186,7 @@ boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, UINT8 spr2, UINT8 ro void M_DrawCup(cupheader_t *cup, fixed_t x, fixed_t y, INT32 lockedTic, boolean isTrophy, UINT8 placement); void M_DrawCupSelect(void); void M_DrawLevelSelect(void); +void M_DrawSealedBack(void); void M_DrawTimeAttack(void); void M_DrawRaceDifficulty(void); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9c9f67ead..47569a85f 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3080,6 +3080,47 @@ void M_DrawLevelSelect(void) M_DrawCupTitle(tay, &levellist.levelsearch); } +static boolean M_LevelSelectToTimeAttackTransitionHelper(void) +{ + if (menutransition.tics == 0) + return false; + + return \ + ( + menutransition.startmenu == &PLAY_LevelSelectDef + && menutransition.endmenu == &PLAY_TimeAttackDef + ) || ( + menutransition.endmenu == &PLAY_LevelSelectDef + && menutransition.startmenu == &PLAY_TimeAttackDef + ); +} + +void M_DrawSealedBack(void) +{ + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + + if (currentMenu != &PLAY_LevelSelectDef + && currentMenu != &PLAY_CupSelectDef) + return; + + INT32 translucencylevel = 7; + if (M_LevelSelectToTimeAttackTransitionHelper()) + { + translucencylevel += menutransition.tics/3; + + if (translucencylevel >= 10) + return; + } + + V_DrawFixedPatch( + 0, 0, + FRACUNIT, + translucencylevel << V_ALPHASHIFT, + W_CachePatchName("MENUI008", PU_CACHE), + R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLACK, GTC_CACHE) + ); +} + void M_DrawTimeAttack(void) { UINT16 map = levellist.choosemap; @@ -3088,27 +3129,95 @@ void M_DrawTimeAttack(void) INT16 rightedge = 149+t+155; INT16 opty = 140; INT32 w; - patch_t *minimap = NULL; UINT8 i; - M_DrawLevelSelectBlock(0, 2, map, true, false); - - //V_DrawFill(24-t, 82, 100, 100, 36); // size test - V_DrawScaledPatch(149+t, 70, 0, W_CachePatchName("BESTTIME", PU_CACHE)); - if (currentMenu == &PLAY_TimeAttackDef && mapheaderinfo[map]) + if (!mapheaderinfo[map]) { - tic_t timerec = 0; - tic_t laprec = 0; + V_DrawRightAlignedString(rightedge-12, opty, 0, "No map!?"); + return; + } + + { + patch_t *minimap = NULL; + INT32 minimapx = 76, minimapy = 130; + + if (M_LevelSelectToTimeAttackTransitionHelper()) + minimapx -= t; + + if (levellist.newgametype == GT_SPECIAL) + { + // Star Within The Seal + +#define SEAL_PULSELEN ((6*TICRATE)/5) // The rate of O_SSTAR3 + INT32 crossfade = (timeattackmenu.ticker % (2*SEAL_PULSELEN)) - SEAL_PULSELEN; + if (crossfade < 0) + crossfade = -crossfade; + crossfade = (crossfade * 10)/SEAL_PULSELEN; +#undef SEAL_PULSELEN + + if (crossfade != 10) + { + minimap = W_CachePatchName( + "K_FINB05", + PU_CACHE + ); + + V_DrawScaledPatch( + minimapx, minimapy, + 0, minimap + ); + } + + if (crossfade != 0) + { + minimap = W_CachePatchName( + "K_FINB04", + PU_CACHE + ); + + V_DrawScaledPatch( + minimapx, minimapy, + (10-crossfade)<minimapPic)) + { + V_DrawScaledPatch( + minimapx - (SHORT(minimap->width)/2), + minimapy - (SHORT(minimap->height)/2), + 0, minimap + ); + } + } + + if (currentMenu == &PLAY_TimeAttackDef) + { + tic_t timerec = mapheaderinfo[map]->records.time; + tic_t laprec = mapheaderinfo[map]->records.lap; UINT32 timeheight = 82; - if ((minimap = mapheaderinfo[map]->minimapPic)) - V_DrawScaledPatch(24-t, 82, 0, minimap); - - timerec = mapheaderinfo[map]->records.time; - laprec = mapheaderinfo[map]->records.lap; - if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) && (mapheaderinfo[map]->numlaps != 1)) { @@ -3142,6 +3251,9 @@ void M_DrawTimeAttack(void) else opty = 80; + // Done after to overlay material + M_DrawLevelSelectBlock(0, 2, map, true, false); + for (i = 0; i < currentMenu->numitems; i++) { UINT32 f = (i == itemOn) ? highlightflags : 0; diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c index 2074a3326..5f1ed6954 100644 --- a/src/menus/play-local-race-time-attack.c +++ b/src/menus/play-local-race-time-attack.c @@ -121,7 +121,7 @@ menu_t PLAY_TAReplayDef = { 2, 5, M_DrawTimeAttack, NULL, - NULL, + M_TimeAttackTick, NULL, NULL, NULL @@ -167,7 +167,7 @@ menu_t PLAY_TAReplayGuestDef = { 2, 5, M_DrawTimeAttack, NULL, - NULL, + M_TimeAttackTick, NULL, NULL, NULL @@ -208,7 +208,7 @@ menu_t PLAY_TAGhostsDef = { 2, 5, M_DrawTimeAttack, NULL, - NULL, + M_TimeAttackTick, NULL, NULL, NULL @@ -219,6 +219,8 @@ void M_PrepareTimeAttack(INT32 choice) { (void) choice; + timeattackmenu.ticker = 0; + // Gametype guess if (levellist.guessgt != MAXGAMETYPES) { diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index d6a5a4410..6cd4052f8 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -276,11 +276,13 @@ void M_CupSelectHandler(INT32 choice) } else if (count == 1 && levellist.levelsearch.timeattack == true) { - PLAY_TimeAttackDef.transitionID = currentMenu->transitionID+1; + currentMenu->transitionID = PLAY_TimeAttackDef.transitionID+1; M_LevelSelected(0); } else { + currentMenu->transitionID = PLAY_LevelSelectDef.transitionID; + // Keep cursor position if you select the same cup again, reset if it's a different cup if (oldcup != newcup || levellist.cursor >= count) { diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 928cabf46..09e69e3b3 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -18,17 +18,6 @@ menuitem_t PLAY_LevelSelect[] = {IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_LevelSelectHandler}, 0, 0}, }; -static void M_DrawLevelSelectBack(void) -{ - if (!levellist.levelsearch.tutorial) - { - M_DrawMenuBackground(); - return; - } - - M_DrawExtrasBack(); -} - menu_t PLAY_LevelSelectDef = { sizeof(PLAY_LevelSelect) / sizeof(menuitem_t), &PLAY_CupSelectDef, @@ -40,7 +29,7 @@ menu_t PLAY_LevelSelectDef = { NULL, 2, 5, M_DrawLevelSelect, - M_DrawLevelSelectBack, + NULL, M_LevelSelectTick, NULL, NULL, @@ -293,16 +282,43 @@ boolean M_LevelListFromGametype(INT16 gt) CV_SetValue(&cv_dummyspbattack, 0); } - PLAY_CupSelectDef.music = \ - PLAY_LevelSelectDef.music = \ - PLAY_TimeAttackDef.music = \ - currentMenu->music; - if (gamestate == GS_MENU) { - PLAY_CupSelectDef.menuitems[0].patch = \ - PLAY_LevelSelectDef.menuitems[0].patch = \ - currentMenu->menuitems[itemOn].patch; + const char *music; + void (*bgroutine)(void); + + if (gt == GT_SPECIAL) + { + music = "SSTAR3"; + bgroutine = M_DrawSealedBack; + } + else + { + music = currentMenu->music; + bgroutine = currentMenu->bgroutine; + + // Not for the time attack ones + PLAY_CupSelectDef.menuitems[0].patch = \ + PLAY_LevelSelectDef.menuitems[0].patch = \ + currentMenu->menuitems[itemOn].patch; + } + + menu_t *remap_menus[] = { + &PLAY_CupSelectDef, + &PLAY_LevelSelectDef, + &PLAY_TimeAttackDef, + &PLAY_TAReplayDef, + &PLAY_TAReplayGuestDef, + &PLAY_TAGhostsDef, + NULL + }; + + size_t i; + for (i = 0; remap_menus[i]; i++) + { + remap_menus[i]->music = music; + remap_menus[i]->bgroutine = bgroutine; + } } } @@ -311,6 +327,8 @@ boolean M_LevelListFromGametype(INT16 gt) if (levellist.levelsearch.cupmode) { + PLAY_CupSelectDef.transitionID = PLAY_LevelSelectDef.transitionID; + const boolean secondrowlocked = M_CupSecondRowLocked(); if (cupgrid.cache_secondrowlocked != secondrowlocked) { @@ -786,8 +804,6 @@ void M_LevelSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { M_SetMenuDelay(pid); - - PLAY_TimeAttackDef.transitionID = currentMenu->transitionID; M_LevelSelected(levellist.cursor); } else if (M_MenuBackPressed(pid)) From 6d0f90216cef7b45b961e7cdc36f3a40bfd9e6a6 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 5 Dec 2023 21:38:06 +0000 Subject: [PATCH 32/64] Fix bug with default background when transitioning between gametype select and Special Mode --- src/menus/transient/level-select.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 09e69e3b3..de121775c 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -296,11 +296,6 @@ boolean M_LevelListFromGametype(INT16 gt) { music = currentMenu->music; bgroutine = currentMenu->bgroutine; - - // Not for the time attack ones - PLAY_CupSelectDef.menuitems[0].patch = \ - PLAY_LevelSelectDef.menuitems[0].patch = \ - currentMenu->menuitems[itemOn].patch; } menu_t *remap_menus[] = { @@ -319,6 +314,11 @@ boolean M_LevelListFromGametype(INT16 gt) remap_menus[i]->music = music; remap_menus[i]->bgroutine = bgroutine; } + + // Not for the time attack ones + PLAY_CupSelectDef.menuitems[0].patch = \ + PLAY_LevelSelectDef.menuitems[0].patch = \ + currentMenu->menuitems[itemOn].patch; } } From 91682c3250808e6af081a76630cca3400bb6eab0 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 7 Dec 2023 16:36:04 +0000 Subject: [PATCH 33/64] Fix transitions for Level Select -> Time Attack Previous version was too specific --- src/k_menudraw.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 47569a85f..b33893c29 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3080,27 +3080,29 @@ void M_DrawLevelSelect(void) M_DrawCupTitle(tay, &levellist.levelsearch); } +static boolean M_LevelSelectHasBG(menu_t *check) +{ + if (check == NULL) + check = currentMenu; + + return (check == &PLAY_LevelSelectDef + || check == &PLAY_CupSelectDef); +} + static boolean M_LevelSelectToTimeAttackTransitionHelper(void) { if (menutransition.tics == 0) return false; - return \ - ( - menutransition.startmenu == &PLAY_LevelSelectDef - && menutransition.endmenu == &PLAY_TimeAttackDef - ) || ( - menutransition.endmenu == &PLAY_LevelSelectDef - && menutransition.startmenu == &PLAY_TimeAttackDef - ); + return (M_LevelSelectHasBG(menutransition.startmenu)) + != M_LevelSelectHasBG(menutransition.endmenu); } void M_DrawSealedBack(void) { V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - if (currentMenu != &PLAY_LevelSelectDef - && currentMenu != &PLAY_CupSelectDef) + if (M_LevelSelectHasBG(currentMenu) == false) return; INT32 translucencylevel = 7; From ae8db80a75a560ccfc34f693a96c340f81ecf015 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 7 Dec 2023 23:03:58 +0000 Subject: [PATCH 34/64] Goner Setup: Background has stronger presence - Show pure blue with NO SIGNAL when Video Options have not been configured - Show underneath other menus --- src/k_menu.h | 1 + src/k_menudraw.c | 12 +++++++++--- src/menus/main-goner.cpp | 18 ++++++++++++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 5dea1c177..9d252f326 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -206,6 +206,7 @@ extern menuitem_t MAIN_Goner[]; extern menu_t MAIN_GonerDef; void M_GonerTick(void); +void M_DrawGonerBack(void); void M_GonerProfile(INT32 choice); void M_GonerTutorial(INT32 choice); void M_GonerResetLooking(int type); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b33893c29..40a893a2e 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -785,7 +785,9 @@ void M_Drawer(void) if (menuactive) { boolean drawbgroutine = false; - if (gamestate == GS_MENU) + boolean trulystarted = M_GameTrulyStarted(); + + if (gamestate == GS_MENU && trulystarted) { if (currentMenu->bgroutine) drawbgroutine = true; @@ -798,7 +800,11 @@ void M_Drawer(void) && (currentMenu->behaviourflags & MBF_DRAWBGWHILEPLAYING)) drawbgroutine = true; - if (!WipeInAction && currentMenu != &PAUSE_PlaybackMenuDef) + if (!Playing() && !trulystarted) + { + M_DrawGonerBack(); + } + else if (!WipeInAction && currentMenu != &PAUSE_PlaybackMenuDef) { V_DrawFadeScreen(122, 3); } @@ -3861,7 +3867,7 @@ void M_DrawMPServerBrowser(void) void M_DrawOptionsCogs(void) { // the background isn't drawn outside of being in the main menu state. - if (gamestate == GS_MENU) + if (gamestate == GS_MENU && M_GameTrulyStarted()) { patch_t *back[3] = {W_CachePatchName("OPT_BG1", PU_CACHE), W_CachePatchName("OPT_BG2", PU_CACHE), W_CachePatchName("OPT_BG3", PU_CACHE)}; INT32 tflag = 0; diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 89786d9cd..972b53a8a 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -33,7 +33,6 @@ menuitem_t MAIN_Goner[] = {.routine = M_GonerTutorial}, 0, 0}, }; -static void M_DrawGonerBack(void); static void M_GonerDrawer(void); menu_t MAIN_GonerDef = { @@ -497,10 +496,25 @@ void M_GonerTick(void) } } -static void M_DrawGonerBack(void) +void M_DrawGonerBack(void) { srb2::Draw drawer = srb2::Draw(); + if (gamedata->gonerlevel <= GDGONER_VIDEO) + { + drawer + .width(BASEVIDWIDTH) + .height(BASEVIDHEIGHT) + .fill(157); + + drawer + .xy(10, 10) + .font(srb2::Draw::Font::kConsole) + .flags(V_ADD|V_10TRANS) + .text("NO SIGNAL"); + return; + } + drawer .width(BASEVIDWIDTH) .height(BASEVIDHEIGHT) From 8a2445820e3a0f81603a1abe05922af31bc2392b Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 7 Dec 2023 23:11:05 +0000 Subject: [PATCH 35/64] General clearup relating to Goner Setup - Clean up handling of goner_levelworking to retry current dialogue if something changes gamedata->gonerlevel - Create wrapper func for resetting ALL text - Gamedata-modifying cheats will set to GDGONER_DONE --- src/m_cheat.c | 5 +++++ src/menus/main-goner.cpp | 30 +++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/m_cheat.c b/src/m_cheat.c index 7ef09b6d0..677e9e610 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -94,7 +94,10 @@ static UINT8 cheatf_warp(void) // Goofy, but this call needs to be before M_ClearMenus because that path // calls G_LoadLevel, which will trigger a gamedata save. Garbage factory if (success) + { + gamedata->gonerlevel = GDGONER_DONE; G_SetUsedCheats(); + } M_ClearMenus(true); @@ -181,6 +184,8 @@ static UINT8 cheatf_devmode(void) mapheaderinfo[i]->records.mapvisited = MV_MAX; } + gamedata->gonerlevel = GDGONER_DONE; + M_ClearMenus(true); // This is a developer feature, you know how to delete ringdata diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 972b53a8a..d3301b130 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -370,6 +370,13 @@ void M_GonerHidePassword(void) S_StartSound(NULL, sfx_s3k5b); } +void M_GonerResetText(void) +{ + goner_typewriter.ClearText(); + LinesToDigest.clear(); + LinesOutput.clear(); +} + }; // namespace void M_GonerResetLooking(int type) @@ -383,8 +390,6 @@ void M_GonerResetLooking(int type) else if (goner_youactuallylooked > 2*TICRATE && goner_lasttypelooking == gamedata->gonerlevel) { - if (goner_levelworking > gamedata->gonerlevel) - goner_levelworking--; gamedata->gonerlevel++; LinesToDigest.clear(); } @@ -403,6 +408,7 @@ void M_GonerCheckLooking(void) void M_GonerTick(void) { static bool first = true; + static int lastseenlevel = GDGONER_INIT; if (goner_levelworking == GDGONER_INIT) { @@ -417,15 +423,23 @@ void M_GonerTick(void) first = true; // a lie, but only slightly... // Handle rewinding if you clear your gamedata. - goner_typewriter.ClearText(); - LinesToDigest.clear(); - LinesOutput.clear(); + M_GonerResetText(); goner_levelworking = GDGONER_INIT; } M_GonerResetLooking(GDGONER_INIT); + if (gamedata->gonerlevel != lastseenlevel) + { + if (goner_levelworking >= gamedata->gonerlevel) + { + // If the valid range has changed, try the current one again + goner_levelworking--; + } + lastseenlevel = gamedata->gonerlevel; + } + if (first) { first = goner_gdq = false; @@ -671,6 +685,8 @@ void M_GonerGDQ(boolean opinion) } LinesToDigest.reverse(); - goner_levelworking = GDGONER_TUTORIAL; - gamedata->gonerlevel = GDGONER_DONE; + if (gamedata->gonerlevel <= GDGONER_TUTORIAL) + { + goner_levelworking = gamedata->gonerlevel = GDGONER_TUTORIAL; + } } From 11fcb0b9ae7a834cb6d693d83d4c4304598ac0a0 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 7 Dec 2023 23:36:02 +0000 Subject: [PATCH 36/64] Goner Setup: Cook 3 - Add "Outro" One last guaranteed visit to the setup, to get a sendoff before the game truly begins. (The "survey program" joke is now themed softlock prevention.) --- src/k_menudraw.c | 55 +++++++++++++++---------- src/k_menufunc.c | 16 +++++++- src/m_cond.c | 7 +++- src/m_cond.h | 1 + src/menus/main-goner.cpp | 65 ++++++++++++++++++++++++++---- src/menus/transient/level-select.c | 7 +--- 6 files changed, 115 insertions(+), 36 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 40a893a2e..70ce57187 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1144,11 +1144,29 @@ void M_DrawKartGamemodeMenu(void) void M_DrawHorizontalMenu(void) { - INT32 x = BASEVIDWIDTH/2, y = currentMenu->y, i; + INT32 x, y, i, final = currentMenu->extra2-1, showflags; const INT32 width = 80; + y = currentMenu->y; + + x = (BASEVIDWIDTH - 8*final)/2; + for (i = 0; i < currentMenu->extra2; i++, x += 8) + { + if (i == itemOn) + { + V_DrawFill(x-2, y + 16, 4, 4, 0); + } + else + { + V_DrawFill(x-1, y + 17, 2, 2, + (i >= currentMenu->numitems) ? 20 : 10 + ); + } + } + i = itemOn; + x = BASEVIDWIDTH/2; do { @@ -1161,9 +1179,21 @@ void M_DrawHorizontalMenu(void) while (x < BASEVIDWIDTH + (width/2)) { + showflags = 0; + if (i == final) + { + showflags |= V_STRINGDANCE; + if (itemOn == i) + showflags |= V_YELLOWMAP; + } + else if (i == itemOn) + { + showflags |= highlightflags; + } + V_DrawCenteredThinString( x, y, - (i == itemOn) ? highlightflags : 0, + showflags, currentMenu->menuitems[i].text ); @@ -1172,30 +1202,13 @@ void M_DrawHorizontalMenu(void) x += width; } - y++; // thin string means better to bottom-align these - if (itemOn != 0) - V_DrawCharacter((BASEVIDWIDTH - width)/2 + 3 - (skullAnimCounter/5), y, + V_DrawCharacter((BASEVIDWIDTH - width)/2 + 3 - (skullAnimCounter/5), y + 1, '\x1C' | highlightflags, false); // left arrow if (itemOn != currentMenu->numitems-1) - V_DrawCharacter((BASEVIDWIDTH + width)/2 - 10 + (skullAnimCounter/5), y, + V_DrawCharacter((BASEVIDWIDTH + width)/2 - 10 + (skullAnimCounter/5), y + 1, '\x1D' | highlightflags, false); // right arrow - - x = (BASEVIDWIDTH - 8*(currentMenu->extra2-1))/2; - for (i = 0; i < currentMenu->extra2; i++, x += 8) - { - if (i == itemOn) - { - V_DrawFill(x-2, y + 15, 4, 4, 0); - } - else - { - V_DrawFill(x-1, y + 16, 2, 2, - (i >= currentMenu->numitems) ? 20 : 10 - ); - } - } } #define MAXMSGLINELEN 256 diff --git a/src/k_menufunc.c b/src/k_menufunc.c index c6ebeb401..5b5374b23 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -563,6 +563,20 @@ void M_StartControlPanel(void) Music_Stop("title"); + if (gamedata != NULL + && gamedata->gonerlevel < GDGONER_OUTRO + && gamestartchallenge < MAXUNLOCKABLES) + { + // See M_GameTrulyStarted + if ( + gamedata->unlockpending[gamestartchallenge] + || gamedata->unlocked[gamestartchallenge] + ) + { + gamedata->gonerlevel = GDGONER_OUTRO; + } + } + if (M_GameTrulyStarted() == false) { // Are you ready for the First Boot Experience? @@ -600,7 +614,7 @@ void M_StartControlPanel(void) } else { - if (restoreMenu == NULL) + if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef) restoreMenu = &MainDef; currentMenu = M_SpecificMenuRestore(M_InterruptMenuWithChallenges(restoreMenu)); restoreMenu = NULL; diff --git a/src/m_cond.c b/src/m_cond.c index 353f19bc0..8fff98d7f 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -3220,10 +3220,13 @@ boolean M_GameTrulyStarted(void) return true; // Okay, we can check to see if this challenge has been achieved. - return ( + /*return ( gamedata->unlockpending[gamestartchallenge] || gamedata->unlocked[gamestartchallenge] - ); + );*/ + // Actually, on second thought, let's let the Goner Setup play one last time + // The above is used in M_StartControlPanel instead + return (gamedata->gonerlevel == GDGONER_DONE); } boolean M_CheckNetUnlockByID(UINT16 unlockid) diff --git a/src/m_cond.h b/src/m_cond.h index 4bccce9b1..551ad7e42 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -296,6 +296,7 @@ typedef enum { GDGONER_SOUND, GDGONER_PROFILE, GDGONER_TUTORIAL, + GDGONER_OUTRO, GDGONER_DONE, } gdgoner_t; diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index d3301b130..dccf1ddd7 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -12,6 +12,9 @@ #include +static void M_GonerDrawer(void); +static void M_GonerConclude(INT32 choice); + menuitem_t MAIN_Goner[] = { {IT_STRING | IT_CALL, NULL, NULL, NULL, {.routine = M_QuitSRB2}, 0, 0}, // will be replaced @@ -31,9 +34,11 @@ menuitem_t MAIN_Goner[] = {IT_STRING | IT_CALL, "BEGIN TUTORIAL", "PREPARE FOR INTEGRATION.", NULL, {.routine = M_GonerTutorial}, 0, 0}, -}; -static void M_GonerDrawer(void); + {IT_STRING | IT_CALL, "START GAME", + "I WILL SUCCEED.", NULL, + {.routine = M_GonerConclude}, 0, 0}, +}; menu_t MAIN_GonerDef = { 1, // Intentionally not the sizeof calc @@ -305,7 +310,6 @@ void M_AddGonerLines(void) break; } case GDGONER_TUTORIAL: - case GDGONER_DONE: // maybe we could do something different for this eventually { if (!leftoff) { @@ -329,6 +333,36 @@ void M_AddGonerLines(void) break; } + case GDGONER_OUTRO: + { + if (!leftoff) + { + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, + "And... the training data is completed."); + } + LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, + "It's kind of funny, actually."); + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, + "Oh? Care to elucidate, Prower?"); + LinesToDigest.emplace_front(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(GONERSPEAKER_TAILS, TICRATE/2, + "It could have been five days or five years of development on our ""\x82""Ring Racers""\x80"", and that barely matters."); + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/4, + "Ha! As if. I'd like to think our partnership hasn't felt that long."); + 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, + "Now, I'm willing to let bygones be bygones."); + LinesToDigest.emplace_front(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(GONERSPEAKER_TAILS, 0, + "We've kept the keys from you long enough!"); + break; + } + case GDGONER_DONE: + break; + default: LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "I am error"); @@ -624,6 +658,15 @@ void M_GonerProfile(INT32 choice) M_GonerResetLooking(GDGONER_PROFILE); } +static boolean M_GonerSurveyResponse(INT32 ch) +{ + if (ch != CH_YES) + return; + + if (gamedata->gonerlevel < GDGONER_OUTRO) + gamedata->gonerlevel = GDGONER_OUTRO; +} + void M_GonerTutorial(INT32 choice) { (void)choice; @@ -646,15 +689,23 @@ void M_GonerTutorial(INT32 choice) cupgrid.grandprix = false; levellist.levelsearch.timeattack = false; - if (!M_LevelListFromGametype(GT_TUTORIAL)) + 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("SURVEY_PROGRAM", + M_StartMessage("Agreement", "YOU ACCEPT EVERYTHING THAT WILL HAPPEN FROM NOW ON.", - &M_QuitResponse, MM_YESNO, "I agree", "Cancel"); + &M_GonerSurveyResponse, MM_YESNO, "I agree", "Cancel"); } +} - goner_levelworking = gamedata->gonerlevel = GDGONER_DONE; +static void M_GonerConclude(INT32 choice) +{ + (void)choice; + + gamedata->gonerlevel = GDGONER_DONE; + + M_ClearMenus(true); + M_GonerResetText(); } void M_GonerGDQ(boolean opinion) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index de121775c..ec0c3034b 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -744,11 +744,8 @@ void M_LevelSelected(INT16 add) D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - if (M_GameTrulyStarted() == false) - { - // No restoreMenu set. - } - else if (levellist.levelsearch.tutorial) + if (!M_GameTrulyStarted() || + levellist.levelsearch.tutorial) { restoreMenu = currentMenu; } From f09c0a4b6ca7c441e2b305faf5c89629a1081cc5 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 8 Dec 2023 12:38:23 +0000 Subject: [PATCH 37/64] Forgot to commit compilation corrections last night - Fix repeat for GDGONER_VIDEO text - M_GonerSurveyResponse --- src/menus/main-goner.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index dccf1ddd7..2e5a3851e 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -464,16 +464,6 @@ void M_GonerTick(void) M_GonerResetLooking(GDGONER_INIT); - if (gamedata->gonerlevel != lastseenlevel) - { - if (goner_levelworking >= gamedata->gonerlevel) - { - // If the valid range has changed, try the current one again - goner_levelworking--; - } - lastseenlevel = gamedata->gonerlevel; - } - if (first) { first = goner_gdq = false; @@ -487,6 +477,18 @@ void M_GonerTick(void) gamedata->gonerlevel = GDGONER_INTRO; M_GonerRailroad(false); + + lastseenlevel = gamedata->gonerlevel; + } + else if (gamedata->gonerlevel != lastseenlevel) + { + if (goner_levelworking >= gamedata->gonerlevel) + { + // If the valid range has changed, try the current one again + goner_levelworking--; + } + + lastseenlevel = gamedata->gonerlevel; } goner_typewriter.WriteText(); @@ -529,7 +531,7 @@ void M_GonerTick(void) else if (goner_levelworking <= gamedata->gonerlevel) { if (goner_levelworking == GDGONER_INTRO && gamedata->gonerlevel < GDGONER_VIDEO) - gamedata->gonerlevel = GDGONER_VIDEO; + gamedata->gonerlevel = lastseenlevel = GDGONER_VIDEO; if (++goner_levelworking > gamedata->gonerlevel) { @@ -658,9 +660,9 @@ void M_GonerProfile(INT32 choice) M_GonerResetLooking(GDGONER_PROFILE); } -static boolean M_GonerSurveyResponse(INT32 ch) +static void M_GonerSurveyResponse(INT32 ch) { - if (ch != CH_YES) + if (ch != MA_YES) return; if (gamedata->gonerlevel < GDGONER_OUTRO) From c82d24b4bcd351385d7b9c26651f42dc73480017 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 8 Dec 2023 13:08:06 +0000 Subject: [PATCH 38/64] Music starts after Sound Options completed Currently plays _OCEAN for testing, but will be changed to play _GONER when Journey In Modulating Time's short loop is ready. Explicitly restricts the basic title screen's music from playing, too. --- src/f_finale.c | 2 +- src/k_menufunc.c | 3 --- src/menus/main-goner.cpp | 14 ++++++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/f_finale.c b/src/f_finale.c index 4df44d2d5..2db2dd90c 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1770,7 +1770,7 @@ void F_TitleScreenTicker(boolean run) { if (finalecount == 0) { - if (!Music_Playing("title")) + if (cache_gametrulystarted && !Music_Playing("title")) { // Now start the music Music_Loop("title", looptitle); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 5b5374b23..2f315b2bd 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -582,9 +582,6 @@ void M_StartControlPanel(void) // Are you ready for the First Boot Experience? M_ResetOptions(); currentMenu = &MAIN_GonerDef; - currentMenu->lastOn = 0; - Music_Remap("menu_nocred", "_GONER"); - Music_Play("menu_nocred"); } else if (cv_currprofile.value == -1) // Only ask once per session. { diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 2e5a3851e..2cec80c2e 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -5,6 +5,7 @@ #include "../m_cond.h" #include "../r_skins.h" #include "../s_sound.h" +#include "../music.h" #include "../p_local.h" // P_AutoPause #include "../st_stuff.h" // faceprefix #include "../v_draw.hpp" @@ -391,6 +392,15 @@ void M_GonerRailroad(bool set) S_StartSound(NULL, sfx_s3k63); } +void M_GonerPlayMusic(void) +{ + if (gamedata->gonerlevel <= GDGONER_SOUND || Music_Playing("menu")) + return; + + Music_Remap("menu", "_OCEAN"); //"_GONER"); + Music_Play("menu"); +} + void M_GonerHidePassword(void) { if (currentMenu->menuitems[0].mvar2) @@ -402,6 +412,8 @@ void M_GonerHidePassword(void) {.routine = M_QuitSRB2}, 0, 1}; S_StartSound(NULL, sfx_s3k5b); + + M_GonerPlayMusic(); } void M_GonerResetText(void) @@ -488,6 +500,8 @@ void M_GonerTick(void) goner_levelworking--; } + M_GonerPlayMusic(); + lastseenlevel = gamedata->gonerlevel; } From 536bed7dc60bb4876b7bbf48427462c52002eb3f Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 8 Dec 2023 13:46:10 +0000 Subject: [PATCH 39/64] When resetting the Goner Setup, make sure itemOn is set to a valid entry --- src/menus/main-goner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 2cec80c2e..ef4891a4b 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -489,6 +489,7 @@ void M_GonerTick(void) gamedata->gonerlevel = GDGONER_INTRO; M_GonerRailroad(false); + itemOn = 0; lastseenlevel = gamedata->gonerlevel; } From 24b5bb8f8619034aaf35a5f738a63b509bb542e4 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 8 Dec 2023 20:47:05 +0000 Subject: [PATCH 40/64] Options menu pass 1 - Surface ReduceVFX and Tilting - Group the Show FPS, Input Delay, and FOCUS LOST together on HUD options - Reduce the importance of console - Console back colour now exclusively part of the Online Chat Options - Hide Console text size - Move Chat Spam Protection to Server Options - Improve some tooltips - Skyboxes - Message tint - Remove embarassing waifu hidden text --- src/menus/options-hud-1.c | 14 ++++++------ src/menus/options-hud-online.c | 21 +++++++----------- src/menus/options-server-1.c | 3 +++ src/menus/options-video-1.c | 38 ++++++++++++++++++--------------- src/menus/play-online-join-ip.c | 2 +- 5 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/menus/options-hud-1.c b/src/menus/options-hud-1.c index 5b5f9f811..34c494955 100644 --- a/src/menus/options-hud-1.c +++ b/src/menus/options-hud-1.c @@ -4,7 +4,6 @@ #include "../k_menu.h" #include "../r_main.h" // cv_showhud #include "../v_video.h" // cv_constextsize -#include "../console.h" // console cvars menuitem_t OPTIONS_HUD[] = { @@ -21,12 +20,15 @@ menuitem_t OPTIONS_HUD[] = {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, +/* -- Nah, console isn't even bound by default, if you know how to use it you can change the size there {IT_STRING | IT_CVAR, "Console Text Size", "Size of the text within the console.", - NULL, {.cvar = &cv_constextsize}, 0, 0}, + NULL, {.cvar = &cv_constextsize}, 0, 0},*/ - // we spell words properly here. - {IT_STRING | IT_CVAR, "Console Tint", "Change the background colour of the console.", - NULL, {.cvar = &cons_backcolor}, 0, 0}, + {IT_STRING | IT_CVAR, "Show FPS", "Displays the game framerate at the lower right corner of the screen.", + NULL, {.cvar = &cv_ticrate}, 0, 0}, + + {IT_STRING | IT_CVAR, "Show Input Delay", "Displays your input delay at the lower right corner of the screen.", + NULL, {.cvar = &cv_showping}, 0, 0}, {IT_STRING | IT_CVAR, "Show \"FOCUS LOST\"", "Displays \"FOCUS LOST\" when the game window isn't the active window.", NULL, {.cvar = &cv_showfocuslost}, 0, 0}, @@ -34,7 +36,7 @@ menuitem_t OPTIONS_HUD[] = {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, - {IT_STRING | IT_SUBMENU, "Online HUD Options...", "HUD options related to the online chat box and other features.", + {IT_STRING | IT_SUBMENU, "Online Chat Options...", "HUD options related to the online chat box.", NULL, {.submenu = &OPTIONS_HUDOnlineDef}, 0, 0}, }; diff --git a/src/menus/options-hud-online.c b/src/menus/options-hud-online.c index e6cd9469d..b7449d28d 100644 --- a/src/menus/options-hud-online.c +++ b/src/menus/options-hud-online.c @@ -2,6 +2,7 @@ /// \brief Online HUD Options #include "../k_menu.h" +#include "../console.h" // console cvars menuitem_t OPTIONS_HUDOnline[] = { @@ -12,29 +13,23 @@ menuitem_t OPTIONS_HUDOnline[] = {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, - {IT_STRING | IT_CVAR, "Chat Box Tint", "Changes the background colour of the chat box.", - NULL, {.cvar = &cv_chatbacktint}, 0, 0}, + {IT_STRING | IT_CVAR, "Chat Box Tint", "Change the background color of the chat box.", + NULL, {.cvar = &cons_backcolor}, 0, 0}, - {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Chat Box Width", "Change the width of the Chat Box", + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Chat Box Width", "Change the width of the Chat Box.", NULL, {.cvar = &cv_chatwidth}, 0, 0}, - {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Chat Box Height", "Change the height of the Chat Box", + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Chat Box Height", "Change the height of the Chat Box.", NULL, {.cvar = &cv_chatheight}, 0, 0}, {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, - {IT_STRING | IT_CVAR, "Message Fadeout Time", "How long chat messages stay displayed with the chat closed.", + {IT_STRING | IT_CVAR, "Message Fadeout Time (s)", "How long chat messages stay displayed with the chat closed.", NULL, {.cvar = &cv_chattime}, 0, 0}, - {IT_STRING | IT_CVAR, "Spam Protection", "Prevents too many message from a single player from being displayed.", - NULL, {.cvar = &cv_chatspamprotection}, 0, 0}, - - {IT_SPACE | IT_NOTHING, NULL, NULL, - NULL, {NULL}, 0, 0}, - - {IT_STRING | IT_CVAR, "Local Ping Display", "In netgames, displays your ping at the lower right corner of the screen.", - NULL, {.cvar = &cv_showping}, 0, 0}, + {IT_STRING | IT_CVAR, "Message Tint", "Shows the tint for new chat messages when the box is closed.", + NULL, {.cvar = &cv_chatbacktint}, 0, 0}, }; diff --git a/src/menus/options-server-1.c b/src/menus/options-server-1.c index 893d28606..2b85ab610 100644 --- a/src/menus/options-server-1.c +++ b/src/menus/options-server-1.c @@ -40,6 +40,9 @@ menuitem_t OPTIONS_Server[] = {IT_STRING | IT_CVAR, "Mute Chat", "Prevents non-admins from sending chat messages.", NULL, {.cvar = &cv_mute}, 0, 0}, + {IT_STRING | IT_CVAR, "Chat Spam Protection", "Prevents too many message from a single player.", + NULL, {.cvar = &cv_chatspamprotection}, 0, 0}, + {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, diff --git a/src/menus/options-video-1.c b/src/menus/options-video-1.c index ea331d99d..be534ddf2 100644 --- a/src/menus/options-video-1.c +++ b/src/menus/options-video-1.c @@ -13,27 +13,31 @@ menuitem_t OPTIONS_Video[] = {IT_STRING | IT_CALL, "Set Resolution...", "Change the screen resolution for the game.", NULL, {.routine = M_VideoModeMenu}, 0, 0}, -// A check to see if you're not running on a fucking antique potato powered stone i guess??????? - #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) {IT_STRING | IT_CVAR, "Fullscreen", "Set whether you want to use fullscreen or windowed mode.", NULL, {.cvar = &cv_fullscreen}, 0, 0}, #endif + {IT_STRING | IT_CVAR, "Vertical Sync", "Works with your screen to reduce image tearing and judder.", NULL, {.cvar = &cv_vidwait}, 0, 0}, - {IT_NOTHING|IT_SPACE, NULL, "Kanade best waifu! I promise!", - NULL, {NULL}, 0, 0}, - - // Everytime I see a screenshot at max gamma I die inside - {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Gamma", "Adjusts the overall brightness of the game.", - NULL, {.cvar = &cv_globalgamma}, 0, 0}, - {IT_STRING | IT_CVAR, "FPS Cap", "Handles the frame rate of the game (35 to match game logic)", NULL, {.cvar = &cv_fpscap}, 0, 0}, - {IT_STRING | IT_CVAR, "Enable Skyboxes", "Turning this off will improve performance at the detriment of visuals for many maps.", - NULL, {.cvar = &cv_skybox}, 0, 0}, + {IT_NOTHING|IT_SPACE, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Screen Tilting", "The view rotatation on inclines can be disabled to reduce motion sickness.", + NULL, {.cvar = &cv_tilting}, 0, 0}, + + {IT_STRING | IT_CVAR, "Reduce Visual Effects", "If on, some less-important particle cues will be hidden.", + NULL, {.cvar = &cv_reducevfx}, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_SLIDER, "Gamma", "Adjusts the overall brightness of the game.", + NULL, {.cvar = &cv_globalgamma}, 0, 0}, + + {IT_NOTHING|IT_SPACE, NULL, NULL, + NULL, {NULL}, 0, 0}, {IT_STRING | IT_CVAR, "Draw Distance", "How far objects can be drawn. A tradeoff between performance & visibility.", NULL, {.cvar = &cv_drawdist}, 0, 0}, @@ -41,13 +45,13 @@ menuitem_t OPTIONS_Video[] = {IT_STRING | IT_CVAR, "Weather Draw Distance", "Affects how far weather visuals can be drawn. Lower values improve performance.", NULL, {.cvar = &cv_drawdist_precip}, 0, 0}, - {IT_STRING | IT_CVAR, "Show FPS", "Displays the game framerate at the lower right corner of the screen.", - NULL, {.cvar = &cv_ticrate}, 0, 0}, - - {IT_NOTHING|IT_SPACE, NULL, "Kanade best waifu! I promise!", - NULL, {NULL}, 0, 0}, + {IT_STRING | IT_CVAR, "Enable Skyboxes", "Turning this off may improve performance, but reduces courses' background details.", + NULL, {.cvar = &cv_skybox}, 0, 0}, #ifdef HWRENDER + {IT_NOTHING|IT_SPACE, NULL, NULL, + NULL, {NULL}, 0, 0}, + {IT_STRING | IT_SUBMENU, "Hardware Options...", "For usage and configuration of the OpenGL renderer.", NULL, {.submenu = &OPTIONS_VideoOGLDef}, 0, 0}, #endif @@ -59,7 +63,7 @@ menu_t OPTIONS_VideoDef = { &OPTIONS_MainDef, 0, OPTIONS_Video, - 32, 80, + 32, 80-8, SKINCOLOR_PLAGUE, 0, MBF_DRAWBGWHILEPLAYING, NULL, diff --git a/src/menus/play-online-join-ip.c b/src/menus/play-online-join-ip.c index 28e20d38e..e7a839714 100644 --- a/src/menus/play-online-join-ip.c +++ b/src/menus/play-online-join-ip.c @@ -19,7 +19,7 @@ menuitem_t PLAY_MP_JoinIP[] = {IT_STRING, "CONNECT ", "Attempt to connect to the server you entered the IP for.", NULL, {NULL}, 0, 0}, - {IT_STRING | IT_SPACE, "LAST IPs JOINED:", "Kanade best waifu :)", + {IT_STRING | IT_SPACE, "LAST IPs JOINED:", NULL, NULL, {NULL}, 0, 0}, {IT_STRING, "servip1", "The last 3 IPs you've succesfully joined are displayed here.", From 7d313af4db9d263e629ee73f8931453ca5fc05a7 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 8 Dec 2023 22:18:13 +0000 Subject: [PATCH 41/64] MT_ITEMCAPSULE: In GT_TUTORIAL, only respawn after 5 seconds --- src/p_inter.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/p_inter.c b/src/p_inter.c index 4c600ef0b..8ce0870a1 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2034,8 +2034,12 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget INT16 spacing = (target->radius >> 1) / target->scale; // set respawn fuse - if (damagetype == DMG_INSTAKILL || K_CapsuleTimeAttackRules() == true) // no respawns - ; + if (damagetype == DMG_INSTAKILL) + ; // Don't respawn (external) + else if (gametype == GT_TUTORIAL) + target->fuse = 5*TICRATE; + else if (K_CapsuleTimeAttackRules() == true) + ; // Don't respawn (internal) else if (target->threshold == KITEM_SUPERRING) target->fuse = 20*TICRATE; else From ee0eff89b471627d666f1330005f0282363816e4 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 8 Dec 2023 22:19:06 +0000 Subject: [PATCH 42/64] K_SPBInGame: Optimise to use trackercap instead of full thinkerlist romp --- src/k_kart.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 6f57ffe2b..c84d89f14 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12864,14 +12864,12 @@ UINT8 K_GetOrbinautItemFrame(UINT8 count) boolean K_IsSPBInGame(void) { - UINT8 i; - thinker_t *think; - // is there an SPB chasing anyone? if (spbplace != -1) return true; // do any players have an SPB in their item slot? + UINT8 i; for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) @@ -12881,14 +12879,15 @@ boolean K_IsSPBInGame(void) return true; } + // spbplace is still -1 until a fired SPB finds a target, so look for an in-map SPB just in case - for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next) + mobj_t *mobj; + for (mobj = trackercap; mobj; mobj = mobj->itnext) { - if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + if (mobj->type != MT_SPB) continue; - if (((mobj_t *)think)->type == MT_SPB) - return true; + return true; } return false; From c34b5f603470b395dc17a050c56ba1f87e254ada Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 8 Dec 2023 22:24:38 +0000 Subject: [PATCH 43/64] SPB Capsule improvements - Destroy all SPB Capsules in the stage when an SPB becomes relevant - When an SPB is put in a player's roulette - When an SPB is thrown - Automatically happens when SPB Capsule is touched - Don't respawn a popped SPB Capsule while an SPB is out --- src/k_kart.c | 2 +- src/k_objects.h | 2 ++ src/k_roulette.c | 3 +++ src/objects/spb.c | 30 ++++++++++++++++++++++++++++++ src/p_mobj.c | 8 ++++++++ 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index c84d89f14..546df2958 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -5060,7 +5060,7 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I Obj_JawzThrown(th, finalspeed, dir); break; case MT_SPB: - th->movefactor = finalspeed; + Obj_SPBThrown(th, finalspeed); break; case MT_BUBBLESHIELDTRAP: P_SetScale(th, ((5*th->destscale)>>2)*4); diff --git a/src/k_objects.h b/src/k_objects.h index c564cc2a9..5ee821c39 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -41,9 +41,11 @@ void Obj_ItemDebrisThink(mobj_t *debris); fixed_t Obj_ItemDebrisBounce(mobj_t *debris, fixed_t momz); /* SPB */ +void Obj_SPBThrown(mobj_t *spb, fixed_t finalspeed); void Obj_SPBThink(mobj_t *spb); void Obj_SPBExplode(mobj_t *spb); void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher); +void Obj_SPBEradicateCapsules(void); /* SPB Juicebox Rings */ void Obj_MantaRingThink(mobj_t *manta); diff --git a/src/k_roulette.c b/src/k_roulette.c index b66754c25..53a6d20ba 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1577,6 +1577,9 @@ static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) player->itemtype = K_ItemResultToType(getitem); player->itemamount = K_ItemResultToAmount(getitem); + + if (player->itemtype == KITEM_SPB) + Obj_SPBEradicateCapsules(); } /*-------------------------------------------------- diff --git a/src/objects/spb.c b/src/objects/spb.c index b90da6789..077e25173 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -74,6 +74,36 @@ enum #define spb_owner(o) ((o)->target) #define spb_chase(o) ((o)->tracer) +void Obj_SPBEradicateCapsules(void) +{ + thinker_t *think; + mobj_t *mo; + + // Expensive operation :D? + for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next) + { + if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) + continue; + + mo = (mobj_t *)think; + + if (mo->type != MT_ITEMCAPSULE) + continue; + + if (!mo->health || mo->fuse) + continue; + + P_KillMobj(mo, NULL, NULL, DMG_NORMAL); + } +} + +void Obj_SPBThrown(mobj_t *spb, fixed_t finalspeed) +{ + spb_speed(spb) = finalspeed; + + Obj_SPBEradicateCapsules(); +} + static void SPBMantaRings(mobj_t *spb) { fixed_t vScale = INT32_MAX; diff --git a/src/p_mobj.c b/src/p_mobj.c index 76d2daa6d..28b4a3f61 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -10435,6 +10435,14 @@ static boolean P_FuseThink(mobj_t *mobj) return false; case MT_ITEMCAPSULE: + + if (mobj->threshold == KITEM_SPB && K_IsSPBInGame()) + { + // SPB is in play. Try again in a short bit. + mobj->fuse += TICRATE/2; + return true; + } + if (mobj->spawnpoint) P_SpawnMapThing(mobj->spawnpoint); else From 1505167422af7e687c14008ed8383a4f1677e1a4 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 9 Dec 2023 17:32:15 +0000 Subject: [PATCH 44/64] Goner Setp: Add scroll --- src/k_menudraw.c | 2 + src/menus/main-goner.cpp | 147 +++++++++++++++++++++++++++++++++------ 2 files changed, 129 insertions(+), 20 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index c24c752fb..be3229748 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1150,6 +1150,8 @@ void M_DrawHorizontalMenu(void) y = currentMenu->y; + V_DrawFadeFill(0, y-2, BASEVIDWIDTH, 24, 0, 31, 5); + x = (BASEVIDWIDTH - 8*final)/2; for (i = 0; i < currentMenu->extra2; i++, x += 8) { diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index ef4891a4b..611510d07 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -15,6 +15,7 @@ static void M_GonerDrawer(void); static void M_GonerConclude(INT32 choice); +static boolean M_GonerInputs(INT32 ch); menuitem_t MAIN_Goner[] = { @@ -56,7 +57,7 @@ menu_t MAIN_GonerDef = { M_GonerTick, NULL, NULL, - NULL, + M_GonerInputs, }; namespace @@ -160,6 +161,19 @@ std::forward_list LinesOutput; int goner_levelworking = GDGONER_INIT; bool goner_gdq = false; +int goner_scroll = 0; +int goner_scrollend = 0; + +void M_GonerResetText(void) +{ + goner_typewriter.ClearText(); + LinesToDigest.clear(); + LinesOutput.clear(); + + goner_scroll = 0; + goner_scrollend = 0; +} + void M_AddGonerLines(void) { SRB2_ASSERT(LinesToDigest.empty()); @@ -416,13 +430,6 @@ void M_GonerHidePassword(void) M_GonerPlayMusic(); } -void M_GonerResetText(void) -{ - goner_typewriter.ClearText(); - LinesToDigest.clear(); - LinesOutput.clear(); -} - }; // namespace void M_GonerResetLooking(int type) @@ -535,13 +542,20 @@ void M_GonerTick(void) goner_delay--; else if (!LinesToDigest.empty()) { - if (!LinesOutput.empty()) - LinesOutput.front().value = goner_typewriter.textLines; + // Only add new lines if the scroll is invalid + if (!goner_scroll) + { + if (!LinesOutput.empty()) + { + LinesOutput.front().value = goner_typewriter.textLines; + goner_scrollend++; + } - auto line = LinesToDigest.front(); - if (line.Handle()) - LinesOutput.push_front(line); - LinesToDigest.pop_front(); + auto line = LinesToDigest.front(); + if (line.Handle()) + LinesOutput.push_front(line); + LinesToDigest.pop_front(); + } } else if (goner_levelworking <= gamedata->gonerlevel) { @@ -588,17 +602,51 @@ void M_DrawGonerBack(void) static void M_GonerDrawer(void) { - srb2::Draw drawer = srb2::Draw().x(BASEVIDWIDTH/4); + srb2::Draw drawer = srb2::Draw(); float newy = BASEVIDHEIGHT/2 + (3*12); - boolean first = true; + boolean first = true; int lastspeaker = MAXGONERSPEAKERS; + int workscroll = goner_scroll; + + if (workscroll) + { + float scrollamount = -72; // a bit more than BASEVIDHEIGHT/3 + + for (auto & element : LinesOutput) + { + if (first) + { + scrollamount += goner_typewriter.textLines*12; + first = false; + } + else + { + scrollamount += element.value*12; + + if (lastspeaker != element.speaker) + scrollamount += 2; + } + + if (!workscroll) break; + workscroll--; + + lastspeaker = element.speaker; + } + + if (scrollamount > 0) + newy += scrollamount; + + first = true; + lastspeaker = MAXGONERSPEAKERS; + workscroll = goner_scroll; + } for (auto & element : LinesOutput) { - INT32 flags = V_TRANSLUCENT; std::string text; + INT32 flags; if (newy < 0) break; @@ -606,7 +654,6 @@ static void M_GonerDrawer(void) { text = goner_typewriter.text; newy -= goner_typewriter.textLines*12; - flags = 0; first = false; } else @@ -620,12 +667,17 @@ static void M_GonerDrawer(void) lastspeaker = element.speaker; - //if (newy > BASEVIDHEIGHT) continue; -- not needed yet + flags = (workscroll == 0) + ? 0 + : V_TRANSLUCENT; + workscroll--; + + if (newy > BASEVIDHEIGHT) continue; auto speaker = goner_speakers[element.speaker]; srb2::Draw line = drawer - .xy(speaker.offset, newy) + .xy(BASEVIDWIDTH/4 + speaker.offset, newy) .flags(flags); line @@ -645,6 +697,25 @@ static void M_GonerDrawer(void) } } + if (goner_scroll) + { + const char *scrolltext = "SCROLL DOWN TO CONTINUE"; + const int width = V_StringWidth(scrolltext, 0) + 2; + + srb2::Draw popup = drawer.xy(BASEVIDWIDTH/2, currentMenu->y - 4 - 9); + + popup + .xy(-width/2, -1) + .width(width) + .height(10) + .fill(20); + popup + .align(srb2::Draw::Align::kCenter) + .font(srb2::Draw::Font::kConsole) + .flags(V_GRAYMAP) + .text(scrolltext); + } + M_DrawHorizontalMenu(); } @@ -758,3 +829,39 @@ void M_GonerGDQ(boolean opinion) goner_levelworking = gamedata->gonerlevel = GDGONER_TUTORIAL; } } + +static boolean M_GonerInputs(INT32 ch) +{ + const UINT8 pid = 0; + static int holdtime = 0; + const int magicscroll = 4; // hehe + + (void)ch; + + if (menucmd[pid].dpad_ud != 0) + { + if (((++holdtime) % magicscroll) == 1) // Instantly responsive + { + if (menucmd[pid].dpad_ud < 0) + { + if (goner_scroll < goner_scrollend) + goner_scroll++; + } + else + { + if (goner_scroll > 0) + { + goner_scroll--; + if (goner_delay < magicscroll) + goner_delay = magicscroll; + } + } + + return true; + } + } + + holdtime = 0; + + return false; +} From de7d53b4ea9e41219de88897a5a8e2929d521d04 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 10 Dec 2023 15:21:51 +0000 Subject: [PATCH 45/64] M_LevelListFromGametype: Fix invalid Tutorial-specific behaviour --- src/menus/transient/level-select.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index ec0c3034b..54ea2c078 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -586,10 +586,14 @@ boolean M_LevelListFromGametype(INT16 gt) possiblecursor++; } - if (test != NEXTMAP_INVALID) + if (test < nummapheaders) + { levellist.cursor = possiblecursor; + invalidatedcursor = false; + } } - else if (invalidatedcursor) + + if (invalidatedcursor) { levellist.cursor = 0; } From cdb2b44aa6d1f3b8985d2c4dd012570af22a8391 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 10 Dec 2023 16:10:45 +0000 Subject: [PATCH 46/64] ACS-Dialogue interface: New threads with dialogue auto-skip previous threads with dialogue If some dialogue needs to be never-missable, that's the mapper's responsibility - but now they won't get randomly interleaved if multiple are activated just by regular free driving, which was the worst kind of default --- src/acs/call-funcs.cpp | 44 ++++++++++++++++++++++++++++++----------- src/acs/environment.cpp | 5 +++-- src/acs/thread.hpp | 11 ++++++++--- src/k_dialogue.cpp | 11 +++++++++++ src/k_dialogue.hpp | 5 +++++ 5 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index f7354723d..5da29fcbb 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -667,6 +667,27 @@ bool CallFunc_CameraWait(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM:: return true; // Execution interrupted } +/*-------------------------------------------------- + bool Dialogue_ValidCheck(ACSVM::Thread *thread) + + Helper to check if the thread's dialogue + context is valid, initialising if not set. +--------------------------------------------------*/ +static bool Dialogue_ValidCheck(ACSVM::Thread *thread) +{ + // TODO when we move away from g_dialogue + if (netgame) + { + return false; + } + + auto info = &static_cast(thread)->info; + if (!info->dialogue_era) + info->dialogue_era = g_dialogue.GetNewEra(); + + return g_dialogue.EraIsValid(info->dialogue_era); +} + /*-------------------------------------------------- bool CallFunc_DialogueWaitDismiss(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) @@ -678,17 +699,18 @@ bool CallFunc_DialogueWaitDismiss(ACSVM::Thread *thread, const ACSVM::Word *argV (void)argV; (void)argC; - // TODO when we move away from g_dialogue - if (netgame) + if (Dialogue_ValidCheck(thread) == false) { return false; } g_dialogue.SetDismissable(true); + auto info = &static_cast(thread)->info; + thread->state = { ACSVM::ThreadState::WaitTag, - 0, + info->dialogue_era, ACS_TAGTYPE_DIALOGUE }; @@ -706,17 +728,18 @@ bool CallFunc_DialogueWaitText(ACSVM::Thread *thread, const ACSVM::Word *argV, A (void)argV; (void)argC; - // TODO when we move away from g_dialogue - if (netgame) + if (Dialogue_ValidCheck(thread) == false) { return false; } g_dialogue.SetDismissable(false); + auto info = &static_cast(thread)->info; + thread->state = { ACSVM::ThreadState::WaitTag, - 1, + info->dialogue_era, ACS_TAGTYPE_DIALOGUE }; @@ -2025,8 +2048,7 @@ bool CallFunc_DialogueSetSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, (void)argC; - // TODO when we move away from g_dialogue - if (netgame) + if (Dialogue_ValidCheck(thread) == false) { return false; } @@ -2069,8 +2091,7 @@ bool CallFunc_DialogueSetCustomSpeaker(ACSVM::Thread *thread, const ACSVM::Word (void)argC; - // TODO when we move away from g_dialogue - if (netgame) + if (Dialogue_ValidCheck(thread) == false) { return false; } @@ -2161,8 +2182,7 @@ bool CallFunc_DialogueNewText(ACSVM::Thread *thread, const ACSVM::Word *argV, AC (void)argC; - // TODO when we move away from g_dialogue - if (netgame) + if (Dialogue_ValidCheck(thread) == false) { return false; } diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index e6f9554cb..6979a3cd0 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -312,12 +312,13 @@ bool Environment::checkTag(ACSVM::Word type, ACSVM::Word tag) case ACS_TAGTYPE_DIALOGUE: { // TODO when we move away from g_dialogue - if (netgame) + // See also call-funcs.cpp Dialogue_ValidCheck + if (netgame || !g_dialogue.EraIsValid(tag)) // cheeky reuse { return true; } - if (tag == 0) // cheeky reuse + if (g_dialogue.Dismissable()) { // wait for dismissal return (!g_dialogue.Active()); diff --git a/src/acs/thread.hpp b/src/acs/thread.hpp index f72aac34f..faa345c15 100644 --- a/src/acs/thread.hpp +++ b/src/acs/thread.hpp @@ -66,6 +66,7 @@ public: sector_t *sector; // Sector that activated this thread. polyobj_t *po; // Polyobject that activated this thread. bool fromLineSpecial; // Called from P_ProcessLineSpecial. + UINT32 dialogue_era; // Prevents overlapping dialogue scripts. ThreadInfo() : thread_era { thinker_era }, @@ -74,7 +75,8 @@ public: side{ 0 }, sector{ nullptr }, po{ nullptr }, - fromLineSpecial{ false } + fromLineSpecial{ false }, + dialogue_era { 0 } { } @@ -85,7 +87,8 @@ public: side{ info.side }, sector{ info.sector }, po{ info.po }, - fromLineSpecial{ info.fromLineSpecial } + fromLineSpecial{ info.fromLineSpecial }, + dialogue_era { info.dialogue_era } { P_SetTarget(&mo, info.mo); } @@ -97,7 +100,8 @@ public: side{ activator->side }, sector{ activator->sector }, po{ activator->po }, - fromLineSpecial{ static_cast(activator->fromLineSpecial) } + fromLineSpecial{ static_cast(activator->fromLineSpecial) }, + dialogue_era { 0 } { P_SetTarget(&mo, activator->mo); } @@ -118,6 +122,7 @@ public: side = info.side; sector = info.sector; po = info.po; + dialogue_era = info.dialogue_era; return *this; } diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index c95dc8d23..d3fbe82dd 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -478,11 +478,22 @@ void Dialogue::Dismiss(void) typewriter.ClearText(); } +UINT32 Dialogue::GetNewEra(void) +{ + return (++current_era); +} + +bool Dialogue::EraIsValid(INT32 comparison) +{ + return (current_era == comparison); +} + void Dialogue::Unset(void) { Dismiss(); SetSpeaker(); slide = 0; + current_era = 0; } /* diff --git a/src/k_dialogue.hpp b/src/k_dialogue.hpp index adce8fa84..bdea25626 100644 --- a/src/k_dialogue.hpp +++ b/src/k_dialogue.hpp @@ -49,6 +49,9 @@ public: void Dismiss(void); void Unset(void); + UINT32 GetNewEra(void); + bool EraIsValid(INT32 comparison); + class Typewriter { public: @@ -76,6 +79,8 @@ public: private: Typewriter typewriter; + INT32 current_era; + patch_t *bgPatch; patch_t *confirmPatch; From 75205e3f7926e1760043583e7ff08cc6f5dca6be Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 10 Dec 2023 18:23:57 +0000 Subject: [PATCH 47/64] New ACS functions (resolves #697) - For Tutorials specifically - void Dialogue_AutoDismiss(void) - Dismisses the current dialogue (including from other threads). - str CheckTutorialChallenge(void) - Returns special values depending on state relevant to the Tutorial Challenge. - Returns a blank string in netgames, or if none of the following are true. - Returns "Active" if the skip is in progress. - Returns "Failed" if the skip was just failed. - Returns "Locked" if not available with this gamedata. - Other tiny check functions - bool PositionStart(void) - Returns true if leveltime < starttime. - bool FreePlay(void) - Returns true if in Free Play. --- src/acs/call-funcs.cpp | 90 ++++++++++++++++++++++++++++++++++++++++- src/acs/call-funcs.hpp | 4 ++ src/acs/environment.cpp | 4 ++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 5da29fcbb..07d5c5940 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1301,7 +1301,9 @@ bool CallFunc_PlayerRings(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM: && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { - rings = info->mo->player->rings; + rings = (gametyperules & GTR_SPHERES) + ? info->mo->player->spheres + : info->mo->player->rings; } thread->dataStk.push(rings); @@ -1771,6 +1773,20 @@ bool CallFunc_TimeAttack(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM:: return false; } +/*-------------------------------------------------- + bool CallFunc_FreePlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if the map is in Free Play. +--------------------------------------------------*/ +bool CallFunc_FreePlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push((M_NotFreePlay() == false)); + return false; +} + /*-------------------------------------------------- bool CallFunc_GrandPrix(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) @@ -1785,6 +1801,20 @@ bool CallFunc_GrandPrix(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::W return false; } +/*-------------------------------------------------- + bool CallFunc_PositionStart(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns if the map is in POSITION!! +--------------------------------------------------*/ +bool CallFunc_PositionStart(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + thread->dataStk.push((starttime != 0 && leveltime < starttime)); + return false; +} + /*-------------------------------------------------- bool CallFunc_GetGrabbedSprayCan(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) @@ -1822,6 +1852,45 @@ bool CallFunc_GetGrabbedSprayCan(ACSVM::Thread *thread, const ACSVM::Word *argV, return false; } +/*-------------------------------------------------- + bool CallFunc_CheckTutorialChallenge(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Returns the Tutorial Challenge status, if possible. +--------------------------------------------------*/ +bool CallFunc_CheckTutorialChallenge(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + Environment *env = &ACSEnv; + + (void)argV; + (void)argC; + + if (netgame == false) // behaviour is not particularly sync-friendly + { + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + thread->dataStk.push(~env->getString( "Active" )->idx); + return false; + } + + if (tutorialchallenge == TUTORIALSKIP_FAILED) + { + thread->dataStk.push(~env->getString( "Failed" )->idx); + return false; + } + + if (gamedata != nullptr + && gamedata->enteredtutorialchallenge == true + && M_GameTrulyStarted() == false) + { + thread->dataStk.push(~env->getString( "Locked" )->idx); + return false; + } + } + + thread->dataStk.push(0); + return false; +} + /*-------------------------------------------------- bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) @@ -2196,6 +2265,25 @@ bool CallFunc_DialogueNewText(ACSVM::Thread *thread, const ACSVM::Word *argV, AC return false; } +/*-------------------------------------------------- + bool CallFunc_DialogueAutoDismiss(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Dismiss the current dialogue text. +--------------------------------------------------*/ +bool CallFunc_DialogueAutoDismiss(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + if (Dialogue_ValidCheck(thread) == false) + { + return false; + } + + g_dialogue.Dismiss(); + return false; +} + /*-------------------------------------------------- bool CallFunc_MusicPlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index 89ab3d351..e3379ee22 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -83,8 +83,11 @@ bool CallFunc_LowestLap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::W bool CallFunc_EncoreMode(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PrisonBreak(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_TimeAttack(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_FreePlay(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GrandPrix(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_PositionStart(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GetGrabbedSprayCan(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_CheckTutorialChallenge(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PodiumPosition(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); @@ -102,6 +105,7 @@ bool CallFunc_MusicRemap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM:: bool CallFunc_DialogueSetSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_DialogueSetCustomSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_DialogueNewText(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_DialogueAutoDismiss(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_Freeze(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 6979a3cd0..d1fd639a4 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -169,6 +169,9 @@ Environment::Environment() addFuncDataACS0( 313, addCallFunc(CallFunc_GrandPrix)); addFuncDataACS0( 314, addCallFunc(CallFunc_GetGrabbedSprayCan)); addFuncDataACS0( 315, addCallFunc(CallFunc_PlayerBot)); + addFuncDataACS0( 316, addCallFunc(CallFunc_PositionStart)); + addFuncDataACS0( 317, addCallFunc(CallFunc_FreePlay)); + addFuncDataACS0( 318, addCallFunc(CallFunc_CheckTutorialChallenge)); addFuncDataACS0( 500, addCallFunc(CallFunc_CameraWait)); addFuncDataACS0( 501, addCallFunc(CallFunc_PodiumPosition)); @@ -188,6 +191,7 @@ Environment::Environment() addFuncDataACS0( 602, addCallFunc(CallFunc_DialogueNewText)); addFuncDataACS0( 603, addCallFunc(CallFunc_DialogueWaitDismiss)); addFuncDataACS0( 604, addCallFunc(CallFunc_DialogueWaitText)); + addFuncDataACS0( 605, addCallFunc(CallFunc_DialogueAutoDismiss)); } ACSVM::Thread *Environment::allocThread() From 647a9d0a0e19c974bfa802d474675ac0dee21270 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 17 Dec 2023 13:18:35 +0000 Subject: [PATCH 48/64] Goner setup music: Change behaviour - Hook into M_PlayMenuJam, to reduce bespoke behaviour - Use the finalised track string - Play on the Sound Options menu Also, use MAIN_Goner as reference for the password field's presence, because I lost like 30 minutes to a related bug --- src/k_menu.h | 1 + src/k_menufunc.c | 17 ++++++++++++----- src/menus/main-goner.cpp | 35 +++++++++++++++++++---------------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 9d252f326..80e3f9073 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -212,6 +212,7 @@ void M_GonerTutorial(INT32 choice); void M_GonerResetLooking(int type); void M_GonerCheckLooking(void); void M_GonerGDQ(boolean opinion); +boolean M_GonerMusicPlayable(void); extern menuitem_t PLAY_CharSelect[]; extern menu_t PLAY_CharSelectDef; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 2f315b2bd..f2ec06d00 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -357,11 +357,6 @@ void M_PlayMenuJam(void) menu_t *refMenu = (menuactive ? currentMenu : restoreMenu); static boolean musicstatepermitted = false; - if (M_GameTrulyStarted() == false) - { - return; - } - if (challengesmenu.pending) { Music_StopAll(); @@ -375,6 +370,17 @@ void M_PlayMenuJam(void) if (Playing() || soundtest.playing) return; + if (M_GameTrulyStarted() == false) + { + if (M_GonerMusicPlayable() && NotCurrentlyPlaying("_GONER")) + { + Music_Remap("menu", "_GONER"); + Music_Play("menu"); + } + + return; + } + if (refMenu != NULL && refMenu->music != NULL) { if (refMenu->music[0] == '.' && refMenu->music[1] == '\0') @@ -582,6 +588,7 @@ void M_StartControlPanel(void) // Are you ready for the First Boot Experience? M_ResetOptions(); currentMenu = &MAIN_GonerDef; + M_PlayMenuJam(); } else if (cv_currprofile.value == -1) // Only ask once per session. { diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 611510d07..58d90c394 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -187,7 +187,7 @@ void M_AddGonerLines(void) // This one always plays, so it checks the levelworking instead of gamedata. if (goner_levelworking == GDGONER_INTRO) { - if (!currentMenu->menuitems[0].mvar2) + if (!MAIN_Goner[0].mvar2) { LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Metal Sonic. Are you online?"); @@ -406,28 +406,19 @@ void M_GonerRailroad(bool set) S_StartSound(NULL, sfx_s3k63); } -void M_GonerPlayMusic(void) -{ - if (gamedata->gonerlevel <= GDGONER_SOUND || Music_Playing("menu")) - return; - - Music_Remap("menu", "_OCEAN"); //"_GONER"); - Music_Play("menu"); -} - void M_GonerHidePassword(void) { - if (currentMenu->menuitems[0].mvar2) + if (MAIN_Goner[0].mvar2) return; - currentMenu->menuitems[0] = + MAIN_Goner[0] = {IT_STRING | IT_CALL, "EXIT PROGRAM", "CONCLUDE OBSERVATIONS NOW.", NULL, {.routine = M_QuitSRB2}, 0, 1}; S_StartSound(NULL, sfx_s3k5b); - M_GonerPlayMusic(); + M_PlayMenuJam(); } }; // namespace @@ -451,6 +442,20 @@ void M_GonerResetLooking(int type) goner_youactuallylooked = 0; } +boolean M_GonerMusicPlayable(void) +{ + if (!MAIN_Goner[0].mvar2) + return false; + + if (currentMenu == &OPTIONS_SoundDef) + return true; + + if (gamedata->gonerlevel <= GDGONER_SOUND) + return false; + + return true; +} + void M_GonerCheckLooking(void) { if (goner_lasttypelooking != gamedata->gonerlevel) @@ -487,7 +492,7 @@ void M_GonerTick(void) { first = goner_gdq = false; - currentMenu->menuitems[0] = + MAIN_Goner[0] = {IT_STRING | IT_CVAR | IT_CV_STRING, ". . .", "ATTEMPT ADMINISTRATOR ACCESS.", NULL, {.cvar = &cv_dummyextraspassword}, 0, 0}; @@ -508,8 +513,6 @@ void M_GonerTick(void) goner_levelworking--; } - M_GonerPlayMusic(); - lastseenlevel = gamedata->gonerlevel; } From a15a836755cfb49be21c33bd6198eed2c766fc31 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 17 Dec 2023 13:18:58 +0000 Subject: [PATCH 49/64] Minor phrasing updates to dialog --- src/menus/main-goner.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 58d90c394..b9de9874b 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -303,7 +303,7 @@ void M_AddGonerLines(void) "Every racer carries one, to contain their personal settings."); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "It helps get your ""\x87""controls""\x80"" set up nice and quickly, "\ - "when starting your vehicle."); + "when starting your vehicle and navigating the menu."); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "And it helps track your wins, too."); @@ -322,14 +322,18 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "Go on, do your ""\x87""Profile Setup""\x80""!"); + break; } case GDGONER_TUTORIAL: { if (!leftoff) { + LinesToDigest.emplace_front(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(GONERSPEAKER_EGGMAN, TICRATE/5, - "...right, now that that's sorted. How's the upload going?"); + "Miles. How's the upload going?"); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "Just finished."); @@ -344,7 +348,8 @@ void M_AddGonerLines(void) "It's time to ""\x87""begin your Tutorial""\x80""!"); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, - "That's a lot to get through. Good luck, MS-1!"); + "Remember, MS-1. Even when you move on from this setup, you "\ + "can always change your Options at any time from the menu."); break; } From 129fad2010182c6ed988a3f5bc7490a0d7d5f114 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 17 Dec 2023 19:24:32 +0000 Subject: [PATCH 50/64] First pass at adding background to Goner Setup Tails and Eggman talking to each other/Metal Sonic/the player. --- src/menus/main-goner.cpp | 258 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 250 insertions(+), 8 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index b9de9874b..1f87b5fad 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -10,6 +10,7 @@ #include "../st_stuff.h" // faceprefix #include "../v_draw.hpp" #include "../k_dialogue.hpp" +#include "../m_random.h" #include @@ -117,6 +118,8 @@ std::array goner_speakers = {}; srb2::Dialogue::Typewriter goner_typewriter; int goner_delay; +int goner_scroll = 0; +int goner_scrollend = -1; class GonerChatLine { @@ -124,6 +127,7 @@ public: gonerspeakers_t speaker; std::string dialogue; int value; // Mutlipurpose. + void (*routine)(void); GonerChatLine(gonerspeakers_t speaker, int delay, std::string dialogue) { @@ -135,22 +139,38 @@ public: dialogue.c_str() ); this->value = delay; + + this->routine = nullptr; }; + GonerChatLine(int delay, void (*routine)(void)) + { + this->value = delay; + this->routine = routine; + + this->speaker = MAXGONERSPEAKERS; + this->dialogue = ""; + } + // Returns true if line is text bool Handle(void) { - if (speaker >= MAXGONERSPEAKERS) + goner_delay = value; + + if (routine != nullptr) + routine(); + + if (speaker >= MAXGONERSPEAKERS || dialogue.empty()) return false; goner_typewriter.voiceSfx = goner_speakers[speaker].TalkSound(); goner_typewriter.NewText(dialogue); - goner_delay = value; - value = 1; // this is now repurposed as the number of lines visible + goner_scrollend++; + return true; }; }; @@ -158,12 +178,131 @@ public: std::forward_list LinesToDigest; std::forward_list LinesOutput; +class GonerBGData +{ +public: + int miles_mouth; + bool miles_electric, miles_prev_electric; + bool miles_cameralook; + int miles_timetoblink, miles_prev_timetoblink; + + GonerBGData() + { + miles_mouth = 1; + miles_electric = miles_prev_electric = true; + miles_cameralook = false; + miles_timetoblink = miles_prev_timetoblink = M_RandomRange(2*TICRATE, 4*TICRATE); + }; + + bool NeutralMouthCheck(gonerspeakers_t speaker) + { + return (LinesOutput.empty() + || LinesOutput.front().speaker != speaker + || goner_typewriter.textDone + || goner_typewriter.text.empty()); + } + + void Tick() + { + if (miles_timetoblink == 0) + { + if (miles_prev_timetoblink) + { + miles_timetoblink = 4*TICRATE - miles_prev_timetoblink; + miles_prev_timetoblink = 0; + } + else + { + miles_timetoblink = miles_prev_timetoblink = M_RandomRange(2*TICRATE, 4*TICRATE); + } + } + miles_timetoblink--; + + miles_prev_electric = miles_electric; + + if (NeutralMouthCheck(GONERSPEAKER_TAILS)) + miles_mouth = 1; + else + { + char c = tolower(goner_typewriter.text.back()); + char incomingc = goner_typewriter.textDest.empty() + ? '\0' + : tolower(goner_typewriter.textDest.back()); + switch (c) + { + // Close mouth + case 'm': + case 'w': + case 'p': + case 'b': + case '.': + case ',': + case ':': + case ';': + miles_mouth = 1; + break; + + // Vowels + case 'a': miles_mouth = 2; break; + case 'e': miles_mouth = 3; break; + case 'i': miles_mouth = 4; break; + case 'o': miles_mouth = 5; break; + case 'u': miles_mouth = 6; break; + + // VOWELBIGUOUS + case 'y': miles_mouth = 7; break; + + // Hissth + case 't': + case 's': + case 'r': + case 'n': + miles_mouth = 7; break; + + // Approximation, since MS-1 is said a LOT by Tails. + case '-': + if (incomingc != '1') + break; + miles_mouth = 5; break; + case '1': miles_mouth = 7; break; + + // No update for you! + default: + break; + } + } + } +}; + +GonerBGData goner_background; + +void Miles_Look_Camera() +{ + if (goner_background.miles_cameralook) + return; + goner_background.miles_cameralook = true; + goner_background.miles_timetoblink = goner_background.miles_prev_timetoblink = 0; +} + +void Miles_Look_Electric() +{ + goner_background.miles_electric = true; + + if (!goner_background.miles_cameralook) + return; + goner_background.miles_cameralook = false; + goner_background.miles_timetoblink = goner_background.miles_prev_timetoblink = 0; +} + +void Miles_Electric_Lower() +{ + goner_background.miles_electric = false; + Miles_Look_Camera(); +} + int goner_levelworking = GDGONER_INIT; bool goner_gdq = false; -int goner_scroll = 0; -int goner_scrollend = 0; - void M_GonerResetText(void) { goner_typewriter.ClearText(); @@ -171,7 +310,7 @@ void M_GonerResetText(void) LinesOutput.clear(); goner_scroll = 0; - goner_scrollend = 0; + goner_scrollend = -1; } void M_AddGonerLines(void) @@ -197,6 +336,7 @@ void M_AddGonerLines(void) if (leftoff) { + LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "It must have run into some sort of error..."); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, @@ -218,25 +358,31 @@ void M_AddGonerLines(void) "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, "I don't feel very safe!"); + LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/4, "But its programming is definitely locked down..."); + LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_front(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, "Wait, I'm getting weird readings over the network."); + LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(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, "The ""\x87""viewport""\x80"" and ""\x87""audio""\x80"" "\ "config looks like it got messed up."); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "So you're right. I wonder if it has anything to do with that outburst."); + LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Alright, Metal! I don't remember your specifications offhand. "\ "First things first, go ahead and set up your "\ @@ -253,26 +399,32 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Now, calibrate your ""\x87""Sound Options""\x80""."); + LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "You always make your stuff so loud by default, Eggman. It might need a moment."); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Not Metal! He always needed to be stealthy. But go on, set your sliders."); + LinesToDigest.emplace_front(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, "Oh! Let's tell Metal about our project!"); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Of course. I and my lab assista-"); + LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "Lab PARTNER."); + LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Irrelevant!"); } @@ -283,16 +435,20 @@ void M_AddGonerLines(void) "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, "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, "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, "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, "Yes! That's one of my contributions."); @@ -301,17 +457,22 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(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, "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, "And it helps track your wins, too."); + LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, "Bragging rights. My idea!"); + LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(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, "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 "\ @@ -332,9 +493,11 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(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, "Miles. How's the upload going?"); + LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "Just finished."); @@ -342,6 +505,7 @@ void M_AddGonerLines(void) "Perfect."); } + LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Now, Metal... it's important you pay attention."); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, @@ -487,6 +651,7 @@ void M_GonerTick(void) // Handle rewinding if you clear your gamedata. M_GonerResetText(); + goner_background = GonerBGData(); goner_levelworking = GDGONER_INIT; } @@ -522,6 +687,7 @@ void M_GonerTick(void) } goner_typewriter.WriteText(); + goner_background.Tick(); if (menutyping.active || menumessage.active || P_AutoPause()) return; @@ -556,7 +722,6 @@ void M_GonerTick(void) if (!LinesOutput.empty()) { LinesOutput.front().value = goner_typewriter.textLines; - goner_scrollend++; } auto line = LinesToDigest.front(); @@ -606,6 +771,83 @@ void M_DrawGonerBack(void) .width(BASEVIDWIDTH) .height(BASEVIDHEIGHT) .fill(31); + + { + srb2::Draw eggman = drawer.xy(-70, 20); + + // body + eggman.patch("GON_EB"); + + // head + { + if (goner_typewriter.syllable + && !goner_background.NeutralMouthCheck(GONERSPEAKER_EGGMAN)) + eggman = eggman.y(1); + + eggman.patch("GON_E1H1"); + } + } + + { + srb2::Draw miles = drawer.xy(205, 45); + + // body + miles.patch("GON_T_B"); + + // head/eyes + miles.patch( + va("GON_T1H%u", + goner_background.miles_cameralook + ? 1 + : 2 + ) + ); + if (goner_background.miles_timetoblink == 0) + { + // eyelids + miles.patch("GON_T1E3"); + } + + // mouth + miles.patch( + va("GON_T1M%u", + goner_background.miles_mouth) + ); + + // miles electric and hands (and arms..?) + { + int y = 0; + if (!goner_background.miles_electric) + { + y = 20; + } + if (goner_background.miles_prev_electric != goner_background.miles_electric) + { + y--; + } + if (y != 0) + { + miles = miles.y(y); + } + + bool drawarms = ( + goner_background.miles_electric + && !goner_background.NeutralMouthCheck(GONERSPEAKER_TAILS) + ); + if (drawarms) + { + miles.patch("GON_TA1"); + miles + .flags(V_TRANSLUCENT) + .patch("GON_TME1"); + } + else + { + miles.patch("GON_TME1"); + } + miles.patch("GON_THA1"); + } + } } static void M_GonerDrawer(void) From fe669ff281bb136a51162399118a2d41d409cb7d Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 00:46:20 +0000 Subject: [PATCH 51/64] Goner Setup Background part 2 - Eggman's mouth moves - Smooth camera movement - Smooth Miles Electric movement - Less fragile/instant blinking --- src/k_menu.h | 1 + src/k_menufunc.c | 7 + src/menus/main-goner.cpp | 272 +++++++++++++++++++++++++++++---------- 3 files changed, 212 insertions(+), 68 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 80e3f9073..404e69b76 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -206,6 +206,7 @@ extern menuitem_t MAIN_Goner[]; extern menu_t MAIN_GonerDef; void M_GonerTick(void); +void M_GonerBGTick(void); void M_DrawGonerBack(void); void M_GonerProfile(INT32 choice); void M_GonerTutorial(INT32 choice); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index f2ec06d00..5bbad7122 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -1179,7 +1179,14 @@ void M_Ticker(void) } if (--skullAnimCounter <= 0) + { skullAnimCounter = 8; + } + + if (!Playing() && !M_GameTrulyStarted()) + { + M_GonerBGTick(); + } #if 0 if (currentMenu == &PAUSE_PlaybackMenuDef) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 1f87b5fad..7040cd9cb 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -11,6 +11,8 @@ #include "../v_draw.hpp" #include "../k_dialogue.hpp" #include "../m_random.h" +#include "../r_main.h" +#include "../m_easing.h" #include @@ -133,7 +135,7 @@ public: { this->speaker = speaker; this->dialogue = V_ScaledWordWrap( - (BASEVIDWIDTH/2 + 12) << FRACBITS, + (BASEVIDWIDTH/2 + 6) << FRACBITS, FRACUNIT, FRACUNIT, FRACUNIT, 0, TINY_FONT, dialogue.c_str() @@ -181,49 +183,102 @@ std::forward_list LinesOutput; class GonerBGData { public: - int miles_mouth; - bool miles_electric, miles_prev_electric; - bool miles_cameralook; - int miles_timetoblink, miles_prev_timetoblink; + bool miles_electric; + fixed_t miles_electric_delta; + bool miles_cameralook, miles_pendinglook; + int miles_timetoblink, miles_next_timetoblink; + + gonerspeakers_t focuslast; + fixed_t focusdelta; + + int focusmouth; + + fixed_t x, focusx; + fixed_t y, focusy; GonerBGData() { - miles_mouth = 1; - miles_electric = miles_prev_electric = true; + x = focusx = 0; + y = focusy = 0; + + focuslast = MAXGONERSPEAKERS; + focusdelta = 0; + + focusmouth = 1; + + miles_electric = true; + miles_electric_delta = FRACUNIT; miles_cameralook = false; - miles_timetoblink = miles_prev_timetoblink = M_RandomRange(2*TICRATE, 4*TICRATE); + miles_timetoblink = M_RandomRange(2*TICRATE, 4*TICRATE); + miles_next_timetoblink = (4*TICRATE + 10) - miles_timetoblink; }; - bool NeutralMouthCheck(gonerspeakers_t speaker) + bool NeutralMouthCheck() { return (LinesOutput.empty() - || LinesOutput.front().speaker != speaker || goner_typewriter.textDone || goner_typewriter.text.empty()); } void Tick() { + gonerspeakers_t focuscurrent = MAXGONERSPEAKERS; + if (currentMenu == &MAIN_GonerDef && !LinesOutput.empty()) + { + focuscurrent = LinesOutput.front().speaker; + } + + if (focuslast != focuscurrent) + { + focusdelta = FRACUNIT; + x = focusx; + y = focusy; + + switch (focuscurrent) + { + case GONERSPEAKER_TAILS: + focusx = -10*FRACUNIT; + focusy = 0; + break; + case GONERSPEAKER_EGGMAN: + focusx = 10*FRACUNIT; + focusy = 0; + break; + default: + focusx = 0; + focusy = 20*FRACUNIT; + break; + } + + focuslast = focuscurrent; + } + if (miles_timetoblink == 0) { - if (miles_prev_timetoblink) + if (miles_next_timetoblink) { - miles_timetoblink = 4*TICRATE - miles_prev_timetoblink; - miles_prev_timetoblink = 0; + miles_timetoblink = miles_next_timetoblink; + miles_next_timetoblink = 0; } else { - miles_timetoblink = miles_prev_timetoblink = M_RandomRange(2*TICRATE, 4*TICRATE); + miles_timetoblink = M_RandomRange(2*TICRATE, 4*TICRATE); + miles_next_timetoblink = (4*TICRATE + 10) - miles_timetoblink; } } + else if (miles_timetoblink == 5) + { + miles_cameralook = miles_pendinglook; + } miles_timetoblink--; - miles_prev_electric = miles_electric; - - if (NeutralMouthCheck(GONERSPEAKER_TAILS)) - miles_mouth = 1; + if (NeutralMouthCheck()) + focusmouth = 1; else { + // The following is loosely based on code by Tyron, + // generously donated from an unreleased SRB2Kart mod. + char c = tolower(goner_typewriter.text.back()); char incomingc = goner_typewriter.textDest.empty() ? '\0' @@ -235,36 +290,40 @@ public: case 'w': case 'p': case 'b': - case '.': - case ',': - case ':': - case ';': - miles_mouth = 1; + focusmouth = 1; break; // Vowels - case 'a': miles_mouth = 2; break; - case 'e': miles_mouth = 3; break; - case 'i': miles_mouth = 4; break; - case 'o': miles_mouth = 5; break; - case 'u': miles_mouth = 6; break; + case 'a': focusmouth = 2; break; + case 'e': focusmouth = 3; break; + case 'i': focusmouth = 4; break; + case 'o': focusmouth = 5; break; + case 'u': focusmouth = 6; break; // VOWELBIGUOUS - case 'y': miles_mouth = 7; break; + case 'y': focusmouth = 7; break; // Hissth case 't': case 's': case 'r': case 'n': - miles_mouth = 7; break; + focusmouth = 7; break; // Approximation, since MS-1 is said a LOT by Tails. case '-': if (incomingc != '1') break; - miles_mouth = 5; break; - case '1': miles_mouth = 7; break; + focusmouth = 5; break; + case '1': focusmouth = 7; break; + + // Conclude dialogue + case '.': + case '!': + case ',': + case ':': + case ';': + focusmouth = 0; break; // No update for you! default: @@ -276,27 +335,46 @@ public: GonerBGData goner_background; +void Miles_SetPendingLook(bool set) +{ + if (goner_background.miles_pendinglook == set) + return; + + goner_background.miles_pendinglook = set; + + if (goner_background.miles_timetoblink > 10) + { + goner_background.miles_timetoblink = 10; + goner_background.miles_next_timetoblink = 0; + } + else if (goner_background.miles_timetoblink < 5) + goner_background.miles_next_timetoblink = 10; +} + +void Miles_SetElectric(bool set) +{ + if (goner_background.miles_electric == set) + return; + + goner_background.miles_electric = set; + goner_background.miles_electric_delta = + FRACUNIT - goner_background.miles_electric_delta; +} + void Miles_Look_Camera() { - if (goner_background.miles_cameralook) - return; - goner_background.miles_cameralook = true; - goner_background.miles_timetoblink = goner_background.miles_prev_timetoblink = 0; + Miles_SetPendingLook(true); } void Miles_Look_Electric() { - goner_background.miles_electric = true; - - if (!goner_background.miles_cameralook) - return; - goner_background.miles_cameralook = false; - goner_background.miles_timetoblink = goner_background.miles_prev_timetoblink = 0; + Miles_SetElectric(true); + Miles_SetPendingLook(false); } void Miles_Electric_Lower() { - goner_background.miles_electric = false; + Miles_SetElectric(false); Miles_Look_Camera(); } @@ -513,7 +591,7 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "Remember, MS-1. Even when you move on from this setup, you "\ - "can always change your Options at any time from the menu."); + "can always change your ""\x87""Options""\x80"" at any time from the menu."); break; } @@ -632,6 +710,12 @@ void M_GonerCheckLooking(void) goner_youactuallylooked++; } +void M_GonerBGTick(void) +{ + // Laundering CPP code through C-callable funcs ~toast 171223 + goner_background.Tick(); +} + void M_GonerTick(void) { static bool first = true; @@ -643,7 +727,7 @@ void M_GonerTick(void) // Init. goner_speakers[GONERSPEAKER_EGGMAN] = GonerSpeaker("eggman", 0); - goner_speakers[GONERSPEAKER_TAILS] = GonerSpeaker("tails", 12); + goner_speakers[GONERSPEAKER_TAILS] = GonerSpeaker("tails", 6); } else if (gamedata->gonerlevel == GDGONER_INIT) { @@ -687,7 +771,6 @@ void M_GonerTick(void) } goner_typewriter.WriteText(); - goner_background.Tick(); if (menutyping.active || menumessage.active || P_AutoPause()) return; @@ -772,24 +855,48 @@ void M_DrawGonerBack(void) .height(BASEVIDHEIGHT) .fill(31); + drawer = drawer.xy( + FixedToFloat(Easing_InOutCubic( + goner_background.focusdelta, + goner_background.focusx, + goner_background.x + )), + FixedToFloat(Easing_InOutCubic( + goner_background.focusdelta, + goner_background.focusy, + goner_background.y + )) + ); + + if (goner_background.focusdelta && renderdeltatics <= 2*FRACUNIT) { - srb2::Draw eggman = drawer.xy(-70, 20); + goner_background.focusdelta -= renderdeltatics/TICRATE; + if (goner_background.focusdelta < 0) + goner_background.focusdelta = 0; + } + + { + srb2::Draw eggman = drawer.xy(-60, 20); + bool eggfocus = (!goner_background.NeutralMouthCheck() + && LinesOutput.front().speaker == GONERSPEAKER_EGGMAN); // body eggman.patch("GON_EB"); // head { - if (goner_typewriter.syllable - && !goner_background.NeutralMouthCheck(GONERSPEAKER_EGGMAN)) - eggman = eggman.y(1); + int mouth = 7; // eggman grins by default + if (eggfocus && goner_background.focusmouth) + mouth = goner_background.focusmouth; - eggman.patch("GON_E1H1"); + eggman.patch(va("GON_E1H%u", mouth)); } } { srb2::Draw miles = drawer.xy(205, 45); + bool milesfocus = (!goner_background.NeutralMouthCheck() + && LinesOutput.front().speaker == GONERSPEAKER_TAILS); // body miles.patch("GON_T_B"); @@ -802,37 +909,66 @@ void M_DrawGonerBack(void) : 2 ) ); - if (goner_background.miles_timetoblink == 0) + if (goner_background.miles_timetoblink < 10) { - // eyelids - miles.patch("GON_T1E3"); + // eyelids 1-01 2-23 3-45 2-67 1-89 + int blink = ((goner_background.miles_timetoblink > 4) + ? 3 - (goner_background.miles_timetoblink - 4)/2 + : 1 + goner_background.miles_timetoblink/2 + ); + miles.patch(va("GON_T1E%u", blink)); } // mouth - miles.patch( - va("GON_T1M%u", - goner_background.miles_mouth) - ); + { + int mouth = 1; + if (milesfocus && goner_background.focusmouth) + mouth = goner_background.focusmouth; + + miles.patch(va("GON_T1M%u", mouth)); + } // miles electric and hands (and arms..?) { - int y = 0; - if (!goner_background.miles_electric) + if (goner_background.miles_electric) { - y = 20; + miles = miles + .y(FixedToFloat(Easing_InOutBack( // raising requires a little effort + goner_background.miles_electric_delta, + 20*FRACUNIT, + 0 + ))) + .x(FixedToFloat(Easing_InOutBack( + goner_background.miles_electric_delta, + 3*FRACUNIT, + 0 + ))); } - if (goner_background.miles_prev_electric != goner_background.miles_electric) + else { - y--; + miles = miles + .y(FixedToFloat(Easing_OutBack( // dropping requires little effort + goner_background.miles_electric_delta, + 0, + 20*FRACUNIT + ))) + .x(FixedToFloat(Easing_OutBack( + goner_background.miles_electric_delta, + 0, + 3*FRACUNIT + ))); } - if (y != 0) + + if (goner_background.miles_electric_delta < FRACUNIT) { - miles = miles.y(y); + goner_background.miles_electric_delta += renderdeltatics/(TICRATE/3); + if (goner_background.miles_electric_delta > FRACUNIT) + goner_background.miles_electric_delta = FRACUNIT; } bool drawarms = ( goner_background.miles_electric - && !goner_background.NeutralMouthCheck(GONERSPEAKER_TAILS) + && milesfocus ); if (drawarms) { @@ -854,7 +990,7 @@ static void M_GonerDrawer(void) { srb2::Draw drawer = srb2::Draw(); - float newy = BASEVIDHEIGHT/2 + (3*12); + float newy = currentMenu->y - 12; boolean first = true; int lastspeaker = MAXGONERSPEAKERS; @@ -927,7 +1063,7 @@ static void M_GonerDrawer(void) auto speaker = goner_speakers[element.speaker]; srb2::Draw line = drawer - .xy(BASEVIDWIDTH/4 + speaker.offset, newy) + .xy(BASEVIDWIDTH/4 + speaker.offset + 3, newy) .flags(flags); line From f5722b7569f2b6e1547b30751860875df1298f5c Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 01:10:37 +0000 Subject: [PATCH 52/64] Intro adjustments - Force to play when selecting "START GAME" from the Goner Setup - Make far shorter on the First Boot, obscuring the extended Kart Krew vroom, to prevent it playing identically twice - Prevent skipping the intro at all if Ring Cup hasn't been unlocked - Both for the shortened initial startup and the START GAME first encounter - It already clears dirmenu if relevant, don't do that twice --- src/d_netcmd.c | 3 --- src/f_finale.c | 14 ++++++++++++-- src/menus/main-goner.cpp | 2 ++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 386182a4a..b6825d3e3 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -4795,9 +4795,6 @@ static void Command_Playintro_f(void) if (netgame) return; - if (dirmenu) - closefilemenu(true); - F_StartIntro(); } diff --git a/src/f_finale.c b/src/f_finale.c index 66eea452d..f1aa884f2 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -303,6 +303,8 @@ static tic_t introscenetime[NUMINTROSCENES] = // custom intros void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer); +static boolean skippableallowed = true; + void F_StartIntro(void) { if (gamestate) @@ -315,6 +317,11 @@ void F_StartIntro(void) D_ClearState(); } + skippableallowed = ( + gamestartchallenge >= MAXUNLOCKABLES + || (gamedata && gamedata->unlocked[gamestartchallenge]) + ); + M_ClearMenus(false); D_SetDeferredStartTitle(false); @@ -459,7 +466,7 @@ void F_IntroTicker(void) timetonext--; - if (D_IsDeferredStartTitle()) + if (skippableallowed && D_IsDeferredStartTitle()) { D_StartTitle(); return; @@ -468,7 +475,10 @@ void F_IntroTicker(void) if (timetonext <= 0) { intro_scenenum++; - if (intro_scenenum == NUMINTROSCENES) + if (intro_scenenum == (M_GameTrulyStarted() + ? NUMINTROSCENES + : INTROSCENE_KREW) + ) { D_StartTitle(); // Custom built fade to skip the to-black diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 7040cd9cb..b3f5adeb8 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -5,6 +5,7 @@ #include "../m_cond.h" #include "../r_skins.h" #include "../s_sound.h" +#include "../f_finale.h" #include "../music.h" #include "../p_local.h" // P_AutoPause #include "../st_stuff.h" // faceprefix @@ -1178,6 +1179,7 @@ static void M_GonerConclude(INT32 choice) gamedata->gonerlevel = GDGONER_DONE; + F_StartIntro(); M_ClearMenus(true); M_GonerResetText(); } From ca6ebd89d8741fb2f416d3f02839b9def9005165 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 13:09:03 +0000 Subject: [PATCH 53/64] Minor dialogue/Tails posing updates - Assign poses for Outro and GDQ Passwords - Flicker camera when showing visuals after using a GDQ Password --- src/menus/main-goner.cpp | 42 +++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index b3f5adeb8..c8c8a745c 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -197,11 +197,15 @@ public: fixed_t x, focusx; fixed_t y, focusy; + int darkframes; + GonerBGData() { x = focusx = 0; y = focusy = 0; + darkframes = 0; + focuslast = MAXGONERSPEAKERS; focusdelta = 0; @@ -254,6 +258,9 @@ public: focuslast = focuscurrent; } + if (darkframes) + darkframes--; + if (miles_timetoblink == 0) { if (miles_next_timetoblink) @@ -461,7 +468,6 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "So you're right. I wonder if it has anything to do with that outburst."); - LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Alright, Metal! I don't remember your specifications offhand. "\ "First things first, go ahead and set up your "\ @@ -475,6 +481,7 @@ void M_AddGonerLines(void) LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Ah, you can see us now. Good."); } + LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "Now, calibrate your ""\x87""Sound Options""\x80""."); @@ -600,6 +607,7 @@ void M_AddGonerLines(void) { if (!leftoff) { + LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, "And... the training data is completed."); } @@ -607,18 +615,27 @@ void M_AddGonerLines(void) "It's kind of funny, actually."); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, "Oh? Care to elucidate, Prower?"); + LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, - "No matter how much time we took getting here, a machine like Metal can play it back in minutes."); + "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, - "It could have been five days or five years of development on our ""\x82""Ring Racers""\x80"", and that barely matters."); + "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, - "Ha! As if. I'd like to think our partnership hasn't felt that long."); - LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, - "But yes. Perhaps now you have a better appreciation of what we're building here, Metal."); + "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, + "But yes. Perhaps now you have a better appreciation of what "\ + "we're building here, Metal."); + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/5, "Now, I'm willing to let bygones be bygones."); LinesToDigest.emplace_front(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."); + "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, "We've kept the keys from you long enough!"); break; @@ -856,6 +873,9 @@ void M_DrawGonerBack(void) .height(BASEVIDHEIGHT) .fill(31); + if (goner_background.darkframes) + return; + drawer = drawer.xy( FixedToFloat(Easing_InOutCubic( goner_background.focusdelta, @@ -1200,13 +1220,18 @@ void M_GonerGDQ(boolean opinion) { LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/2, "Why wouldn't you save the frames..?"); + + LinesToDigest.emplace_front(0, Miles_Look_Camera); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, 0, "Don't mind him. Good luck on the run!"); + LinesToDigest.emplace_front(0, Miles_Look_Electric); } else // Save The Frames { + LinesToDigest.emplace_front(0, Miles_Electric_Lower); LinesToDigest.emplace_front(GONERSPEAKER_TAILS, TICRATE/2, "But what about all the little animals..."); + LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, 0, "It's just logical. I know you'll conquer this run."); } @@ -1214,6 +1239,9 @@ void M_GonerGDQ(boolean opinion) if (gamedata->gonerlevel <= GDGONER_TUTORIAL) { + if (gamedata->gonerlevel <= GDGONER_VIDEO) + goner_background.darkframes = 5; // handle abrupt transition + goner_levelworking = gamedata->gonerlevel = GDGONER_TUTORIAL; } } From 73ff45458e7dff0187a2f7d730720c09dbcbd780 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 14:10:55 +0000 Subject: [PATCH 54/64] Quit prompt updates for when game hasn't truly started - More clinical wording - Guarantee never auto-show when Playing() --- src/k_menufunc.c | 2 +- src/menus/main-1.c | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 5bbad7122..6760c7850 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -759,7 +759,7 @@ void M_GoBack(INT32 choice) M_SetupNextMenu(currentMenu->prevMenu, false); } - else if (M_GameTrulyStarted()) + else if (Playing() || M_GameTrulyStarted()) M_ClearMenus(true); else // No returning to the title screen. M_QuitSRB2(-1); diff --git a/src/menus/main-1.c b/src/menus/main-1.c index f7108a606..12f547000 100644 --- a/src/menus/main-1.c +++ b/src/menus/main-1.c @@ -18,6 +18,7 @@ #include "../z_zone.h" #include "../i_video.h" // I_FinishUpdate #include "../i_system.h" // I_Sleep +#include "../m_cond.h" // M_GameTrulyStarted menuitem_t MainMenu[] = { @@ -79,8 +80,37 @@ void M_QuitSRB2(INT32 choice) { // We pick index 0 which is language sensitive, or one at random, // between 1 and maximum number. + // ------------------------------------------------------------// + // ...no we don't! We haven't for ages! + // But I'm leaving that comment in, unmodified, because it dates + // ALL the way back to the original 1993 Doom source publication. + // One act of kindness has far-reaching consequences for so many + // people. It's a week until christmas as I'm writing this -- + // for those who read this, what act of kindness can you bring + // to others? ~toast 181223 + (void)choice; - M_StartMessage("Quit Game", "Are you sure you want to quit playing?\n", &M_QuitResponse, MM_YESNO, "Leave the game", "No, I want to go back!"); + + if (M_GameTrulyStarted()) + { + M_StartMessage( + "Quit Game", + "Are you sure you want to quit playing?\n", + &M_QuitResponse, MM_YESNO, + "Leave the game", + "No, I want to go back!" + ); + + return; + } + + M_StartMessage( + "Exit Program", + "Are you sure you want to quit?\n", + &M_QuitResponse, MM_YESNO, + "Yes", + "Cancel" + ); } void M_QuitResponse(INT32 ch) From c63ebf212977ea262f78171b19e7071984678b21 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 14:25:10 +0000 Subject: [PATCH 55/64] M_DrawMenuTyping: Update informational text, since you can no longer automatically switch between Virtual Keyboard and standard typing while the dialog is open. --- src/k_menudraw.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index be3229748..01259d02d 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -617,11 +617,17 @@ static void M_DrawMenuTyping(void) // Some contextual stuff if (menutyping.keyboardtyping) { - V_DrawThinString(returnx, y, V_GRAYMAP, "Type using your keyboard. Press Enter to confirm & exit.\nUse your controller or any directional input to use the Virtual Keyboard.\n"); + V_DrawThinString(returnx, y, V_GRAYMAP, + "Type using your keyboard. Press Enter to confirm & exit." + //"\nPress any button on your controller to use the Virtual Keyboard." + ); } else { - V_DrawThinString(returnx, y, V_GRAYMAP, "Type using the Virtual Keyboard. Use the \'OK\' button to confirm & exit.\nPress any keyboard key not bound to a control to use it."); + V_DrawThinString(returnx, y, V_GRAYMAP, + "Type using the Virtual Keyboard. Use the \'OK\' button to confirm & exit." + //"\nPress any keyboard key to type normally." + ); } } From 55e3abf58d3c1497fbb36c407c841b79d3dcf348 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 14:30:58 +0000 Subject: [PATCH 56/64] Teach players how to interact with Goner Setup just before input is first required - Checks for whether a Gamepad is connected - If yes, says you can use "your Gamepad" - If no, says you can use "Enter, ESC, and the Arrow Keys" - Show ENTER and ESC on any Message prompt (instead of A/B/X) if no Gamepad is connected - Add M_DrawMediocreKeyboardKey, a genericisation of the previous entry in the series' Time Attack HUD --- src/k_menudraw.c | 79 +++++++++++++++++++++++++++++++--------- src/menus/main-goner.cpp | 30 +++++++++++++++ 2 files changed, 91 insertions(+), 18 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 01259d02d..c8e67e29a 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -632,6 +632,31 @@ static void M_DrawMenuTyping(void) } +static void M_DrawMediocreKeyboardKey(const char *text, INT32 *workx, INT32 worky, boolean push, boolean rightaligned) +{ + INT32 buttonwidth = V_StringWidth(text, 0) + 2; + + if (rightaligned) + { + (*workx) -= buttonwidth; + } + + if (push) + { + worky += 2; + } + else + { + V_DrawFill((*workx)-1, worky+10, buttonwidth, 2, 24); + } + + V_DrawFill((*workx)-1, worky, buttonwidth, 10, 16); + V_DrawString( + (*workx), worky + 1, + 0, text + ); +} + // Draw the message popup submenu void M_DrawMenuMessage(void) { @@ -658,6 +683,10 @@ void M_DrawMenuMessage(void) INT32 workx = x + menumessage.x; INT32 worky = y + menumessage.y; + boolean standardbuttons = ( + cv_currprofile.value != -1 || G_GetNumAvailableGamepads() + ); + boolean push; if (menumessage.closing) @@ -678,19 +707,26 @@ void M_DrawMenuMessage(void) workx -= 2; - workx -= SHORT(kp_button_x[1][0]->width); - K_drawButton( - workx * FRACUNIT, worky * FRACUNIT, - 0, kp_button_x[1], - push - ); + if (standardbuttons) + { + workx -= SHORT(kp_button_x[1][0]->width); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_x[1], + push + ); - workx -= SHORT(kp_button_b[1][0]->width); - K_drawButton( - workx * FRACUNIT, worky * FRACUNIT, - 0, kp_button_b[1], - push - ); + workx -= SHORT(kp_button_b[1][0]->width); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_b[1], + push + ); + } + else + { + M_DrawMediocreKeyboardKey("ESC", &workx, worky, push, true); + } if (menumessage.confirmstr) { @@ -710,12 +746,19 @@ void M_DrawMenuMessage(void) workx -= 2; } - workx -= SHORT(kp_button_a[1][0]->width); - K_drawButton( - workx * FRACUNIT, worky * FRACUNIT, - 0, kp_button_a[1], - push - ); + if (standardbuttons) + { + workx -= SHORT(kp_button_a[1][0]->width); + K_drawButton( + workx * FRACUNIT, worky * FRACUNIT, + 0, kp_button_a[1], + push + ); + } + else + { + M_DrawMediocreKeyboardKey("ENTER", &workx, worky, push, true); + } } x -= 4; diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index c8c8a745c..cb7efb5bf 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -14,6 +14,7 @@ #include "../m_random.h" #include "../r_main.h" #include "../m_easing.h" +#include "../g_input.h" #include @@ -399,6 +400,32 @@ void M_GonerResetText(void) goner_scrollend = -1; } +static void Initial_Control_Info(void) +{ + if (cv_currprofile.value != -1) + return; + + auto line = GonerChatLine(GONERSPEAKER_TAILS, 0, + va("You should be able to use ""\x86""%s""\x80"" to operate this menu.", + (!G_GetNumAvailableGamepads() + ? "Enter, ESC, and the Arrow Keys" + : "your Gamepad" + ) + ) + ); + + if (LinesToDigest.empty()) + { + LinesToDigest.emplace_front(line); + return; + } + + LinesToDigest.emplace_after( + LinesToDigest.begin(), + line + ); +} + void M_AddGonerLines(void) { SRB2_ASSERT(LinesToDigest.empty()); @@ -472,6 +499,9 @@ void M_AddGonerLines(void) "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); + break; } case GDGONER_SOUND: From 83025f10e95439a9986035f3542d270d45de94c9 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 14:33:11 +0000 Subject: [PATCH 57/64] M_DrawMenuTyping: Forgot to commit y update --- src/k_menudraw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index c8e67e29a..b4ac55246 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -607,7 +607,7 @@ static void M_DrawMenuTyping(void) #undef BUTTONHEIGHT - y = 175; + y = 187; if (menutyping.menutypingfade < 9) { From b308a81e9dfa06767460dbeb4867939b6e5a43b3 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 16:29:57 +0000 Subject: [PATCH 58/64] Clean up interactions between Goner Setup and restoreMenu behaviour --- src/k_menu.h | 1 + src/k_menufunc.c | 12 ++++++++++-- src/menus/options-profiles-1.c | 3 +-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 404e69b76..228f6d928 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -648,6 +648,7 @@ boolean M_MenuExtraPressed(UINT8 pid); boolean M_MenuExtraHeld(UINT8 pid); void M_StartControlPanel(void); +void M_ValidateRestoreMenu(void); menu_t *M_SpecificMenuRestore(menu_t *torestore); void M_ClearMenus(boolean callexitmenufunc); void M_SelectableClearMenus(INT32 choice); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 6760c7850..c16737440 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -434,6 +434,12 @@ void M_PlayMenuJam(void) #undef IsCurrentlyPlaying +void M_ValidateRestoreMenu(void) +{ + if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef) + restoreMenu = &MainDef; +} + // // M_SpecificMenuRestore // @@ -587,7 +593,10 @@ void M_StartControlPanel(void) { // Are you ready for the First Boot Experience? M_ResetOptions(); + currentMenu = &MAIN_GonerDef; + restoreMenu = NULL; + M_PlayMenuJam(); } else if (cv_currprofile.value == -1) // Only ask once per session. @@ -618,8 +627,7 @@ void M_StartControlPanel(void) } else { - if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef) - restoreMenu = &MainDef; + M_ValidateRestoreMenu(); currentMenu = M_SpecificMenuRestore(M_InterruptMenuWithChallenges(restoreMenu)); restoreMenu = NULL; diff --git a/src/menus/options-profiles-1.c b/src/menus/options-profiles-1.c index 10b842e31..beb801aa6 100644 --- a/src/menus/options-profiles-1.c +++ b/src/menus/options-profiles-1.c @@ -47,8 +47,7 @@ void M_FirstPickProfile(INT32 c) PR_ApplyProfile(optionsmenu.profilen, 0); - if (restoreMenu == NULL) - restoreMenu = &MainDef; + M_ValidateRestoreMenu(); M_SetupNextMenu(M_SpecificMenuRestore(M_InterruptMenuWithChallenges(restoreMenu)), false); restoreMenu = NULL; From c6edfa0de5e8abfc28933664f41d015c53cd2139 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 17:01:29 +0000 Subject: [PATCH 59/64] Goner Setup Polish - Imply passage of time when returning from Tutorial to Outro by resetting all the background variables - Add an additive layer to the Options cogs, so they show up over an extremely dark background - Metal Sonic will not focus on Tails/Eggman until video is restored - Don't draw the Miles Electric transparent unless it's completely in its upright position --- src/k_menu.h | 1 + src/k_menudraw.c | 20 ++++++--- src/k_menufunc.c | 1 + src/menus/main-goner.cpp | 90 +++++++++++++++++++++++----------------- 4 files changed, 68 insertions(+), 44 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 228f6d928..e750146cd 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -207,6 +207,7 @@ extern menu_t MAIN_GonerDef; void M_GonerTick(void); void M_GonerBGTick(void); +void M_GonerBGImplyPassageOfTime(void); void M_DrawGonerBack(void); void M_GonerProfile(INT32 choice); void M_GonerTutorial(INT32 choice); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b4ac55246..8e73ad320 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3930,10 +3930,13 @@ void M_DrawMPServerBrowser(void) // Draws the cogs and also the options background! void M_DrawOptionsCogs(void) { + boolean trulystarted = M_GameTrulyStarted(); + UINT32 tick = ((optionsmenu.ticker/10) % 3) + 1; + // the background isn't drawn outside of being in the main menu state. - if (gamestate == GS_MENU && M_GameTrulyStarted()) + if (gamestate == GS_MENU && trulystarted) { - patch_t *back[3] = {W_CachePatchName("OPT_BG1", PU_CACHE), W_CachePatchName("OPT_BG2", PU_CACHE), W_CachePatchName("OPT_BG3", PU_CACHE)}; + patch_t *back = W_CachePatchName(va("OPT_BG%u", tick), PU_CACHE); INT32 tflag = 0; UINT8 *c; UINT8 *c2; // colormap for the one we're changing @@ -3941,19 +3944,24 @@ void M_DrawOptionsCogs(void) if (optionsmenu.fade) { c2 = R_GetTranslationColormap(TC_DEFAULT, optionsmenu.lastcolour, GTC_CACHE); - V_DrawFixedPatch(0, 0, FRACUNIT, 0, back[(optionsmenu.ticker/10) %3], c2); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, back, c2); // prepare fade flag: tflag = min(V_90TRANS, (optionsmenu.fade)<gonerlevel = GDGONER_OUTRO; + M_GonerBGImplyPassageOfTime(); } } diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index cb7efb5bf..48b663e2c 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -203,7 +203,7 @@ public: GonerBGData() { x = focusx = 0; - y = focusy = 0; + y = focusy = 20*FRACUNIT; darkframes = 0; @@ -228,39 +228,46 @@ public: void Tick() { - gonerspeakers_t focuscurrent = MAXGONERSPEAKERS; - if (currentMenu == &MAIN_GonerDef && !LinesOutput.empty()) - { - focuscurrent = LinesOutput.front().speaker; - } - - if (focuslast != focuscurrent) - { - focusdelta = FRACUNIT; - x = focusx; - y = focusy; - - switch (focuscurrent) - { - case GONERSPEAKER_TAILS: - focusx = -10*FRACUNIT; - focusy = 0; - break; - case GONERSPEAKER_EGGMAN: - focusx = 10*FRACUNIT; - focusy = 0; - break; - default: - focusx = 0; - focusy = 20*FRACUNIT; - break; - } - - focuslast = focuscurrent; - } - + // This is the visual feed sputtering in and out. if (darkframes) darkframes--; + else if (gamedata->gonerlevel > GDGONER_VIDEO) + { + // Everything in here is Metal Sonic's response to stimulus. + + gonerspeakers_t focuscurrent = MAXGONERSPEAKERS; + if (currentMenu == &MAIN_GonerDef && !LinesOutput.empty()) + { + focuscurrent = LinesOutput.front().speaker; + } + + if (focuslast != focuscurrent) + { + focusdelta = FRACUNIT; + x = focusx; + y = focusy; + + switch (focuscurrent) + { + case GONERSPEAKER_TAILS: + focusx = -10*FRACUNIT; + focusy = 0; + break; + case GONERSPEAKER_EGGMAN: + focusx = 10*FRACUNIT; + focusy = 0; + break; + default: + focusx = 0; + focusy = 20*FRACUNIT; + break; + } + + focuslast = focuscurrent; + } + } + + // Everything below this is the real world. if (miles_timetoblink == 0) { @@ -630,6 +637,7 @@ void M_AddGonerLines(void) 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."); + LinesToDigest.emplace_front(0, Miles_Look_Electric); break; } @@ -637,7 +645,6 @@ void M_AddGonerLines(void) { if (!leftoff) { - LinesToDigest.emplace_front(0, Miles_Look_Electric); LinesToDigest.emplace_front(GONERSPEAKER_EGGMAN, TICRATE/3, "And... the training data is completed."); } @@ -764,6 +771,11 @@ void M_GonerBGTick(void) goner_background.Tick(); } +void M_GonerBGImplyPassageOfTime(void) +{ + goner_background = GonerBGData(); +} + void M_GonerTick(void) { static bool first = true; @@ -1010,17 +1022,19 @@ void M_DrawGonerBack(void) ))); } - if (goner_background.miles_electric_delta < FRACUNIT) + bool drawarms = false; + + if (goner_background.miles_electric_delta == FRACUNIT) + { + drawarms = (milesfocus && goner_background.miles_electric); + } + else { goner_background.miles_electric_delta += renderdeltatics/(TICRATE/3); if (goner_background.miles_electric_delta > FRACUNIT) goner_background.miles_electric_delta = FRACUNIT; } - bool drawarms = ( - goner_background.miles_electric - && milesfocus - ); if (drawarms) { miles.patch("GON_TA1"); From fa6beeaaea31d4d3db1a53b06d88faa3327db379 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 17:27:47 +0000 Subject: [PATCH 60/64] GT_TUTORIAL: Allow MT_RANDOMITEM to respawn --- src/p_inter.c | 2 +- src/p_mobj.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/p_inter.c b/src/p_inter.c index f63d485af..e25ec3a7e 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -289,7 +289,7 @@ static void P_ItemPop(mobj_t *actor) */ // Here at mapload in battle? - if (!(gametyperules & GTR_CIRCUIT) && (actor->flags2 & MF2_BOSSFLEE)) + if (gametype != GT_TUTORIAL && !(gametyperules & GTR_CIRCUIT) && (actor->flags2 & MF2_BOSSFLEE)) { numgotboxes++; diff --git a/src/p_mobj.c b/src/p_mobj.c index 347e7dfd9..55a94c149 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12557,8 +12557,8 @@ void P_RespawnBattleBoxes(void) { thinker_t *th; - if (gametyperules & GTR_CIRCUIT) - return; + /*if (gametyperules & GTR_CIRCUIT) -- already guarding the call + return;*/ tic_t setduration = (nummapboxes > 1) ? TICRATE : (2*TICRATE); From 322e4d3447f61906d0783fc0a2d9b32992526e79 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 17:52:06 +0000 Subject: [PATCH 61/64] M_DrawHorizontalMenu: Final entry flashes when not selected --- src/k_menudraw.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8e73ad320..923c0423b 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1208,10 +1208,14 @@ void M_DrawHorizontalMenu(void) { V_DrawFill(x-2, y + 16, 4, 4, 0); } + else if (i >= currentMenu->numitems) + { + V_DrawFill(x-1, y + 17, 2, 2, 20); + } else { V_DrawFill(x-1, y + 17, 2, 2, - (i >= currentMenu->numitems) ? 20 : 10 + (i == final && skullAnimCounter/5) ? 73 : 10 ); } } From f955c639e8bc0bdcf69087ab2535fcf1fe23f712 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 19:57:50 +0000 Subject: [PATCH 62/64] Profile setup music changes - Plays unconditionally, even while mid-game - ...except on the Goner Setup, per direct request --- src/k_menufunc.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index f3cb098fd..0e0b98b42 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -365,12 +365,25 @@ void M_PlayMenuJam(void) return; } - gdmusic_t override = musicstatepermitted ? gamedata->musicstate : 0; - - if (Playing() || soundtest.playing) + if (soundtest.playing) return; - if (M_GameTrulyStarted() == false) + const boolean trulystarted = M_GameTrulyStarted(); + const boolean profilemode = ( + trulystarted + && optionsmenu.profilemenu + && !optionsmenu.resetprofilemenu + ); + + if (!profilemode && Playing()) + { + if (optionsmenu.resetprofilemenu) + Music_Stop("menu"); + + return; + } + + if (!trulystarted) { if (M_GonerMusicPlayable() && NotCurrentlyPlaying("_GONER")) { @@ -381,6 +394,8 @@ void M_PlayMenuJam(void) return; } + gdmusic_t override = musicstatepermitted ? gamedata->musicstate : 0; + if (refMenu != NULL && refMenu->music != NULL) { if (refMenu->music[0] == '.' && refMenu->music[1] == '\0') @@ -419,7 +434,7 @@ void M_PlayMenuJam(void) return; } - if (cv_menujam_update.value) + if (!profilemode && cv_menujam_update.value) { CV_AddValue(&cv_menujam, 1); CV_SetValue(&cv_menujam_update, 0); From c93e02af4aec31b348183ec34c6369230b5cfb7a Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 20:14:23 +0000 Subject: [PATCH 63/64] Additional Goner BG polish - Helper functions for eased X/Y - Don't jump Metal's camera around when doing things rapidly - Don't have ctrl alt del open mouths when not on the main Goner menu itself --- src/menus/main-goner.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 48b663e2c..82d8f5209 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -221,11 +221,22 @@ public: bool NeutralMouthCheck() { - return (LinesOutput.empty() + return (currentMenu != &MAIN_GonerDef + || LinesOutput.empty() || goner_typewriter.textDone || goner_typewriter.text.empty()); } + fixed_t GetX() + { + return Easing_InOutCubic(focusdelta, focusx, x); + } + + fixed_t GetY() + { + return Easing_InOutCubic(focusdelta, focusy, y); + } + void Tick() { // This is the visual feed sputtering in and out. @@ -243,9 +254,9 @@ public: if (focuslast != focuscurrent) { + x = GetX(); + y = GetY(); focusdelta = FRACUNIT; - x = focusx; - y = focusy; switch (focuscurrent) { @@ -919,16 +930,8 @@ void M_DrawGonerBack(void) return; drawer = drawer.xy( - FixedToFloat(Easing_InOutCubic( - goner_background.focusdelta, - goner_background.focusx, - goner_background.x - )), - FixedToFloat(Easing_InOutCubic( - goner_background.focusdelta, - goner_background.focusy, - goner_background.y - )) + FixedToFloat(goner_background.GetX()), + FixedToFloat(goner_background.GetY()) ); if (goner_background.focusdelta && renderdeltatics <= 2*FRACUNIT) From 2c815258c022694416842039b648b45c192d3c8e Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 18 Dec 2023 20:35:46 +0000 Subject: [PATCH 64/64] P_InitPlayers: In GT_TUTORIAL, force the presence of Mystic Melody if it's unlocked --- src/p_setup.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/p_setup.c b/src/p_setup.c index a3fc5f9f2..2fdb86d54 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7847,8 +7847,7 @@ static void P_InitCamera(void) static void P_InitPlayers(void) { - UINT8 i; - INT32 skin = -1; + INT32 i, skin = -1, follower = -1; // Make sure objectplace is OFF when you first start the level! OP_ResetObjectplace(); @@ -7871,6 +7870,18 @@ static void P_InitPlayers(void) { skin = 0; } + + if (netgame) + ; // shouldn't happen but at least attempt to sync if it does + else for (i = 0; i < numfollowers; i++) + { + if (followers[i].hornsound != sfx_melody) + continue; + + if (K_FollowerUsable(i)) + follower = i; + break; + } } for (i = 0; i < MAXPLAYERS; i++) @@ -7885,8 +7896,10 @@ static void P_InitPlayers(void) { players[i].skin = skin; players[i].skincolor = skins[skin].prefcolor; - players[i].followerskin = -1; - // followercolor can be left alone for hopefully obvious reasons + + players[i].followerskin = follower; + if (follower != -1) + players[i].followercolor = followers[follower].defaultcolor; } G_SpawnPlayer(i);