From 57b1ad15a5da2f2a5ef19bfa27c602e5c75fbcaa Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 26 Mar 2023 21:59:38 +0100 Subject: [PATCH 1/9] GameTypeRules: Add GTR_NOPOSITION Says on the tin - introtime == starttime. --- src/deh_tables.c | 1 + src/doomstat.h | 19 ++++++++++--------- src/k_kart.c | 7 ++++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 7d4f8d990..cfe834370 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5854,6 +5854,7 @@ const char *const GAMETYPERULE_LIST[] = { "NOMP", "NOCUPSELECT", + "NOPOSITION", NULL }; diff --git a/src/doomstat.h b/src/doomstat.h index d72ef266b..b17efb095 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -546,10 +546,10 @@ enum GameTypeRules // Bonus gametype rules GTR_PRISONS = 1<<10, // Can enter Prison Break mode - GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT) - GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT) - GTR_SPECIALSTART = 1<<13, // White fade instant start - GTR_BOSS = 1<<14, // Boss intro and spawning + GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT) + GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT) + GTR_SPECIALSTART = 1<<13, // White fade instant start + GTR_BOSS = 1<<14, // Boss intro and spawning // General purpose rules GTR_POINTLIMIT = 1<<15, // Reaching point limit ends the round @@ -557,12 +557,13 @@ enum GameTypeRules GTR_OVERTIME = 1<<17, // Allow overtime behavior GTR_ENCORE = 1<<18, // Alternate Encore mirroring, scripting, and texture remapping - GTR_TEAMS = 1<<19, // Teams are forced on - GTR_NOTEAMS = 1<<20, // Teams are forced off - GTR_TEAMSTARTS = 1<<21, // Use team-based start positions + GTR_TEAMS = 1<<19, // Teams are forced on + GTR_NOTEAMS = 1<<20, // Teams are forced off + GTR_TEAMSTARTS = 1<<21, // Use team-based start positions - GTR_NOMP = 1<<22, // No multiplayer - GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup. + GTR_NOMP = 1<<22, // No multiplayer + GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup. + GTR_NOPOSITION = 1<<24, // No POSITION // free: to and including 1<<31 }; diff --git a/src/k_kart.c b/src/k_kart.c index 236d53ce4..675b4d1a7 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -186,7 +186,12 @@ void K_TimerInit(void) } } - starttime = (introtime + (3*TICRATE)) + ((2*TICRATE) + (numbulbs * bulbtime)); // Start countdown time, + buffer time + starttime = introtime; + if (!(gametyperules & GTR_NOPOSITION)) + { + // Start countdown time + buffer time + starttime += ((3*TICRATE) + ((2*TICRATE) + (numbulbs * bulbtime))); + } } K_BattleInit(domodeattack); From a0cdc6b71af84e1415c03237d0e173df3a959528 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 26 Mar 2023 22:07:44 +0100 Subject: [PATCH 2/9] FREE PLAY: Make a little more consistent - Cooperative gametypes do not count as FREE PLAY - Sealed Stars count as a Cooperative gametype - Fix Battle Fullscreen having had inverted presence of FREE PLAY since bosses were added --- src/k_hud.c | 5 +++-- src/k_kart.c | 5 +++++ src/m_cond.c | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index ddce61c2b..229cadcfb 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4294,6 +4294,7 @@ static void K_drawBattleFullscreen(void) } // FREE PLAY? + if (K_Cooperative() == false) { UINT8 i; @@ -4306,7 +4307,7 @@ static void K_drawBattleFullscreen(void) break; } - if (i != MAXPLAYERS) + if (i == MAXPLAYERS) K_drawKartFreePlay(); } } @@ -5180,7 +5181,7 @@ void K_drawKartHUD(void) V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. - if (islonesome) + if (islonesome && K_Cooperative() == false) K_drawKartFreePlay(); if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1)) diff --git a/src/k_kart.c b/src/k_kart.c index 675b4d1a7..84bd29929 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11699,6 +11699,11 @@ boolean K_Cooperative(void) return true; } + if (specialstageinfo.valid) + { + return true; + } + return false; } diff --git a/src/m_cond.c b/src/m_cond.c index 5c7016c7a..4e3f31392 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -628,6 +628,10 @@ static boolean M_NotFreePlay(player_t *player) { UINT8 i; + // Rounds that permit Cooperative play can be played by yourself without being FREE PLAY. + if (K_Cooperative()) + return true; + for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] == false || players[i].spectator == true) From 24d8b20124dd65ed6c081995c150d4120628f757 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 26 Mar 2023 22:20:16 +0100 Subject: [PATCH 3/9] GT_TUTORIAL - Replaces `tutorialmode`. - Forces gamespeed to Easy, with no POSITION. - Laps are currently disabled as well, but this can be changed if necessary. - Hides Free Play. - Does not count as a played round (except for Chao Keys). `tutorialmap` has also been removed. This will be replaced in a later commit with something that plays nicer with Ring Racers' existing systems. --- src/d_main.c | 3 --- src/d_netcmd.c | 2 -- src/deh_soc.c | 5 ----- src/doomstat.h | 13 ++++++------- src/f_finale.c | 6 +++--- src/g_game.c | 35 ++++++++++++++++++++++++----------- src/k_grandprix.c | 6 ++++++ src/k_hud.c | 2 +- src/lua_script.c | 6 ------ src/r_main.c | 2 +- 10 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index b754523c7..266158368 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1049,9 +1049,6 @@ void D_ClearState(void) if (rendermode != render_none) V_SetPaletteLump("PLAYPAL"); - // The title screen is obviously not a tutorial! (Unless I'm mistaken) - tutorialmode = false; - cursongcredit.def = NULL; S_StopSounds(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 7ce3e42f7..53e9e9a7f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3016,8 +3016,6 @@ static void Command_Map_f(void) } } - tutorialmode = false; // warping takes us out of tutorial mode - D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, fromlevelselect); Z_Free(realmapname); diff --git a/src/deh_soc.c b/src/deh_soc.c index c96bbbaac..ad3d46145 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3211,11 +3211,6 @@ void readmaincfg(MYFILE *f, boolean mainfile) bootmap = Z_StrDup(word2); //titlechanged = true; } - else if (fastcmp(word, "TUTORIALMAP")) - { - Z_Free(tutorialmap); - tutorialmap = Z_StrDup(word2); - } else if (fastcmp(word, "PODIUMMAP")) { Z_Free(podiummap); diff --git a/src/doomstat.h b/src/doomstat.h index b17efb095..6a31180a9 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -222,9 +222,6 @@ extern char * titlemap; extern boolean hidetitlepics; extern char * bootmap; //bootmap for loading a map on startup -extern char * tutorialmap; // map to load for tutorial -extern boolean tutorialmode; // are we in a tutorial right now? - extern char * podiummap; // map to load for podium extern boolean looptitle; @@ -499,6 +496,7 @@ enum GameType GT_BATTLE, GT_SPECIAL, GT_VERSUS, + GT_TUTORIAL, GT_FIRSTFREESLOT, GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this. @@ -578,10 +576,11 @@ enum GameTypeRules enum TypeOfLevel { // Gametypes - TOL_RACE = 0x0001, ///< Race - TOL_BATTLE = 0x0002, ///< Battle - TOL_BOSS = 0x0004, ///< Boss (variant of battle, but forbidden) - TOL_SPECIAL = 0x0008, ///< Special Stage (variant of race, but forbidden) + TOL_RACE = 0x0001, ///< Race + TOL_BATTLE = 0x0002, ///< Battle + TOL_BOSS = 0x0004, ///< Boss (variant of battle, but forbidden) + TOL_SPECIAL = 0x0008, ///< Special Stage (variant of race, but forbidden) + TOL_TUTORIAL = 0x0010, ///< Tutorial (variant of race, but forbidden) // Modifiers TOL_TV = 0x0100 ///< Midnight Channel specific: draw TV like overlay on HUD diff --git a/src/f_finale.c b/src/f_finale.c index a39d020c0..160ddc27b 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -2826,7 +2826,7 @@ static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length) INT32 gcs = 0; boolean suffixed = true; - if (!tag || !tag[0] || !tutorialmode) + if (!tag || !tag[0] || gametype == GT_TUTORIAL) return false; /* @@ -2859,7 +2859,7 @@ static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length) void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum) { INT32 nosuffixpromptnum = INT32_MAX, nosuffixpagenum = INT32_MAX; - INT32 tutorialpromptnum = (tutorialmode) ? TUTORIAL_PROMPT-1 : 0; + INT32 tutorialpromptnum = (gametype == GT_TUTORIAL) ? TUTORIAL_PROMPT-1 : 0; boolean suffixed = false, found = false; char suffixedtag[33]; @@ -2871,7 +2871,7 @@ void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum strncpy(suffixedtag, tag, 33); suffixedtag[32] = 0; - if (tutorialmode) + if (gametype == GT_TUTORIAL) suffixed = F_GetTextPromptTutorialTag(suffixedtag, 33); for (*promptnum = 0 + tutorialpromptnum; *promptnum < MAX_PROMPTS; (*promptnum)++) diff --git a/src/g_game.c b/src/g_game.c index 9e6b2cb72..5cb9d13f0 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -166,9 +166,6 @@ char * titlemap = NULL; boolean hidetitlepics = false; char * bootmap = NULL; //bootmap for loading a map on startup -char * tutorialmap = NULL; // map to load for tutorial -boolean tutorialmode = false; // are we in a tutorial right now? - char * podiummap = NULL; // map to load for podium boolean looptitle = true; @@ -3359,6 +3356,17 @@ static gametype_t defaultgametypes[] = 0, 0, }, + + // GT_TUTORIAL + { + "Tutorial", + "GT_TUTORIAL", + GTR_NOMP|GTR_NOCUPSELECT|GTR_NOPOSITION, + TOL_TUTORIAL, + int_none, + 0, + 0, + }, }; gametype_t *gametypes[MAXGAMETYPES+1] = @@ -3367,6 +3375,7 @@ gametype_t *gametypes[MAXGAMETYPES+1] = &defaultgametypes[GT_BATTLE], &defaultgametypes[GT_SPECIAL], &defaultgametypes[GT_VERSUS], + &defaultgametypes[GT_TUTORIAL], }; // @@ -3502,6 +3511,7 @@ tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"BATTLE",TOL_BATTLE}, {"BOSS",TOL_BOSS}, {"SPECIAL",TOL_SPECIAL}, + {"TUTORIAL",TOL_TUTORIAL}, {"TV",TOL_TV}, {NULL, 0} }; @@ -4207,16 +4217,19 @@ static void G_DoCompleted(void) if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) { - UINT8 roundtype = GDGT_CUSTOM; + if (gametype != GT_TUTORIAL) + { + UINT8 roundtype = GDGT_CUSTOM; - if (gametype == GT_RACE) - roundtype = GDGT_RACE; - else if (gametype == GT_BATTLE) - roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE); - else if (gametype == GT_SPECIAL || gametype == GT_VERSUS) - roundtype = GDGT_SPECIAL; + if (gametype == GT_RACE) + roundtype = GDGT_RACE; + else if (gametype == GT_BATTLE) + roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE); + else if (gametype == GT_SPECIAL || gametype == GT_VERSUS) + roundtype = GDGT_SPECIAL; - gamedata->roundsplayed[roundtype]++; + gamedata->roundsplayed[roundtype]++; + } gamedata->pendingkeyrounds++; // Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 213e108b5..73cc0d27b 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -715,6 +715,12 @@ boolean K_CanChangeRules(boolean allowdemos) return false; } + if (gametype == GT_TUTORIAL) + { + // Tutorials are locked down. + return false; + } + if (!allowdemos && demo.playback) { // We've already got our important settings! diff --git a/src/k_hud.c b/src/k_hud.c index 229cadcfb..639d8ac9b 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5181,7 +5181,7 @@ void K_drawKartHUD(void) V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. - if (islonesome && K_Cooperative() == false) + if (islonesome && K_Cooperative() == false && gametype != GT_TUTORIAL) K_drawKartFreePlay(); if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1)) diff --git a/src/lua_script.c b/src/lua_script.c index 6e34c9fbf..3a12c177b 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -216,12 +216,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"bootmap")) { lua_pushstring(L, bootmap); return 1; - } else if (fastcmp(word,"tutorialmap")) { - lua_pushstring(L, tutorialmap); - return 1; - } else if (fastcmp(word,"tutorialmode")) { - lua_pushboolean(L, tutorialmode); - return 1; } else if (fastcmp(word,"podiummap")) { lua_pushstring(L, podiummap); return 1; diff --git a/src/r_main.c b/src/r_main.c index 5731f7695..c8cfe4fb5 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1407,7 +1407,7 @@ boolean R_ViewpointHasChasecam(player_t *player) } } - if (player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode) + if (player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN) chasecam = true; // force chasecam on else if (player->spectator) // no spectator chasecam chasecam = false; // force chasecam off From e732f372586de8827d2afdd2805f97a8f8a5423c Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 26 Mar 2023 22:34:37 +0100 Subject: [PATCH 4/9] levelheader_t: `relevantskin` property - On level load for GT_TUTORIAL: - Forces to requested skin (or Eggman if not specified). - Named `relevantskin` and not `tutorialskin` in case it's useful to use the same apparatus later for Adventure. - Also sets default skincolor and disables follower. --- src/deh_soc.c | 5 +++++ src/doomstat.h | 1 + src/p_setup.c | 31 +++++++++++++++++++++++++++++++ src/r_skins.c | 6 ++++++ 4 files changed, 43 insertions(+) diff --git a/src/deh_soc.c b/src/deh_soc.c index ad3d46145..190001f80 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1122,6 +1122,11 @@ void readlevelheader(MYFILE *f, char * name) deh_strlcpy(mapheaderinfo[num]->zonttl, word2, sizeof(mapheaderinfo[num]->zonttl), va("Level header %d: zonetitle", num)); } + else if (fastcmp(word, "RELEVANTSKIN")) + { + deh_strlcpy(mapheaderinfo[num]->relevantskin, word2, + sizeof(mapheaderinfo[num]->relevantskin), va("Level header %d: relevantskin", num)); + } else if (fastcmp(word, "SCRIPTNAME")) { deh_strlcpy(mapheaderinfo[num]->scriptname, word2, diff --git a/src/doomstat.h b/src/doomstat.h index 6a31180a9..01f584dd4 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -427,6 +427,7 @@ struct mapheader_t UINT32 typeoflevel; ///< Combination of typeoflevel flags. UINT8 numlaps; ///< Number of laps in circuit mode, unless overridden. fixed_t gravity; ///< Map-wide gravity. + char relevantskin[SKINNAMESIZE+1]; ///< Skin to use for tutorial (if not provided, uses Eggman.) // Music information char musname[MAXMUSNAMES][7]; ///< Music tracks to play. First dimension is the track number, second is the music string. "" for no music. diff --git a/src/p_setup.c b/src/p_setup.c index 9082c2796..0ca14594b 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -389,6 +389,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) mapheaderinfo[num]->typeoflevel = 0; mapheaderinfo[num]->gravity = DEFAULT_GRAVITY; mapheaderinfo[num]->keywords[0] = '\0'; + mapheaderinfo[num]->relevantskin[0] = '\0'; mapheaderinfo[num]->musname[0][0] = 0; mapheaderinfo[num]->musname_size = 0; mapheaderinfo[num]->positionmus[0] = '\0'; @@ -7392,6 +7393,27 @@ static void P_InitCamera(void) static void P_InitPlayers(void) { UINT8 i; + INT32 skin = -1; + + // Are we forcing a character? + if (gametype == GT_TUTORIAL) + { + // Get skin from name. + if (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->relevantskin[0]) + { + skin = R_SkinAvailable(mapheaderinfo[gamemap-1]->relevantskin); + } + else + { + skin = R_SkinAvailable(DEFAULTSKIN); + } + + // Handle invalid case. + if (skin == -1) + { + skin = 0; + } + } for (i = 0; i < MAXPLAYERS; i++) { @@ -7400,6 +7422,15 @@ static void P_InitPlayers(void) players[i].mo = NULL; + // If we're forcing a character, do it now. + if (skin != -1) + { + players[i].skin = skin; + players[i].skincolor = skins[skin].prefcolor; + players[i].followerskin = -1; + // followercolor can be left alone for hopefully obvious reasons + } + if (!(gametyperules & GTR_CIRCUIT) && K_PodiumSequence() == false) { G_DoReborn(i); diff --git a/src/r_skins.c b/src/r_skins.c index 212256c21..9a9df4f88 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -236,6 +236,12 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins) return true; } + if (gametype == GT_TUTORIAL) + { + // Being forced to play as this character by the tutorial + return true; + } + // Determine if this character is supposed to be unlockable or not if (useplayerstruct && demo.playback) { From fbfb8a507d609bd71cf9352af426beb091f00d1f Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 26 Mar 2023 22:40:27 +0100 Subject: [PATCH 5/9] Extras menu: Add GT_TUTORIAL selection screen - Disabled if no maps available/unlocked - Has another exception for showing TEST RUN under all gametypes - Splitplayers is forbidden --- src/g_game.c | 1 + src/k_menu.h | 1 + src/k_menudraw.c | 7 +++++++ src/menus/extras-1.c | 22 ++++++++++++++++++++++ src/menus/play-online-host.c | 1 + src/menus/transient/level-select.c | 11 ++++++++--- 6 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 5cb9d13f0..526bf621f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3638,6 +3638,7 @@ INT16 G_GetFirstMapOfGametype(UINT8 pgametype) templevelsearch.typeoflevel = G_TOLFlag(pgametype); templevelsearch.cupmode = (!(gametypes[pgametype]->rules & GTR_NOCUPSELECT)); templevelsearch.timeattack = false; + templevelsearch.tutorial = false; templevelsearch.checklocked = true; if (templevelsearch.cupmode) diff --git a/src/k_menu.h b/src/k_menu.h index cd7bbbe68..2018e842a 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -733,6 +733,7 @@ typedef struct levelsearch_s { UINT32 typeoflevel; cupheader_t *cup; boolean timeattack; + boolean tutorial; boolean cupmode; boolean checklocked; } levelsearch_t; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 3ef4eeefd..27cf20c00 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2411,6 +2411,12 @@ 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; @@ -5183,6 +5189,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) templevelsearch.typeoflevel = G_TOLFlag(GT_RACE)|G_TOLFlag(GT_BATTLE); templevelsearch.cupmode = true; templevelsearch.timeattack = false; + templevelsearch.tutorial = false; templevelsearch.checklocked = false; M_DrawCupPreview(146, &templevelsearch); diff --git a/src/menus/extras-1.c b/src/menus/extras-1.c index 771f70036..eb2de1711 100644 --- a/src/menus/extras-1.c +++ b/src/menus/extras-1.c @@ -17,6 +17,9 @@ menuitem_t EXTRAS_Main[] = {IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!", NULL, {.routine = M_Challenges}, 0, 0}, + {IT_STRING | IT_CALL, "Tutorial", "Help Dr. Eggman and Tails test out their new Ring Racers.", + NULL, {.routine = M_LevelSelectInit}, 0, GT_TUTORIAL}, + {IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!", NULL, {.routine = M_Statistics}, 0, 0}, @@ -79,6 +82,25 @@ void M_InitExtras(INT32 choice) } } + // Tutorial + { + levelsearch_t templevelsearch; + UINT8 i = 0; + INT16 map; + + templevelsearch.cup = NULL; + templevelsearch.typeoflevel = G_TOLFlag(GT_TUTORIAL); + templevelsearch.cupmode = false; + templevelsearch.timeattack = false; + templevelsearch.tutorial = true; + templevelsearch.checklocked = true; + + map = M_GetFirstLevelInList(&i, &templevelsearch); + + EXTRAS_Main[extras_tutorial].status = (IT_STRING | + ((map == NEXTMAP_INVALID) ? IT_TRANSTEXT : IT_CALL)); + } + // Egg TV if (M_SecretUnlocked(SECRET_EGGTV, true)) { diff --git a/src/menus/play-online-host.c b/src/menus/play-online-host.c index 95ce0660e..9610154ef 100644 --- a/src/menus/play-online-host.c +++ b/src/menus/play-online-host.c @@ -100,6 +100,7 @@ void M_MPSetupNetgameMapSelect(INT32 choice) levellist.netgame = true; // Make sure we reset those levellist.levelsearch.timeattack = false; + levellist.levelsearch.tutorial = false; levellist.levelsearch.checklocked = true; cupgrid.grandprix = false; diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 234d6d539..4de5b0668 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -61,7 +61,7 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) return false; // Check for TOL (permits TEST RUN outside of time attack) - if ((levelsearch->timeattack || mapheaderinfo[mapnum]->typeoflevel) + if ((levelsearch->timeattack || levelsearch->tutorial || mapheaderinfo[mapnum]->typeoflevel) && !(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) return false; @@ -427,6 +427,7 @@ void M_LevelSelectInit(INT32 choice) // Make sure this is reset as we'll only be using this function for offline games! levellist.netgame = false; levellist.levelsearch.checklocked = true; + levellist.levelsearch.tutorial = (gt == GT_TUTORIAL); switch (currentMenu->menuitems[itemOn].mvar1) { @@ -498,7 +499,7 @@ void M_LevelSelected(INT16 add) { if (gamestate == GS_MENU) { - UINT8 ssplayers = cv_splitplayers.value-1; + UINT8 ssplayers = levellist.levelsearch.tutorial ? 0 : cv_splitplayers.value-1; netgame = false; multiplayer = true; @@ -543,7 +544,11 @@ void M_LevelSelected(INT16 add) D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - if (levellist.netgame == true) + if (levellist.levelsearch.tutorial) + { + restoreMenu = currentMenu; + } + else if (levellist.netgame == true) { restoreMenu = &PLAY_MP_OptSelectDef; } From 8d804872cf762297cd1efa0b5e18a4178a6b68dc Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 26 Mar 2023 22:41:21 +0100 Subject: [PATCH 6/9] M_LevelListFromGametype: Ensure music is continuous when Level Select is entered from Extras menu Also fixes the same issue for Online mode --- src/menus/transient/level-select.c | 54 +++++++++++++++++------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 4de5b0668..776789dc1 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -214,34 +214,42 @@ boolean M_LevelListFromGametype(INT16 gt) static boolean first = true; UINT8 temp = 0; - if (gt != -1 && (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES)) + if (gt != -1) { - if (first) + if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) { - cupgrid.cappages = 0; - cupgrid.builtgrid = NULL; - dummy_lostandfound.cachedlevels[0] = NEXTMAP_INVALID; + if (first) + { + cupgrid.cappages = 0; + cupgrid.builtgrid = NULL; + dummy_lostandfound.cachedlevels[0] = NEXTMAP_INVALID; - first = false; + first = false; + } + + levellist.newgametype = gt; + + levellist.levelsearch.typeoflevel = G_TOLFlag(gt); + if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL) + { + // Sneak in an extra. + levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS); + levellist.guessgt = gt; + } + else + { + levellist.guessgt = MAXGAMETYPES; + } + + levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); + + CV_SetValue(&cv_dummyspbattack, 0); } - levellist.newgametype = gt; - - levellist.levelsearch.typeoflevel = G_TOLFlag(gt); - if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL) - { - // Sneak in an extra. - levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS); - levellist.guessgt = gt; - } - else - { - levellist.guessgt = MAXGAMETYPES; - } - - levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); - - CV_SetValue(&cv_dummyspbattack, 0); + PLAY_CupSelectDef.music = \ + PLAY_LevelSelectDef.music = \ + PLAY_TimeAttackDef.music = \ + currentMenu->music; } // Obviously go to Cup Select in gametypes that have cups. From 8d66b279f88b513faed52381f01e0e2cbbf078ab Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 27 Mar 2023 17:27:11 +0100 Subject: [PATCH 7/9] FREE PLAY: Correct mistaken assumptions. - Make the check consistent between HUD and Challenge conditions by revolving both around M_NotFreePlay. - The HUD appearance checked every individual component of K_CanChangeRules, so just straight up do that here. - If the rules can be changed, battleprisons is always FREE PLAY no matter how many players are present (because it can change quickly). --- src/k_hud.c | 27 ++++++--------------------- src/m_cond.c | 14 +++++++++++--- src/m_cond.h | 2 ++ 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 639d8ac9b..2d76337da 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -4294,22 +4294,7 @@ static void K_drawBattleFullscreen(void) } // FREE PLAY? - if (K_Cooperative() == false) - { - UINT8 i; - - // check to see if there's anyone else at all - for (i = 0; i < MAXPLAYERS; i++) - { - if (i == displayplayers[0]) - continue; - if (playeringame[i] && !players[i].spectator) - break; - } - - if (i == MAXPLAYERS) - K_drawKartFreePlay(); - } + K_drawKartFreePlay(); } static void K_drawKartFirstPerson(void) @@ -4701,12 +4686,13 @@ static void K_drawTrickCool(void) void K_drawKartFreePlay(void) { - // Doesn't support splitscreens higher than 2 for real estate reasons. - if (!LUA_HudEnabled(hud_freeplay)) return; - if (modeattacking || grandprixinfo.gp || bossinfo.valid || stplyr->spectator) + if (stplyr->spectator == true) + return; + + if (M_NotFreePlay(stplyr) == true) return; if (lt_exitticker < TICRATE/2) @@ -5181,8 +5167,7 @@ void K_drawKartHUD(void) V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. - if (islonesome && K_Cooperative() == false && gametype != GT_TUTORIAL) - K_drawKartFreePlay(); + K_drawKartFreePlay(); if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1)) { diff --git a/src/m_cond.c b/src/m_cond.c index 4e3f31392..45db53caf 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -624,13 +624,21 @@ void M_UpdateConditionSetsPending(void) } } -static boolean M_NotFreePlay(player_t *player) +boolean M_NotFreePlay(player_t *player) { UINT8 i; - // Rounds that permit Cooperative play can be played by yourself without being FREE PLAY. - if (K_Cooperative()) + if (K_CanChangeRules(true) == false) + { + // Rounds with direction are never FREE PLAY. return true; + } + + if (battleprisons) + { + // Prison Break is battle's FREE PLAY. + return false; + } for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/m_cond.h b/src/m_cond.h index f65bba15b..c7712d5f7 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -325,6 +325,8 @@ void M_ClearConditionSet(UINT8 set); void M_ClearSecrets(void); void M_ClearStats(void); +boolean M_NotFreePlay(player_t *player); + // Updating conditions and unlockables boolean M_CheckCondition(condition_t *cn, player_t *player); boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall); From c174e254011d704320b0e5d682e4510e68ca2c5b Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 27 Mar 2023 19:19:05 +0100 Subject: [PATCH 8/9] Adjust position of Rings/Lives, Spheres, Speedometer and Accessibility Icons when gametype info is not drawn Necessary to make Tutorial/Sealed Stars not have a huge gap where the big information used to be --- src/k_hud.c | 124 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 35 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 2d76337da..cd8f77af6 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -2488,7 +2488,7 @@ static void K_DrawLivesDigits(INT32 x, INT32 y, INT32 width, INT32 flags, patch_ V_DrawScaledPatch(x, y, flags, font[stplyr->lives % 10]); } -static void K_drawRingCounter(void) +static void K_drawRingCounter(boolean gametypeinfoshown) { const boolean uselives = G_GametypeUsesLives(); SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; @@ -2547,43 +2547,57 @@ static void K_drawRingCounter(void) fr = fx; + if (gametypeinfoshown) + { + fy -= 10; + } + // Rings if (!uselives) { - V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy-10, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[1]); + V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[1]); if (flipflag) fr += 15; } else - V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[0]->width) - 3) : 0), fy-10, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); + V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[0]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); - V_DrawMappedPatch(fr+ringx, fy-13, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_smallring[ringanim_realframe], (colorring ? ringmap : NULL)); + V_DrawMappedPatch(fr+ringx, fy-3, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_smallring[ringanim_realframe], (colorring ? ringmap : NULL)); if (stplyr->rings < 0) // Draw the minus for ring debt - V_DrawMappedPatch(fr+7, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminussmall, ringmap); + V_DrawMappedPatch(fr+7, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminussmall, ringmap); - V_DrawMappedPatch(fr+11, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap); - V_DrawMappedPatch(fr+15, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap); + V_DrawMappedPatch(fr+11, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap); + V_DrawMappedPatch(fr+15, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap); // SPB ring lock if (stplyr->pflags & PF_RINGLOCK) - V_DrawScaledPatch(fr-12, fy-23, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]); + V_DrawScaledPatch(fr-12, fy-13, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]); // Lives if (uselives) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); - V_DrawMappedPatch(fr+21, fy-13, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_MINIMAP], colormap); + V_DrawMappedPatch(fr+21, fy-3, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_MINIMAP], colormap); if (stplyr->lives >= 0) - K_DrawLivesDigits(fr+34, fy-10, 4, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font); + K_DrawLivesDigits(fr+34, fy, 4, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font); } } else { - fy = LAPS_Y-11; + fy = LAPS_Y; - if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) - fy -= 4; + if (gametypeinfoshown) + { + fy -= 11; + + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fy -= 4; + } + else + { + fy += 9; + } // Rings if (!uselives) @@ -2622,9 +2636,9 @@ static void K_drawRingCounter(void) #undef RINGANIM_FLIPFRAME -static void K_drawKartAccessibilityIcons(INT32 fx) +static void K_drawKartAccessibilityIcons(boolean gametypeinfoshown, INT32 fx) { - INT32 fy = LAPS_Y-25; + INT32 fy = LAPS_Y-14; INT32 splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; //INT32 step = 1; -- if there's ever more than one accessibility icon @@ -2632,8 +2646,17 @@ static void K_drawKartAccessibilityIcons(INT32 fx) if (r_splitscreen < 2) // adjust to speedometer height { - if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) - fy -= 4; + if (gametypeinfoshown) + { + fy -= 11; + + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fy -= 4; + } + else + { + fy += 9; + } } else { @@ -2681,13 +2704,13 @@ static void K_drawKartAccessibilityIcons(INT32 fx) } } -static void K_drawKartSpeedometer(void) +static void K_drawKartSpeedometer(boolean gametypeinfoshown) { static fixed_t convSpeed; UINT8 labeln = 0; UINT8 numbers[3]; INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; - INT32 battleoffset = 0; + INT32 fy = LAPS_Y-14; if (!stplyr->exiting) // Keep the same speed value as when you crossed the finish line! { @@ -2722,19 +2745,28 @@ static void K_drawKartSpeedometer(void) numbers[1] = ((convSpeed / 10) % 10); numbers[2] = (convSpeed % 10); - if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) - battleoffset = -4; + if (gametypeinfoshown) + { + fy -= 11; - V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker); - V_DrawScaledPatch(LAPS_X+7, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[0]]); - V_DrawScaledPatch(LAPS_X+13, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[1]]); - V_DrawScaledPatch(LAPS_X+19, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[2]]); - V_DrawScaledPatch(LAPS_X+29, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometerlabel[labeln]); + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fy -= 4; + } + else + { + fy += 9; + } - K_drawKartAccessibilityIcons(56); + V_DrawScaledPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker); + V_DrawScaledPatch(LAPS_X+7, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[0]]); + V_DrawScaledPatch(LAPS_X+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[1]]); + V_DrawScaledPatch(LAPS_X+19, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[2]]); + V_DrawScaledPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometerlabel[labeln]); + + K_drawKartAccessibilityIcons(gametypeinfoshown, 56); } -static void K_drawBlueSphereMeter(void) +static void K_drawBlueSphereMeter(boolean gametypeinfoshown) { const UINT8 maxBars = 4; const UINT8 segColors[] = {73, 64, 52, 54, 55, 35, 34, 33, 202, 180, 181, 182, 164, 165, 166, 153, 152}; @@ -2752,7 +2784,17 @@ static void K_drawBlueSphereMeter(void) if (r_splitscreen < 2) // don't change shit for THIS splitscreen. { fx = LAPS_X; - fy = LAPS_Y-22; + fy = LAPS_Y-7; + + if (gametypeinfoshown) + { + fy -= 11 + 4; + } + else + { + fy += 9; + } + V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_spheresticker); } else @@ -2771,7 +2813,12 @@ static void K_drawBlueSphereMeter(void) flipflag = V_FLIP; // make the string right aligned and other shit xstep = -xstep; } - fy -= 16; + + if (gametypeinfoshown) + { + fy -= 16; + } + V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_splitspheresticker); } @@ -5068,6 +5115,8 @@ void K_drawKartHUD(void) } else { + boolean gametypeinfoshown = false; + if (LUA_HudEnabled(hud_position)) { if (bossinfo.valid) @@ -5089,31 +5138,36 @@ void K_drawKartHUD(void) { if (gametyperules & GTR_CIRCUIT) { - K_drawKartLaps(); + if (numlaps > 1) + { + K_drawKartLaps(); + gametypeinfoshown = true; + } } else if (gametyperules & GTR_BUMPERS) { K_drawKartBumpersOrKarma(); + gametypeinfoshown = true; } } // Draw the speedometer and/or accessibility icons if (cv_kartspeedometer.value && !r_splitscreen && (LUA_HudEnabled(hud_speedometer))) { - K_drawKartSpeedometer(); + K_drawKartSpeedometer(gametypeinfoshown); } else { - K_drawKartAccessibilityIcons(0); + K_drawKartAccessibilityIcons(gametypeinfoshown, 0); } if (gametyperules & GTR_SPHERES) { - K_drawBlueSphereMeter(); + K_drawBlueSphereMeter(gametypeinfoshown); } else { - K_drawRingCounter(); + K_drawRingCounter(gametypeinfoshown); } if (modeattacking && !bossinfo.valid) From 534209b51967b451b7c0cca98ae77a4f65a72b0c Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 30 Mar 2023 01:23:17 +0100 Subject: [PATCH 9/9] Extras Menu adjustment (again) - Uncomment extras_tutorial again - Put Tutorial second after Addons, so Challenges and Statistics are next to each other again --- src/k_menu.h | 2 +- src/menus/extras-1.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 2018e842a..f443a174a 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1017,8 +1017,8 @@ extern struct extrasmenu_s { typedef enum { extras_addons = 0, + extras_tutorial, extras_challenges, - //extras_tutorial, extras_statistics, extras_eggtv, extras_stereo, diff --git a/src/menus/extras-1.c b/src/menus/extras-1.c index eb2de1711..21cd2337e 100644 --- a/src/menus/extras-1.c +++ b/src/menus/extras-1.c @@ -14,12 +14,12 @@ menuitem_t EXTRAS_Main[] = {IT_STRING | IT_CALL, NULL, NULL, NULL, {.routine = M_Addons}, 0, 0}, - {IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!", - NULL, {.routine = M_Challenges}, 0, 0}, - {IT_STRING | IT_CALL, "Tutorial", "Help Dr. Eggman and Tails test out their new Ring Racers.", NULL, {.routine = M_LevelSelectInit}, 0, GT_TUTORIAL}, + {IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!", + NULL, {.routine = M_Challenges}, 0, 0}, + {IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!", NULL, {.routine = M_Statistics}, 0, 0},