diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 662f7f1f2..196c31d01 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1928,14 +1928,6 @@ bool CallFunc_CheckTutorialChallenge(ACSVM::Thread *thread, const ACSVM::Word *a 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); diff --git a/src/deh_soc.c b/src/deh_soc.c index 849d9a0ac..b7ccb9187 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2952,7 +2952,8 @@ static void readcondition(UINT16 set, UINT32 id, char *word2) || (++offset && fastcmp(params[0], "CREDITS")) || (++offset && fastcmp(params[0], "REPLAY")) || (++offset && fastcmp(params[0], "CRASH")) - || (++offset && fastcmp(params[0], "TUTORIALSKIP"))) + || (++offset && fastcmp(params[0], "TUTORIALSKIP")) + || (++offset && fastcmp(params[0], "TUTORIALDONE"))) { //PARAMCHECK(1); ty = UC_ADDON + offset; diff --git a/src/g_game.c b/src/g_game.c index df9da2f3a..6bf730f24 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4137,11 +4137,6 @@ void G_GetNextMap(void) netgame == false && gametype == GT_TUTORIAL && nextmap == NEXTMAP_TUTORIALCHALLENGE - && ( - !gamedata - || gamedata->enteredtutorialchallenge == false - || M_GameTrulyStarted() == true - ) ) { nextmap = G_MapNumber(tutorialchallengemap); @@ -4152,7 +4147,7 @@ void G_GetNextMap(void) // A gamedata save will happen on successful level enter // Also set character, color, and follower from profile - + D_SendPlayerConfig(0); } } @@ -4853,27 +4848,39 @@ void G_EndGame(void) } // Only do evaluation and credits in singleplayer contexts - if (!netgame && grandprixinfo.gp == true) + if (!netgame) { - G_HandleSaveLevel(true); - - if (nextmap == NEXTMAP_CEREMONY) // end game with ceremony + if (gametype == GT_TUTORIAL) { - if (K_StartCeremony() == true) + // Tutorial was finished + gamedata->tutorialdone = true; + + M_UpdateUnlockablesAndExtraEmblems(true, true); + gamedata->deferredsave = true; + } + + if (grandprixinfo.gp == true) + { + G_HandleSaveLevel(true); + + if (nextmap == NEXTMAP_CEREMONY) // end game with ceremony { + if (K_StartCeremony() == true) + { + return; + } + } + if (nextmap == NEXTMAP_CREDITS) // end game with credits + { + F_StartCredits(); + return; + } + if (nextmap == NEXTMAP_EVALUATION) // end game with evaluation + { + F_InitGameEvaluation(); + F_StartGameEvaluation(); return; } - } - if (nextmap == NEXTMAP_CREDITS) // end game with credits - { - F_StartCredits(); - return; - } - if (nextmap == NEXTMAP_EVALUATION) // end game with evaluation - { - F_InitGameEvaluation(); - F_StartGameEvaluation(); - return; } } diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index e080d5c5e..6fcf6e135 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -76,6 +76,7 @@ void srb2::save_ng_gamedata() ng.milestones.finishedtutorialchallenge = gamedata->finishedtutorialchallenge; ng.milestones.enteredtutorialchallenge = gamedata->enteredtutorialchallenge; ng.milestones.sealedswapalerted = gamedata->sealedswapalerted; + ng.milestones.tutorialdone = gamedata->tutorialdone; ng.milestones.gonerlevel = gamedata->gonerlevel; ng.prisons.thisprisoneggpickup = gamedata->thisprisoneggpickup; ng.prisons.prisoneggstothispickup = gamedata->prisoneggstothispickup; @@ -480,6 +481,7 @@ void srb2::load_ng_gamedata() gamedata->finishedtutorialchallenge = js.milestones.finishedtutorialchallenge; gamedata->enteredtutorialchallenge = js.milestones.enteredtutorialchallenge; gamedata->sealedswapalerted = js.milestones.sealedswapalerted; + gamedata->tutorialdone = js.milestones.tutorialdone; gamedata->gonerlevel = js.milestones.gonerlevel; gamedata->thisprisoneggpickup = js.prisons.thisprisoneggpickup; gamedata->prisoneggstothispickup = js.prisons.prisoneggstothispickup; diff --git a/src/g_gamedata.h b/src/g_gamedata.h index 3c21ea5c2..c8899c84f 100644 --- a/src/g_gamedata.h +++ b/src/g_gamedata.h @@ -96,6 +96,7 @@ struct GamedataMilestonesJson final bool finishedtutorialchallenge; bool enteredtutorialchallenge; bool sealedswapalerted; + bool tutorialdone; NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMilestonesJson, @@ -108,7 +109,8 @@ struct GamedataMilestonesJson final majorkeyskipattempted, finishedtutorialchallenge, enteredtutorialchallenge, - sealedswapalerted + sealedswapalerted, + tutorialdone ) }; diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 106df8550..be054fa15 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -259,7 +259,7 @@ void K_UpdateMatchRaceBots(void) else if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) { pmax = 8; // can you believe this is a nerf - difficulty = MAXBOTDIFFICULTY; + difficulty = 4; } else if (K_CanChangeRules(true) == false) { @@ -321,6 +321,27 @@ void K_UpdateMatchRaceBots(void) } } + auto clear_bots = [&numbots](UINT8 max) + { + UINT8 i = MAXPLAYERS; + while (numbots > max && i > 0) + { + i--; + + if (playeringame[i] && players[i].bot) + { + CL_RemovePlayer(i, KR_LEAVE); + numbots--; + } + } + }; + + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + // Prevent Eggman bot carrying over from Tutorial + clear_bots(0); + } + if (numbots < wantedbots) { // We require MORE bots! @@ -374,17 +395,7 @@ void K_UpdateMatchRaceBots(void) } else if (numbots > wantedbots) { - i = MAXPLAYERS; - while (numbots > wantedbots && i > 0) - { - i--; - - if (playeringame[i] && players[i].bot) - { - CL_RemovePlayer(i, KR_LEAVE); - numbots--; - } - } + clear_bots(wantedbots); } // We should have enough bots now :) diff --git a/src/m_cond.c b/src/m_cond.c index a3733f899..8d7c1f3b4 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -676,6 +676,7 @@ void M_ClearStats(void) gamedata->enteredtutorialchallenge = false; gamedata->finishedtutorialchallenge = false; gamedata->sealedswapalerted = false; + gamedata->tutorialdone = false; gamedata->musicstate = GDMUSIC_NONE; gamedata->importprofilewins = false; @@ -1726,6 +1727,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) return false; case UC_TUTORIALSKIP: return (gamedata->finishedtutorialchallenge == true); + case UC_TUTORIALDONE: + return (gamedata->tutorialdone == true); case UC_PASSWORD: return (cn->stringvar == NULL); @@ -2605,6 +2608,8 @@ static const char *M_GetConditionString(condition_t *cn) return NULL; case UC_TUTORIALSKIP: return "successfully skip the Tutorial"; + case UC_TUTORIALDONE: + return "complete the Tutorial"; case UC_PASSWORD: return "enter a secret password"; diff --git a/src/m_cond.h b/src/m_cond.h index 5c2142561..88905fda5 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -67,6 +67,7 @@ typedef enum UC_REPLAY, // Save a replay UC_CRASH, // Hee ho ! UC_TUTORIALSKIP, // Complete the Tutorial Challenge + UC_TUTORIALDONE, // Complete the Tutorial at all UC_PASSWORD, // Type in something funny @@ -393,6 +394,7 @@ struct gamedata_t boolean enteredtutorialchallenge; boolean finishedtutorialchallenge; boolean sealedswapalerted; + boolean tutorialdone; gdmusic_t musicstate; UINT8 gonerlevel; diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index c7e9ae12d..486104117 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -103,11 +103,15 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) // Finally, the most complex check: does the map have lock conditions? if (levelsearch->checklocked) { - // Check for visitation - if (!(mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED) - && !(mapheaderinfo[mapnum]->records.mapvisited & MV_VISITED) - && !(cup && cup->cachedlevels[0] == mapnum)) - return false; + // All tutorial courses can be visited for the first time once the game has truly started. + if (levelsearch->tutorial == false || M_GameTrulyStarted() == false) + { + // Check for visitation + if (!(mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED) + && !(mapheaderinfo[mapnum]->records.mapvisited & MV_VISITED) + && !(cup && cup->cachedlevels[0] == mapnum)) + return false; + } // Check for completion if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 8a9598174..058f4a792 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -7696,6 +7696,10 @@ static void P_InitLevelSettings(void) // We don't touch the gamespeed, though! } + else if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + gamespeed = KARTSPEED_NORMAL; + } else if (grandprixinfo.gp == true) { if (multi_speed) @@ -7703,10 +7707,7 @@ static void P_InitLevelSettings(void) gamespeed = grandprixinfo.gamespeed; } } - else if ( - modeattacking != ATTACKING_NONE - || tutorialchallenge == TUTORIALSKIP_INPROGRESS - ) + else if (modeattacking != ATTACKING_NONE) { if (multi_speed) {