From 0762b93ef2640d4bfe4984078370e5c59420fd79 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 16 Nov 2023 21:44:52 +0000 Subject: [PATCH] Tutorial skipping challenge - first pass - Go to a MainCfg-specified map on its guessed gametype - `TutorialChallengeMap = RR_ZonedCity` - Some unique settings - K_CanChangeRules() == false - If GTR_CIRCUIT, make it Hard Speed - If GTR_BUMPERS, die in one hit - If GTR_BOTS, make them all difficulty 13 of the default bot skin - No Medals or Spray Cans during the Tutorial Challenge - Complete the stage in 1st place or don't bother at all - Has the "Give up" menu option available, but no "Try Again" - Condition for successfully completing the Tutorial Skip - `Condition1 = TutorialSkip` Related bugfixes: - Correctly wipe skipstats when returning to the Title/menus - Typing of `ultimatemode` (hey this isn't accessible by anything right now I wonder) --- src/d_clisrv.c | 1 + src/d_main.cpp | 5 ++- src/deh_soc.c | 8 +++- src/doomstat.h | 9 +++- src/g_game.c | 72 ++++++++++++++++++++++++++++---- src/g_game.h | 3 +- src/g_state.h | 2 +- src/k_battle.c | 3 ++ src/k_bot.cpp | 36 ++++++++++++---- src/k_grandprix.c | 2 +- src/k_kart.c | 3 ++ src/k_tally.cpp | 17 ++++++-- src/lua_script.c | 3 ++ src/m_cond.c | 5 +++ src/m_cond.h | 2 + src/menus/transient/pause-game.c | 6 ++- src/p_mobj.c | 5 ++- src/p_setup.c | 5 ++- src/y_inter.c | 3 +- 19 files changed, 161 insertions(+), 29 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 111c7a086..b9e01856b 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1443,6 +1443,7 @@ static void CL_LoadReceivedSavegame(boolean reloading) demo.playback = false; demo.title = false; titlemapinaction = false; + tutorialchallenge = TUTORIALSKIP_NONE; automapactive = false; // load a base level diff --git a/src/d_main.cpp b/src/d_main.cpp index bfcd0fc96..524a2225e 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1053,8 +1053,11 @@ void D_ClearState(void) memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); memset(&roundqueue, 0, sizeof(struct roundqueue)); - // empty maptol so mario/etc sounds don't play in sound test when they shouldn't + // empty some other semi-important state maptol = 0; + nextmapoverride = 0; + skipstats = 0; + tutorialchallenge = TUTORIALSKIP_NONE; gameaction = ga_nothing; memset(displayplayers, 0, sizeof(displayplayers)); diff --git a/src/deh_soc.c b/src/deh_soc.c index c436cb87c..86b36931b 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2791,7 +2791,8 @@ static void readcondition(UINT16 set, UINT32 id, char *word2) else if ((offset=0) || fastcmp(params[0], "ADDON") || (++offset && fastcmp(params[0], "CREDITS")) || (++offset && fastcmp(params[0], "REPLAY")) - || (++offset && fastcmp(params[0], "CRASH"))) + || (++offset && fastcmp(params[0], "CRASH")) + || (++offset && fastcmp(params[0], "TUTORIALSKIP"))) { //PARAMCHECK(1); ty = UC_ADDON + offset; @@ -3469,6 +3470,11 @@ void readmaincfg(MYFILE *f, boolean mainfile) titlemap = Z_StrDup(word2); titlechanged = true; } + else if (fastcmp(word, "TUTORIALCHALLENGEMAP")) + { + Z_Free(tutorialchallengemap); + tutorialchallengemap = Z_StrDup(word2); + } else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "TITLEPICSHIDE")) { hidetitlepics = (boolean)(value != 0 || word2[0] == 'T' || word2[0] == 'Y'); diff --git a/src/doomstat.h b/src/doomstat.h index 354c9c919..efce0e5d6 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -246,11 +246,16 @@ extern INT32 g_localplayers[MAXSPLITSCREENPLAYERS]; extern char * titlemap; extern boolean hidetitlepics; -extern char * bootmap; //bootmap for loading a map on startup +extern boolean looptitle; +extern char * bootmap; //bootmap for loading a map on startup extern char * podiummap; // map to load for podium -extern boolean looptitle; +extern char * tutorialchallengemap; // map to load for tutorial skip +extern UINT8 tutorialchallenge; +#define TUTORIALSKIP_NONE 0 +#define TUTORIALSKIP_FAILED 1 +#define TUTORIALSKIP_INPROGRESS 2 // CTF colors. extern UINT16 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor_bluering; diff --git a/src/g_game.c b/src/g_game.c index 665331ee4..526508ea1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -83,7 +83,7 @@ gameaction_t gameaction; gamestate_t gamestate = GS_NULL; -UINT8 ultimatemode = false; +boolean ultimatemode = false; JoyType_t Joystick[MAXSPLITSCREENPLAYERS]; @@ -170,11 +170,13 @@ tic_t timeinmap; // Ticker for time spent in level (used for levelcard display) char * titlemap = NULL; boolean hidetitlepics = false; -char * bootmap = NULL; //bootmap for loading a map on startup +boolean looptitle = true; +char * bootmap = NULL; //bootmap for loading a map on startup char * podiummap = NULL; // map to load for podium -boolean looptitle = true; +char * tutorialchallengemap = NULL; // map to load for tutorial skip +UINT8 tutorialchallenge = TUTORIALSKIP_NONE; UINT16 skincolor_redteam = SKINCOLOR_RED; UINT16 skincolor_blueteam = SKINCOLOR_BLUE; @@ -717,14 +719,18 @@ INT32 G_MapNumber(const char * name) name += 8; + if (strcasecmp("TITLE", name) == 0) + return NEXTMAP_TITLE; if (strcasecmp("EVALUATION", name) == 0) return NEXTMAP_EVALUATION; if (strcasecmp("CREDITS", name) == 0) return NEXTMAP_CREDITS; if (strcasecmp("CEREMONY", name) == 0) return NEXTMAP_CEREMONY; - if (strcasecmp("TITLE", name) == 0) - return NEXTMAP_TITLE; + if (strcasecmp("VOTING", name) == 0) + return NEXTMAP_VOTING; + if (strcasecmp("TUTORIALCHALLENGE", name) == 0) + return NEXTMAP_TUTORIALCHALLENGE; return NEXTMAP_INVALID; } @@ -4170,8 +4176,6 @@ static void G_DoCompleted(void) G_SetGamestate(GS_NULL); wipegamestate = GS_NULL; - - prevmap = (INT16)(gamemap-1); } // Finally, if you're not exiting, guarantee NO CONTEST. @@ -4199,6 +4203,39 @@ static void G_DoCompleted(void) } // And lastly, everything in anticipation for Intermission/level change. + + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + if ( + players[consoleplayer].position != 1 + || !players[consoleplayer].exiting + || (players[consoleplayer].pflags & PF_NOCONTEST) + ) + { + // Return to whence you came with your tail between your legs + tutorialchallenge = TUTORIALSKIP_FAILED; + G_SetGametype(GT_TUTORIAL); + nextmapoverride = prevmap+1; + } + else + { + // Proceed. + nextmapoverride = NEXTMAP_TITLE+1; + + gamedata->finishedtutorialchallenge = true; + M_UpdateUnlockablesAndExtraEmblems(true, true); + G_SaveGameData(); + } + } + else + { + // The "else" might not be strictly needed, but I don't + // want the "challenge" map to be considered visited before it's your time. + // ~toast 161123 (5 years of srb2kart, woooouuuu) + prevmap = gamemap-1; + tutorialchallenge = TUTORIALSKIP_NONE; + } + if (!demo.playback) { // Set up power level gametype scrambles @@ -4269,6 +4306,23 @@ void G_AfterIntermission(void) // void G_NextLevel(void) { + if ( + gametype == GT_TUTORIAL + && nextmap == NEXTMAP_TUTORIALCHALLENGE + ) + { + nextmap = G_MapNumber(tutorialchallengemap); + if ( + nextmap < nummapheaders + && mapheaderinfo[nextmap] != NULL + && mapheaderinfo[nextmap]->typeoflevel != 0 + ) + { + tutorialchallenge = TUTORIALSKIP_INPROGRESS; + G_SetGametype(G_GuessGametypeByTOL(mapheaderinfo[nextmap]->typeoflevel)); + } + } + if (nextmap >= NEXTMAP_SPECIAL) { G_EndGame(); @@ -4471,6 +4525,7 @@ typedef enum GDEVER_SPECIAL = 1<<3, GDEVER_KEYTUTORIAL = 1<<4, GDEVER_KEYMAJORSKIP = 1<<5, + GDEVER_TUTORIALSKIP = 1<<6, } gdeverdone_t; static const char *G_GameDataFolder(void) @@ -4610,6 +4665,7 @@ void G_LoadGameData(void) gamedata->everseenspecial = !!(everflags & GDEVER_SPECIAL); gamedata->chaokeytutorial = !!(everflags & GDEVER_KEYTUTORIAL); gamedata->majorkeyskipattempted = !!(everflags & GDEVER_KEYMAJORSKIP); + gamedata->finishedtutorialchallenge = !!(everflags & GDEVER_TUTORIALSKIP); } else { @@ -5301,6 +5357,8 @@ void G_SaveGameData(void) everflags |= GDEVER_KEYTUTORIAL; if (gamedata->majorkeyskipattempted) everflags |= GDEVER_KEYMAJORSKIP; + if (gamedata->finishedtutorialchallenge) + everflags |= GDEVER_TUTORIALSKIP; WRITEUINT32(save.p, everflags); // 4 } diff --git a/src/g_game.h b/src/g_game.h index 9464a6e8a..1bcf1f024 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -49,7 +49,8 @@ typedef enum NEXTMAP_CREDITS = INT16_MAX-3, NEXTMAP_CEREMONY = INT16_MAX-4, NEXTMAP_VOTING = INT16_MAX-5, - NEXTMAP_INVALID = INT16_MAX-6, // Always last + NEXTMAP_TUTORIALCHALLENGE = INT16_MAX-6, + NEXTMAP_INVALID = INT16_MAX-7, // Always last NEXTMAP_SPECIAL = NEXTMAP_INVALID } nextmapspecial_t; diff --git a/src/g_state.h b/src/g_state.h index 52a77dcb5..c4d901bc7 100644 --- a/src/g_state.h +++ b/src/g_state.h @@ -58,7 +58,7 @@ typedef enum extern gamestate_t gamestate; extern boolean titlemapinaction; -extern UINT8 ultimatemode; // was sk_insane +extern boolean ultimatemode; // was sk_insane extern gameaction_t gameaction; void G_SetGamestate(gamestate_t newstate); diff --git a/src/k_battle.c b/src/k_battle.c index d5577320d..ee6bb180a 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -39,6 +39,9 @@ UINT8 numtargets = 0; // Capsules busted INT32 K_StartingBumperCount(void) { + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + return 0; + if (battleprisons || K_CheckBossIntro()) { if (grandprixinfo.gp) diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 8c1894fba..2739e05cd 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -128,8 +128,8 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p) void K_UpdateMatchRaceBots(void) { const UINT8 defaultbotskin = R_BotDefaultSkin(); - const UINT8 difficulty = cv_kartbot.value; - UINT8 pmax = std::min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), static_cast(cv_maxconnections.value)); + UINT8 difficulty; + UINT8 pmax = (dedicated ? MAXPLAYERS-1 : MAXPLAYERS); UINT8 numplayers = 0; UINT8 numbots = 0; UINT8 numwaiting = 0; @@ -145,9 +145,27 @@ void K_UpdateMatchRaceBots(void) } grabskins[usableskins] = MAXSKINS; - if (cv_maxplayers.value > 0) + if ((gametyperules & GTR_BOTS) == 0) { - pmax = std::min(pmax, static_cast(cv_maxplayers.value)); + difficulty = 0; + } + else if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + pmax = 8; // can you believe this is a nerf + difficulty = MAXBOTDIFFICULTY; + } + else if (K_CanChangeRules(true) == false) + { + difficulty = 0; + } + else + { + difficulty = cv_kartbot.value; + pmax = std::min(pmax, static_cast(cv_maxconnections.value)); + if (cv_maxplayers.value > 0) + { + pmax = std::min(pmax, static_cast(cv_maxplayers.value)); + } } for (i = 0; i < MAXPLAYERS; i++) @@ -180,9 +198,7 @@ void K_UpdateMatchRaceBots(void) } } - if (K_CanChangeRules(true) == false - || (gametyperules & GTR_BOTS) == 0 - || difficulty == 0) + if (difficulty == 0) { // Remove bots if there are any. wantedbots = 0; @@ -209,7 +225,11 @@ void K_UpdateMatchRaceBots(void) } // Rearrange usable bot skins list to prevent gaps for randomised selection - for (i = 0; i < usableskins; i++) + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + usableskins = 0; // force a crack team of Eggrobo + } + else for (i = 0; i < usableskins; i++) { if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true))) { diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 9f2ed102f..49af3b526 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -831,7 +831,7 @@ boolean K_CanChangeRules(boolean allowdemos) return false; } - if (gametype == GT_TUTORIAL) + if (gametype == GT_TUTORIAL || tutorialchallenge == TUTORIALSKIP_INPROGRESS) { // Tutorials are locked down. return false; diff --git a/src/k_kart.c b/src/k_kart.c index 713562453..e4c531884 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -401,6 +401,9 @@ boolean K_IsPlayerLosing(player_t *player) if (specialstageinfo.valid == true) return false; // anything short of DNF is COOL + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + return true; // anything short of perfect is SUCK + for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) diff --git a/src/k_tally.cpp b/src/k_tally.cpp index af1413141..7a52a862b 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -291,7 +291,11 @@ void level_tally_t::Init(player_t *player) owner = player; gt = gametype; - const boolean game_over = ((player->pflags & PF_LOSTLIFE) == PF_LOSTLIFE); + const boolean game_over = ( + G_GametypeUsesLives() + ? ((player->pflags & PF_LOSTLIFE) == PF_LOSTLIFE) + : (tutorialchallenge == TUTORIALSKIP_INPROGRESS && K_IsPlayerLosing(player)) + ); time = std::min(static_cast(player->realtime), (100 * 60 * TICRATE) - 1); ringPool = player->totalring; @@ -384,7 +388,14 @@ void level_tally_t::Init(player_t *player) { if (game_over == true) { - if (player->lives <= 0) + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + snprintf( + header, sizeof header, + "NICE TRY" + ); + } + else if (G_GametypeUsesLives() && player->lives <= 0) { snprintf( header, sizeof header, @@ -991,7 +1002,7 @@ void level_tally_t::Draw(void) || state == TALLY_ST_GAMEOVER_LIVES || state == TALLY_ST_GAMEOVER_DONE) { - if (owner->lives > 0) + if (G_GametypeUsesLives() && owner->lives > 0) { srb2::Draw lives_drawer = drawer .xy( diff --git a/src/lua_script.c b/src/lua_script.c index f9e316b50..bd54fac61 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -219,6 +219,9 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"podiummap")) { lua_pushstring(L, podiummap); return 1; + } else if (fastcmp(word,"tutorialchallengemap")) { + lua_pushstring(L, tutorialchallengemap); + return 1; // end map vars // begin CTF colors } else if (fastcmp(word,"skincolor_redteam")) { diff --git a/src/m_cond.c b/src/m_cond.c index 39383aafc..a9e4f5161 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -661,6 +661,7 @@ void M_ClearStats(void) gamedata->evercrashed = false; gamedata->chaokeytutorial = false; gamedata->majorkeyskipattempted = false; + gamedata->finishedtutorialchallenge = false; gamedata->musicstate = GDMUSIC_NONE; gamedata->importprofilewins = false; @@ -1499,6 +1500,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return true; } return false; + case UC_TUTORIALSKIP: + return (gamedata->finishedtutorialchallenge == true); case UC_PASSWORD: return (cn->stringvar == NULL); @@ -2307,6 +2310,8 @@ static const char *M_GetConditionString(condition_t *cn) if (gamedata->evercrashed) return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash"; return NULL; + case UC_TUTORIALSKIP: + return "successfully skip the Tutorial"; case UC_PASSWORD: return "enter a secret password"; diff --git a/src/m_cond.h b/src/m_cond.h index 832200d08..acd6e008b 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -63,6 +63,7 @@ typedef enum UC_CREDITS, // Finish watching the credits UC_REPLAY, // Save a replay UC_CRASH, // Hee ho ! + UC_TUTORIALSKIP, // Complete the Tutorial Challenge UC_PASSWORD, // Type in something funny @@ -363,6 +364,7 @@ struct gamedata_t boolean evercrashed; boolean chaokeytutorial; boolean majorkeyskipattempted; + boolean finishedtutorialchallenge; gdmusic_t musicstate; // BACKWARDS COMPAT ASSIST diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index b5677f5cf..4afe00d2e 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -184,8 +184,12 @@ void M_OpenPauseMenu(void) && roundqueue.size != 0 ); + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) { - if (gamestate == GS_LEVEL && !retryallowed) + // NO RETRY, ONLY GIVE UP + giveup = true; + } + else if (gamestate == GS_LEVEL && !retryallowed) { if (gametype == GT_TUTORIAL) { diff --git a/src/p_mobj.c b/src/p_mobj.c index 9910424a1..f797a51f5 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -13038,6 +13038,9 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) skincolornum_t emcolor; INT16 tagnum = mthing->tid; + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + return false; // No out-of-sequence goodies + while (emblem) { if (emblem->type == ET_GLOBAL && emblem->tag == tagnum) @@ -13605,7 +13608,7 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) } case MT_SPRAYCAN: { - if (nummapspraycans == UINT8_MAX) + if (nummapspraycans == UINT8_MAX || tutorialchallenge == TUTORIALSKIP_INPROGRESS) { P_RemoveMobj(mobj); return false; diff --git a/src/p_setup.c b/src/p_setup.c index 99734ef07..1f1e371af 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7609,7 +7609,10 @@ static void P_InitLevelSettings(void) gamespeed = grandprixinfo.gamespeed; } } - else if (modeattacking) + else if ( + modeattacking != ATTACKING_NONE + || tutorialchallenge == TUTORIALSKIP_INPROGRESS + ) { if (gametyperules & GTR_CIRCUIT) { diff --git a/src/y_inter.c b/src/y_inter.c index bc7264a57..2a99f8030 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1905,7 +1905,8 @@ void Y_DetermineIntermissionType(void) // or for failing in time attack mode || (modeattacking && (players[consoleplayer].pflags & PF_NOCONTEST)) // or for explicit requested skip (outside of modeattacking) - || (modeattacking == ATTACKING_NONE && skipstats != 0)) + || (modeattacking == ATTACKING_NONE && skipstats != 0) + || (tutorialchallenge != TUTORIALSKIP_NONE)) { intertype = int_none; return;