diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 48a16fdc8..4017752b5 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -350,10 +350,13 @@ ACSVM::Word Environment::callSpecImpl activator_t *activator = static_cast(Z_Calloc(sizeof(activator_t), PU_LEVEL, nullptr)); auto __ = srb2::finally( - [activator]() + [info, activator]() { - P_SetTarget(&activator->mo, NULL); - Z_Free(activator); + if (info->thread_era == thinker_era) + { + P_SetTarget(&activator->mo, NULL); + Z_Free(activator); + } } ); diff --git a/src/acs/interface.cpp b/src/acs/interface.cpp index 8b56b6ebe..43cd4a2f2 100644 --- a/src/acs/interface.cpp +++ b/src/acs/interface.cpp @@ -70,6 +70,28 @@ void ACS_Shutdown(void) #endif } +/*-------------------------------------------------- + void ACS_InvalidateMapScope(size_t mapID) + + See header file for description. +--------------------------------------------------*/ +void ACS_InvalidateMapScope(void) +{ + Environment *env = &ACSEnv; + + ACSVM::GlobalScope *const global = env->getGlobalScope(0); + ACSVM::HubScope *hub = NULL; + ACSVM::MapScope *map = NULL; + + // Conclude hub scope, even if we are not using it. + hub = global->getHubScope(0); + hub->reset(); + + // Conclude current map scope. + map = hub->getMapScope(0); // This is where you'd put in mapID if you add hub support. + map->reset(); +} + /*-------------------------------------------------- void ACS_LoadLevelScripts(size_t mapID) @@ -103,14 +125,20 @@ void ACS_LoadLevelScripts(size_t mapID) // hubs are to be implemented, this logic would need // to be far more sophisticated. - // Reset hub scope, even if we are not using it. + // Extra note regarding the commented out ->reset()'s: + // This is too late! That needs to be done before + // PU_LEVEL is purged. Call ACS_InvalidateMapScope + // to take care of that. Those lines are left in + // only as a warning to future code spelunkers. + + // Restart hub scope, even if we are not using it. hub = global->getHubScope(0); - hub->reset(); + //hub->reset(); hub->active = true; // Start up new map scope. map = hub->getMapScope(0); // This is where you'd put in mapID if you add hub support. - map->reset(); + //map->reset(); map->active = true; // Insert BEHAVIOR lump into the list. diff --git a/src/acs/interface.h b/src/acs/interface.h index 32ba78b90..0b9881950 100644 --- a/src/acs/interface.h +++ b/src/acs/interface.h @@ -44,6 +44,23 @@ void ACS_Init(void); void ACS_Shutdown(void); +/*-------------------------------------------------- + void ACS_InvalidateMapScope(size_t mapID); + + Resets the ACS hub and map scopes to remove + existing running scripts, without starting + any new scripts. + + Input Arguments:- + None + + Return:- + None +--------------------------------------------------*/ + +void ACS_InvalidateMapScope(void); + + /*-------------------------------------------------- void ACS_LoadLevelScripts(size_t mapID); diff --git a/src/acs/thread.hpp b/src/acs/thread.hpp index 0b20271af..f72aac34f 100644 --- a/src/acs/thread.hpp +++ b/src/acs/thread.hpp @@ -59,6 +59,7 @@ enum acs_tagType_e class ThreadInfo : public ACSVM::ThreadInfo { public: + UINT32 thread_era; // If equal to thinker_era, mobj pointers are safe. mobj_t *mo; // Object that activated this thread. line_t *line; // Linedef that activated this thread. UINT8 side; // Front / back side of said linedef. @@ -67,6 +68,7 @@ public: bool fromLineSpecial; // Called from P_ProcessLineSpecial. ThreadInfo() : + thread_era { thinker_era }, mo{ nullptr }, line{ nullptr }, side{ 0 }, @@ -77,6 +79,7 @@ public: } ThreadInfo(const ThreadInfo &info) : + thread_era { thinker_era }, mo{ nullptr }, line{ info.line }, side{ info.side }, @@ -88,6 +91,7 @@ public: } ThreadInfo(const activator_t *activator) : + thread_era { thinker_era }, mo{ nullptr }, line{ activator->line }, side{ activator->side }, @@ -100,11 +104,15 @@ public: ~ThreadInfo() { - P_SetTarget(&mo, nullptr); + if (thread_era == thinker_era) + { + P_SetTarget(&mo, nullptr); + } } ThreadInfo &operator = (const ThreadInfo &info) { + thread_era = thinker_era; P_SetTarget(&mo, info.mo); line = info.line; side = info.side; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 2ef89d216..1fa557757 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1110,7 +1110,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16); - if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) && !(mapheaderinfo[prevmap]->zonttl[0])) + if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) && !(mapheaderinfo[gamemap-1]->zonttl[0])) netbuffer->u.serverinfo.iszone = 1; else netbuffer->u.serverinfo.iszone = 0; @@ -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 @@ -3992,7 +3993,9 @@ void SV_StopServer(void) Y_EndIntermission(); if (gamestate == GS_VOTING) Y_EndVote(); - gamestate = wipegamestate = GS_NULL; + + G_SetGamestate(GS_NULL); + wipegamestate = GS_NULL; for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) ((UINT16*)localtextcmd[i])[0] = 0; diff --git a/src/d_main.cpp b/src/d_main.cpp index 3c7e51d83..d1814813b 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1062,8 +1062,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/d_netcmd.c b/src/d_netcmd.c index e259c76a2..8edba171b 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2587,7 +2587,7 @@ static void Command_Map_f(void) SplitScreen_OnChange(); } - if (!newnetgame && option_match == 0) + if (!newnetgame && (newgametype != GT_TUTORIAL) && option_match == 0) { grandprixinfo.gp = true; grandprixinfo.initalize = true; @@ -3022,7 +3022,7 @@ static void Command_QueueMap_f(void) return; } - if ((/*newmapnum != 1 &&*/ M_MapLocked(newmapnum))) + if (/*newmapnum != 1 &&*/ M_MapLocked(newmapnum)) { ischeating = true; } diff --git a/src/d_player.h b/src/d_player.h index ed64b9fc0..85d30355f 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -355,6 +355,7 @@ struct respawnvars_t fixed_t pointx; // Respawn position coords to go towards fixed_t pointy; fixed_t pointz; + angle_t pointangle; // Only used when wp is NULL boolean flip; // Flip upside down or not tic_t timer; // Time left on respawn animation once you're there tic_t airtimer; // Time spent in the air before respawning 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 72b7294c2..6e92351f3 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; } @@ -2499,8 +2505,7 @@ void G_MovePlayerToSpawnOrCheatcheck(INT32 playernum) rsp->pointx = pos.x; rsp->pointy = pos.y; rsp->pointz = pos.z; - - players[playernum].mo->angle = Obj_GetCheckpointRespawnAngle(checkpoint); + rsp->pointangle = Obj_GetCheckpointRespawnAngle(checkpoint); Obj_ActivateCheckpointInstantly(checkpoint); @@ -3354,7 +3359,7 @@ UINT32 G_TOLFlag(INT32 pgametype) return 0; } -UINT16 G_GetFirstMapOfGametype(UINT8 pgametype) +UINT16 G_GetFirstMapOfGametype(UINT16 pgametype) { UINT8 i = 0; UINT16 mapnum = NEXTMAP_INVALID; @@ -3364,7 +3369,7 @@ UINT16 G_GetFirstMapOfGametype(UINT8 pgametype) templevelsearch.typeoflevel = G_TOLFlag(pgametype); templevelsearch.cupmode = (!(gametypes[pgametype]->rules & GTR_NOCUPSELECT)); templevelsearch.timeattack = false; - templevelsearch.tutorial = false; + templevelsearch.tutorial = (pgametype == GT_TUTORIAL); templevelsearch.checklocked = true; if (templevelsearch.cupmode) @@ -3618,6 +3623,14 @@ void G_UpdateVisited(void) if (demo.playback) return; + // For some reason, we don't want to update visitation flags. + if (prevmap != gamemap-1) + return; + + // Neither for tutorial skip material + if (nextmapoverride == NEXTMAP_TUTORIALCHALLENGE+1 || tutorialchallenge != TUTORIALSKIP_NONE) + return; + // Check if every local player wiped out. for (i = 0; i < MAXPLAYERS; i++) { @@ -3627,7 +3640,7 @@ void G_UpdateVisited(void) if (!P_IsLocalPlayer(&players[i])) // Not local. continue; - if (players[i].spectator) // Not playing. + if (players[i].spectator == true) // Not playing. continue; if (players[i].pflags & PF_NOCONTEST) // Sonic after not surviving. @@ -3658,7 +3671,7 @@ void G_UpdateVisited(void) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); M_UpdateUnlockablesAndExtraEmblems(true, true); - G_SaveGameData(); + gamedata->deferredsave = true; } void G_HandleSaveLevel(boolean removecondition) @@ -4148,11 +4161,6 @@ static void G_DoCompleted(void) gamedata->deferredsave = true; } - // This isn't in the above block because other - // mechanisms can queue up a gamedata save. - if (gamedata->deferredsave) - G_SaveGameData(); - // Then, update some important game state. { legitimateexit = false; @@ -4172,14 +4180,18 @@ 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. // We do this seperately from the loop above Challenges, // so NOCONTEST-related Challenges don't fire on exitlevel. - for (i = 0; i < MAXPLAYERS; i++) + if (gametype == GT_TUTORIAL) + { + // Maybe one day there'll be another context in which + // there's no way to progress other than ACS, but for + // now, Tutorial is a hardcoded exception. + } + else for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] == false) { @@ -4195,6 +4207,40 @@ static void G_DoCompleted(void) } // And lastly, everything in anticipation for Intermission/level change. + + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + if ( + !legitimateexit + || !players[consoleplayer].exiting + || K_IsPlayerLosing(&players[consoleplayer]) + ) + { + // 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); + gamedata->deferredsave = true; + } + } + 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 @@ -4219,6 +4265,11 @@ static void G_DoCompleted(void) Y_StartIntermission(); G_UpdateVisited(); } + + // This isn't in the above blocks because many + // mechanisms can queue up a gamedata save. + if (gamedata->deferredsave) + G_SaveGameData(); } // See also F_EndCutscene, the only other place which handles intra-map/ending transitions @@ -4265,6 +4316,27 @@ void G_AfterIntermission(void) // void G_NextLevel(void) { + if ( + gametype == GT_TUTORIAL + && nextmap == NEXTMAP_TUTORIALCHALLENGE + && !(gamedata && gamedata->enteredtutorialchallenge) + ) + { + nextmap = G_MapNumber(tutorialchallengemap); + if ( + nextmap < nummapheaders + && mapheaderinfo[nextmap] != NULL + && mapheaderinfo[nextmap]->typeoflevel != 0 + ) + { + tutorialchallenge = TUTORIALSKIP_INPROGRESS; + G_SetGametype(G_GuessGametypeByTOL(mapheaderinfo[nextmap]->typeoflevel)); + + gamedata->enteredtutorialchallenge = true; + // A gamedata save will happen on successful level enter + } + } + if (nextmap >= NEXTMAP_SPECIAL) { G_EndGame(); @@ -4467,6 +4539,8 @@ typedef enum GDEVER_SPECIAL = 1<<3, GDEVER_KEYTUTORIAL = 1<<4, GDEVER_KEYMAJORSKIP = 1<<5, + GDEVER_TUTORIALSKIP = 1<<6, + GDEVER_ENTERTUTSKIP = 1<<7, } gdeverdone_t; static const char *G_GameDataFolder(void) @@ -4606,6 +4680,8 @@ void G_LoadGameData(void) gamedata->everseenspecial = !!(everflags & GDEVER_SPECIAL); gamedata->chaokeytutorial = !!(everflags & GDEVER_KEYTUTORIAL); gamedata->majorkeyskipattempted = !!(everflags & GDEVER_KEYMAJORSKIP); + gamedata->finishedtutorialchallenge = !!(everflags & GDEVER_TUTORIALSKIP); + gamedata->enteredtutorialchallenge = !!(everflags & GDEVER_ENTERTUTSKIP); } else { @@ -5297,6 +5373,10 @@ void G_SaveGameData(void) everflags |= GDEVER_KEYTUTORIAL; if (gamedata->majorkeyskipattempted) everflags |= GDEVER_KEYMAJORSKIP; + if (gamedata->finishedtutorialchallenge) + everflags |= GDEVER_TUTORIALSKIP; + if (gamedata->enteredtutorialchallenge) + everflags |= GDEVER_ENTERTUTSKIP; WRITEUINT32(save.p, everflags); // 4 } @@ -5990,7 +6070,9 @@ INT32 G_FindMap(const char *mapname, char **foundmapnamep, aprop = realmapname; /* Now that we found a perfect match no need to fucking guess. */ - if (strnicmp(realmapname, mapname, mapnamelen) == 0) + if (strnicmp(realmapname, mapname, mapnamelen) == 0 + || (mapheaderinfo[i]->menuttl[0] + && strnicmp(mapheaderinfo[i]->menuttl, mapname, mapnamelen) == 0)) { if (wanttable) { @@ -6024,15 +6106,42 @@ INT32 G_FindMap(const char *mapname, char **foundmapnamep, realmapname = 0; } } + else + if (mapheaderinfo[i]->menuttl[0] && ( aprop = strcasestr(mapheaderinfo[i]->menuttl, mapname) )) + { + if (wanttable) + { + writesimplefreq(freq, &freqc, + mapnum, aprop - mapheaderinfo[i]->menuttl, mapnamelen); + } + if (apromapnum == 0) + { + apromapnum = mapnum; + apromapname = realmapname; + realmapname = 0; + } + } else/* ...match individual keywords */ { freq[freqc].mapnum = mapnum; measurekeywords(&freq[freqc], &freq[freqc].matchd, &freq[freqc].matchc, realmapname, mapname, wanttable); - measurekeywords(&freq[freqc], - &freq[freqc].keywhd, &freq[freqc].keywhc, - mapheaderinfo[i]->keywords, mapname, wanttable); + + if (mapheaderinfo[i]->menuttl[0]) + { + measurekeywords(&freq[freqc], + &freq[freqc].keywhd, &freq[freqc].keywhc, + mapheaderinfo[i]->menuttl, mapname, wanttable); + } + + if (mapheaderinfo[i]->keywords[0]) + { + measurekeywords(&freq[freqc], + &freq[freqc].keywhd, &freq[freqc].keywhc, + mapheaderinfo[i]->keywords, mapname, wanttable); + } + if (freq[freqc].total) freqc++; } diff --git a/src/g_game.h b/src/g_game.h index 9464a6e8a..6d050c75d 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; @@ -279,7 +280,7 @@ FUNCMATH INT32 G_TicsToMilliseconds(tic_t tics); // Don't split up TOL handling UINT32 G_TOLFlag(INT32 pgametype); -UINT16 G_GetFirstMapOfGametype(UINT8 pgametype); +UINT16 G_GetFirstMapOfGametype(UINT16 pgametype); UINT16 G_RandMap(UINT32 tolflags, UINT16 pprevmap, boolean ignoreBuffers, boolean callAgainSoon, UINT16 *extBuffer); void G_AddMapToBuffer(UINT16 map); 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..e43567d20 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -71,8 +71,50 @@ void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e st // For each subsequent round of GP, K_UpdateGrandPrixBots will handle this. players[newplayernum].spectator = grandprixinfo.gp && grandprixinfo.initalize && K_BotDefaultSpectator(); - players[newplayernum].skincolor = skins[skinnum].prefcolor; - sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); + skincolornum_t color = static_cast(skins[skinnum].prefcolor); + const char *realname = skins[skinnum].realname; + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) + { + // The ROYGBIV Rangers + switch (newplayernum) + { + case 1: + color = SKINCOLOR_RED; + realname = "Champ"; + break; + case 2: + color = SKINCOLOR_ORANGE; + realname = "Pharaoh"; + break; + case 3: + color = SKINCOLOR_YELLOW; + realname = "Caesar"; + break; + case 4: + color = SKINCOLOR_GREEN; + realname = "General"; + break; + case 5: + color = SKINCOLOR_CYAN; // blue (lighter than _BLUE) + realname = "Shogun"; + break; + case 6: + color = SKINCOLOR_BLUEBERRY; // indigo + realname = "Emperor"; + break; + case 7: + color = SKINCOLOR_VIOLET; + realname = "King"; + break; + default: + color = SKINCOLOR_BLACK; + realname = "Vizier"; // working in the shadows + break; + } + } + players[newplayernum].skincolor = color; + sprintf(player_names[newplayernum], "%s", realname); + SetPlayerSkinByNum(newplayernum, skinnum); playerconsole[newplayernum] = newplayernum; @@ -128,8 +170,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 +187,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 +240,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 +267,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_hud.c b/src/k_hud.c index 4f9d94729..15c2f0231 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -2352,6 +2352,8 @@ static boolean K_drawKartPositionFaces(void) if ((gametyperules & GTR_BUMPERS) && (players[rankplayer[i]].pflags & PF_ELIMINATED)) V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_ranknobumpers); + else if (K_Cooperative()) + ; else if (gametyperules & GTR_CIRCUIT) { INT32 pos = players[rankplayer[i]].position; @@ -2360,7 +2362,7 @@ static boolean K_drawKartPositionFaces(void) // Draws the little number over the face V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); } - else + else if (gametyperules & GTR_POINTLIMIT) { INT32 flags = V_HUDTRANS | V_SLIDEIN | V_SNAPTOLEFT; @@ -5526,7 +5528,7 @@ void K_drawKartHUD(void) if (!battleprisons) K_drawKartEmeralds(); } - else if (!islonesome) + else if (!islonesome && !K_Cooperative()) K_DrawKartPositionNum(stplyr->position); } diff --git a/src/k_kart.c b/src/k_kart.c index 8059285d7..00952992d 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -396,6 +396,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) @@ -582,6 +585,15 @@ boolean K_TimeAttackRules(void) return true; } + if (gametype == GT_TUTORIAL) + { + // Tutorials are special. By default only one + // player will be playing... but sometimes bots + // can be spawned! So we still guarantee the + // changed behaviour for consistency. + return true; + } + for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] == false || players[i].spectator == true) @@ -13076,6 +13088,12 @@ boolean K_Cooperative(void) return true; } + if (gametype == GT_TUTORIAL) + { + // Maybe this should be a rule. Eventually? + return true; + } + return false; } diff --git a/src/k_menu.h b/src/k_menu.h index f4144ac39..e4700187a 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -451,6 +451,8 @@ typedef enum #endif mpause_admin, mpause_callvote, + + mpause_giveup, mpause_restartmap, mpause_tryagain, @@ -1104,6 +1106,7 @@ extern consvar_t cv_dummyspectator; // Bunch of funny functions for the pause menu...~ void M_RestartMap(INT32 choice); // Restart level (MP) void M_TryAgain(INT32 choice); // Try again (SP) +void M_GiveUp(INT32 choice); // Give up (SP) void M_ConfirmSpectate(INT32 choice); // Spectate confirm when you're alone void M_ConfirmEnterGame(INT32 choice); // Enter game confirm when you're alone void M_ConfirmSpectateChange(INT32 choice); // Splitscreen spectate/play menu func @@ -1133,7 +1136,9 @@ char *M_AddonsHeaderPath(void); extern consvar_t cv_dummyaddonsearch; extern consvar_t cv_dummyextraspassword; +#ifdef TODONEWMANUAL void M_Manual(INT32 choice); +#endif void M_HandleImageDef(INT32 choice); // K_MENUDRAW.C diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 5f78696af..ca030b7a1 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -775,6 +775,7 @@ void M_Drawer(void) if (menuwipe) F_WipeStartScreen(); + // background layer if (menuactive) { if (gamestate == GS_MENU) @@ -785,7 +786,17 @@ void M_Drawer(void) { V_DrawFadeScreen(122, 3); } + } + // draw pause pic + if (paused && !demo.playback && (menuactive || cv_showhud.value)) + { + M_DrawPausedText(0); + } + + // foreground layer + if (menuactive) + { if (currentMenu->drawroutine) currentMenu->drawroutine(); // call current menu Draw routine @@ -813,18 +824,6 @@ void M_Drawer(void) menuwipe = false; } - // draw pause pic - if (paused && !demo.playback && (menuactive || cv_showhud.value)) - { - // Don't cover the Stereo player! - boolean stereo_open = menuactive && currentMenu == &MISC_SoundTestDef; - - if (stereo_open == false) - { - M_DrawPausedText(0); - } - } - if (netgame && Playing()) { boolean mainpause_open = menuactive && currentMenu == &PAUSE_MainDef; @@ -2909,7 +2908,7 @@ static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) V_DrawLSTitleLowString(x2, y+28, 0, word2); } -static void M_DrawLevelSelectBlock(INT16 x, INT16 y, INT16 map, boolean redblink, boolean greyscale) +static void M_DrawLevelSelectBlock(INT16 x, INT16 y, UINT16 map, boolean redblink, boolean greyscale) { UINT8 *colormap = NULL; @@ -2928,6 +2927,20 @@ static void M_DrawLevelSelectBlock(INT16 x, INT16 y, INT16 map, boolean redblink map, colormap); M_DrawHighLowLevelTitle(98+x, y+8, map); + + if (levellist.levelsearch.tutorial && !(mapheaderinfo[map]->records.mapvisited & MV_BEATEN)) + { + V_DrawScaledPatch( + x + 80 + 3, y + 50, 0, + W_CachePatchName( + va( + "CUPBKUP%c", + (greyscale ? '1' : '2') + ), + PU_CACHE + ) + ); + } } void M_DrawLevelSelect(void) diff --git a/src/k_podium.cpp b/src/k_podium.cpp index daada2ec6..a0c275846 100644 --- a/src/k_podium.cpp +++ b/src/k_podium.cpp @@ -980,9 +980,11 @@ void K_FinishCeremony(void) g_podiumData.ranking = true; - // Play the noise now (via G_UpdateVisited's concluding gamedata save) + // Play the noise now (via G_UpdateVisited's concluding challenge check) prevmap = gamemap-1; G_UpdateVisited(); + if (gamedata->deferredsave) + G_SaveGameData(); } /*-------------------------------------------------- diff --git a/src/k_respawn.c b/src/k_respawn.c index f9c03e499..28f8d0403 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -14,6 +14,7 @@ #include "d_player.h" #include "k_kart.h" #include "k_battle.h" +#include "k_objects.h" // Obj_FindCheckpoint, etc #include "g_game.h" #include "p_local.h" #include "p_tick.h" @@ -163,6 +164,9 @@ void K_DoIngameRespawn(player_t *player) K_TumbleInterrupt(player); P_ResetPlayer(player); + mobj_t *checkpoint; + vector3_t pos; + // Set up respawn position if invalid if (player->respawn.manual == true) { @@ -194,6 +198,21 @@ void K_DoIngameRespawn(player_t *player) K_RespawnAtWaypoint(player, player->respawn.wp); } } + else if ((gametyperules & GTR_CHECKPOINTS) + && player->checkpointId + && (checkpoint = Obj_FindCheckpoint(player->checkpointId)) + && Obj_GetCheckpointRespawnPosition(checkpoint, &pos)) + { + player->respawn.wp = NULL; + player->respawn.flip = (checkpoint->flags2 & MF2_OBJECTFLIP) ? true : false; // K_RespawnOffset wants a boolean! + player->respawn.pointx = pos.x; + player->respawn.pointy = pos.y; + player->respawn.pointz = pos.z + K_RespawnOffset(player, player->respawn.flip); + + player->respawn.pointangle = Obj_GetCheckpointRespawnAngle(checkpoint); + + player->respawn.distanceleft = 0; + } else { UINT32 bestdist = UINT32_MAX; @@ -244,6 +263,7 @@ void K_DoIngameRespawn(player_t *player) player->respawn.pointx = 0; player->respawn.pointy = 0; player->respawn.pointz = 0; + player->respawn.pointangle = 0; player->respawn.flip = false; } else @@ -254,7 +274,7 @@ void K_DoIngameRespawn(player_t *player) player->respawn.pointx = beststart->x << FRACBITS; player->respawn.pointy = beststart->y << FRACBITS; - player->mo->angle = ( beststart->angle * ANG1 ); + player->respawn.pointangle = ( beststart->angle * ANG1 ); s = R_PointInSubsector(beststart->x << FRACBITS, beststart->y << FRACBITS)->sector; @@ -476,6 +496,11 @@ static void K_MovePlayerToRespawnPoint(player_t *player) else { // We can now drop! + if (gametyperules & GTR_CHECKPOINTS) + { + // Of course, in gametypes where there's a clear and intended progression, set our direction. + P_SetPlayerAngle(player, (player->drawangle = player->respawn.pointangle)); + } player->respawn.state = RESPAWNST_DROP; return; } diff --git a/src/k_roulette.c b/src/k_roulette.c index 107908fd7..faa6f0e92 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -183,7 +183,7 @@ static kartitems_t K_KartItemReelSpecialEnd[] = KITEM_NONE }; -static kartitems_t K_KartItemReelTimeAttack[] = +static kartitems_t K_KartItemReelRingSneaker[] = { KITEM_SNEAKER, KITEM_SUPERRING, @@ -1228,7 +1228,7 @@ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) fixed_t progress = 0; fixed_t total = 0; - if (bossinfo.valid == true) + if (K_CheckBossIntro() == true) { // Boss in action, use a speed controlled by boss health total = FixedDiv(bossinfo.healthbar, BOSSHEALTHBARLEN); @@ -1345,7 +1345,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet return; } } - else if (gametyperules & GTR_BOSS) + else if (K_CheckBossIntro() == true) { for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) { @@ -1356,10 +1356,10 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } else if (K_TimeAttackRules() == true) { - kartitems_t *presetlist = K_KartItemReelTimeAttack; + kartitems_t *presetlist = K_KartItemReelRingSneaker; // If the objective is not to go fast, it's to cause serious damage. - if (gametyperules & GTR_PRISONS) + if (battleprisons == true) { presetlist = K_KartItemReelBreakTheCapsules; } 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..f1ae5068e 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -661,6 +661,8 @@ void M_ClearStats(void) gamedata->evercrashed = false; gamedata->chaokeytutorial = false; gamedata->majorkeyskipattempted = false; + gamedata->enteredtutorialchallenge = false; + gamedata->finishedtutorialchallenge = false; gamedata->musicstate = GDMUSIC_NONE; gamedata->importprofilewins = false; @@ -1499,6 +1501,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 +2311,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..2b20bb769 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,8 @@ struct gamedata_t boolean evercrashed; boolean chaokeytutorial; boolean majorkeyskipattempted; + boolean enteredtutorialchallenge; + boolean finishedtutorialchallenge; gdmusic_t musicstate; // BACKWARDS COMPAT ASSIST diff --git a/src/menus/extras-1.c b/src/menus/extras-1.c index 24bf60c0f..cb371aec0 100644 --- a/src/menus/extras-1.c +++ b/src/menus/extras-1.c @@ -90,18 +90,7 @@ 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); + UINT16 map = G_GetFirstMapOfGametype(GT_TUTORIAL); EXTRAS_Main[extras_tutorial].status = (IT_STRING | ((map == NEXTMAP_INVALID) ? IT_TRANSTEXT : IT_CALL)); diff --git a/src/menus/extras-statistics.c b/src/menus/extras-statistics.c index 7e774315e..7d9971443 100644 --- a/src/menus/extras-statistics.c +++ b/src/menus/extras-statistics.c @@ -80,7 +80,11 @@ static void M_StatisticsMaps(void) headerexists = false; for (i = 0; i < nummapheaders; i++) { - M_StatisticsAddMap(i, NULL, &headerexists, true); + if (M_StatisticsAddMap(i, NULL, &headerexists, true)) + { + if (!(mapheaderinfo[i]->records.mapvisited & MV_BEATEN)) + break; + } } if ((i = statisticsmenu.numextramedals) != 0) diff --git a/src/menus/options-1.c b/src/menus/options-1.c index 6d91ab224..cfd7339e3 100644 --- a/src/menus/options-1.c +++ b/src/menus/options-1.c @@ -31,8 +31,10 @@ menuitem_t OPTIONS_Main[] = {IT_STRING | IT_SUBMENU, "Data Options", "Miscellaneous data options such as the screenshot format.", NULL, {.submenu = &OPTIONS_DataDef}, 0, 0}, +#ifdef TODONEWMANUAL {IT_STRING | IT_CALL, "Tricks & Secrets", "Those who bother reading a game manual always get the edge over those who don't!", NULL, {.routine = M_Manual}, 0, 0}, +#endif }; // For options menu, the 'extra1' field will determine the background colour to use for... the background! (What a concept!) diff --git a/src/menus/play-online-host.c b/src/menus/play-online-host.c index 0b34f36b1..31c1f449a 100644 --- a/src/menus/play-online-host.c +++ b/src/menus/play-online-host.c @@ -110,7 +110,6 @@ 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 cf5343b98..8929e25b8 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -125,9 +125,25 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) } for (i = 0; i < nummapheaders; i++) + { if (M_CanShowLevelInList(i, levelsearch)) + { count++; + // Tutorial will only show what you've made your way to + if (!levelsearch->checklocked) + continue; + if (!levelsearch->tutorial) + continue; + if (i >= basenummapheaders) + continue; + if (mapheaderinfo[i]->records.mapvisited & MV_BEATEN) + continue; + + break; + } + } + return count; } @@ -188,6 +204,13 @@ UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch) } else { + // Tutorial will only show what you've made your way to + if (levelsearch->checklocked + && levelsearch->tutorial + && mapnum < basenummapheaders + && !(mapheaderinfo[mapnum]->records.mapvisited & MV_BEATEN)) + return NEXTMAP_INVALID; + mapnum++; while (!M_CanShowLevelInList(mapnum, levelsearch) && mapnum < nummapheaders) mapnum++; @@ -215,6 +238,7 @@ boolean M_LevelListFromGametype(INT16 gt) { static boolean first = true; UINT8 temp = 0; + boolean invalidatedcursor = false; if (gt != -1) { @@ -244,6 +268,15 @@ boolean M_LevelListFromGametype(INT16 gt) } levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); + if (!levellist.levelsearch.cupmode) + { + invalidatedcursor = ( + levellist.levelsearch.cup != NULL + || levellist.levelsearch.tutorial != (gt == GT_TUTORIAL) + ); + } + + levellist.levelsearch.tutorial = (gt == GT_TUTORIAL); CV_SetValue(&cv_dummyspbattack, 0); } @@ -412,19 +445,36 @@ boolean M_LevelListFromGametype(INT16 gt) // Okay, just a list of maps then. - if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) == NEXTMAP_INVALID) + levellist.levelsearch.cup = NULL; + + UINT16 test = M_GetFirstLevelInList(&temp, &levellist.levelsearch); + + if (test == NEXTMAP_INVALID) { return false; } // Reset position properly if you go back & forth between gametypes - if (levellist.levelsearch.cup) + levellist.mapcount = M_CountLevelsToShowInList(&levellist.levelsearch); + + if (levellist.levelsearch.tutorial && levellist.levelsearch.checklocked) + { + // Find the first level we haven't played. + UINT16 possiblecursor = 0; + while (test < nummapheaders && (mapheaderinfo[test]->records.mapvisited & MV_BEATEN)) + { + test = M_GetNextLevelInList(test, &temp, &levellist.levelsearch); + possiblecursor++; + } + + if (test != NEXTMAP_INVALID) + levellist.cursor = possiblecursor; + } + else if (invalidatedcursor) { levellist.cursor = 0; - levellist.levelsearch.cup = NULL; } - levellist.mapcount = M_CountLevelsToShowInList(&levellist.levelsearch); M_LevelSelectScrollDest(); levellist.y = levellist.dest; @@ -450,7 +500,6 @@ 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) { diff --git a/src/menus/transient/manual.c b/src/menus/transient/manual.c index e6ca008eb..23d2fe776 100644 --- a/src/menus/transient/manual.c +++ b/src/menus/transient/manual.c @@ -64,6 +64,7 @@ void M_HandleImageDef(INT32 choice) } // Opening manual +#ifdef TODONEWMANUAL void M_Manual(INT32 choice) { (void)choice; @@ -71,3 +72,4 @@ void M_Manual(INT32 choice) MISC_ManualDef.prevMenu = (choice == INT32_MAX ? NULL : currentMenu); M_SetupNextMenu(&MISC_ManualDef, true); } +#endif diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 3f69e4655..4afe00d2e 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -40,6 +40,9 @@ menuitem_t PAUSE_Main[] = {IT_STRING | IT_ARROWS, "CALL VOTE", "M_ICOVOT", NULL, {.routine = M_HandlePauseMenuCallVote}, 0, 0}, + {IT_STRING | IT_CALL, "GIVE UP", "M_ICOGUP", + NULL, {.routine = M_GiveUp}, 0, 0}, + {IT_STRING | IT_CALL, "RESTART MAP", "M_ICORE", NULL, {.routine = M_RestartMap}, 0, 0}, @@ -130,6 +133,8 @@ void M_OpenPauseMenu(void) #ifdef HAVE_DISCORDRPC PAUSE_Main[mpause_discordrequests].status = IT_DISABLED; #endif + + PAUSE_Main[mpause_giveup].status = IT_DISABLED; PAUSE_Main[mpause_restartmap].status = IT_DISABLED; PAUSE_Main[mpause_tryagain].status = IT_DISABLED; @@ -172,19 +177,33 @@ void M_OpenPauseMenu(void) } else if (!netgame && !demo.playback) { - boolean retryallowed = (modeattacking != ATTACKING_NONE || gametype == GT_TUTORIAL); - if ( - retryallowed == false - && gamestate == GS_LEVEL - && G_GametypeUsesLives() - ) + boolean retryallowed = (modeattacking != ATTACKING_NONE); + boolean giveup = ( + grandprixinfo.gp == true + && grandprixinfo.eventmode != GPEVENT_NONE + && roundqueue.size != 0 + ); + + if (tutorialchallenge == TUTORIALSKIP_INPROGRESS) { - for (i = 0; i <= splitscreen; i++) + // NO RETRY, ONLY GIVE UP + giveup = true; + } + else if (gamestate == GS_LEVEL && !retryallowed) + { + if (gametype == GT_TUTORIAL) { - if (players[g_localplayers[i]].lives <= 1) - continue; retryallowed = true; - break; + } + else if (G_GametypeUsesLives()) + { + for (i = 0; i <= splitscreen; i++) + { + if (players[g_localplayers[i]].lives <= 1) + continue; + retryallowed = true; + break; + } } } @@ -192,6 +211,11 @@ void M_OpenPauseMenu(void) { PAUSE_Main[mpause_tryagain].status = IT_STRING | IT_CALL; } + + if (giveup) + { + PAUSE_Main[mpause_giveup].status = IT_STRING | IT_CALL; + } } if (netgame) // && (PAUSE_Main[mpause_admin].status == IT_DISABLED)) @@ -398,6 +422,35 @@ void M_TryAgain(INT32 choice) } } +static void M_GiveUpResponse(INT32 ch) +{ + if (ch != MA_YES) + return; + + if (exitcountdown != 1) + { + G_BeginLevelExit(); + exitcountdown = 1; + + if (server) + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + } + + M_ClearMenus(false); +} + +void M_GiveUp(INT32 choice) +{ + (void)choice; + if (demo.playback) + return; + + if (!Playing()) + return; + + M_StartMessage("Give up", M_GetText("Are you sure you want to\ngive up on this challenge?\n"), &M_GiveUpResponse, MM_YESNO, NULL, NULL); +} + // Pause spectate / join functions void M_ConfirmSpectate(INT32 choice) { diff --git a/src/p_inter.c b/src/p_inter.c index fe3045fb4..433dd5803 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1913,7 +1913,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget // spectating. Because in Free Play, this player // can enter the game again, and these flags would // make them intangible. - if (K_Cooperative() && !target->player->spectator) + if (!(gametyperules & GTR_CHECKPOINTS) && K_Cooperative() && !target->player->spectator) { target->player->pflags |= PF_ELIMINATED; @@ -2570,7 +2570,7 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, player->roundconditions.checkthisframe = true; } - if (gametyperules & GTR_BUMPERS) + if (gametyperules & (GTR_BUMPERS|GTR_CHECKPOINTS)) { player->mo->health--; } diff --git a/src/p_local.h b/src/p_local.h index 4cd26c9fd..90e56e610 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -80,6 +80,7 @@ extern thinker_t thlist[]; extern mobj_t *mobjcache; void P_InitThinkers(void); +void P_InvalidateThinkersWithoutInit(void); void P_AddThinker(const thinklistnum_t n, thinker_t *thinker); void P_RemoveThinker(thinker_t *thinker); void P_UnlinkThinker(thinker_t *thinker); diff --git a/src/p_mobj.c b/src/p_mobj.c index ebb9353b5..990758b8b 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12812,7 +12812,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing) else if (mobj->z == mobj->floorz) mobj->eflags |= MFE_ONGROUND; - mobj->angle = angle; + mobj->angle = p->drawangle = angle; // FAULT if (gamestate == GS_LEVEL && leveltime > introtime && !p->spectator) @@ -12827,6 +12827,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing) p->respawn.pointx = x; p->respawn.pointy = y; p->respawn.pointz = z; + p->respawn.pointangle = angle; } P_AfterPlayerSpawn(playernum); @@ -12881,7 +12882,7 @@ void P_MovePlayerToCheatcheck(INT32 playernum) } } else - p->drawangle = mobj->angle; // default to the camera angle + p->drawangle = mobj->angle = p->respawn.pointangle; K_DoIngameRespawn(p); p->respawn.truedeath = true; @@ -13055,6 +13056,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) @@ -13622,7 +13626,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 0bc686292..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) { @@ -8286,7 +8289,14 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } } - if (K_PodiumHasEmerald()) + // Default + levelfadecol = 31; + + if (gamestate == GS_TITLESCREEN) + { + ; + } + else if (K_PodiumHasEmerald()) { // Special Stage out if (ranspecialwipe != 2) @@ -8316,11 +8326,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) levelfadecol = 0; wipetype = wipe_encore_towhite; } - else - { - // Default - levelfadecol = 31; - } if (rendermode != render_none) { @@ -8343,6 +8348,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) K_UnsetDialogue(); + ACS_InvalidateMapScope(); + LUA_InvalidateLevel(); for (ss = sectors; sectors+numsectors != ss; ss++) diff --git a/src/p_spec.c b/src/p_spec.c index e8d8f8c57..681531333 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -4132,14 +4132,17 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha } else { + // args[2]: cap rings to -20 instead of 0 + SINT8 baseline = (args[2] ? -20 : 0); + // Don't push you below baseline - if (mo->player->rings <= 0) + if (mo->player->rings <= baseline) return false; rings = -(rings); - if (rings > mo->player->rings) - rings = mo->player->rings; + if (rings > (mo->player->rings - baseline)) + rings = (mo->player->rings - baseline); mo->player->rings -= rings; S_StartSound(mo, sfx_antiri); diff --git a/src/p_tick.c b/src/p_tick.c index 8743a2bc6..c61fed92f 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -55,6 +55,8 @@ tic_t leveltime; boolean thinkersCompleted; +UINT32 thinker_era = 0; + static boolean g_freezeCheat; static boolean g_freezeLevel; @@ -278,6 +280,8 @@ void P_InitThinkers(void) { UINT8 i; + P_InvalidateThinkersWithoutInit(); + for (i = 0; i < NUM_THINKERLISTS; i++) { thlist[i].prev = thlist[i].next = &thlist[i]; @@ -299,6 +303,15 @@ void P_InitThinkers(void) Obj_ResetCheckpoints(); } +// +// P_InvalidateThinkersWithoutInit +// + +void P_InvalidateThinkersWithoutInit(void) +{ + thinker_era++; +} + // Adds a new thinker at the end of the list. void P_AddThinker(const thinklistnum_t n, thinker_t *thinker) { @@ -855,15 +868,22 @@ void P_Ticker(boolean run) ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; - // TODO would this be laggy with more conditions in play... - if (((!demo.playback && leveltime > introtime && M_UpdateUnlockablesAndExtraEmblems(true, false)) - || (gamedata && gamedata->deferredsave))) - G_SaveGameData(); - } + if (gamedata && gamestate == GS_LEVEL && !demo.playback) + { + // Keep track of how long they've been playing! + gamedata->totalplaytime++; - // Keep track of how long they've been playing! - if (!demo.playback) // Don't increment if a demo is playing. - gamedata->totalplaytime++; + // TODO would this be laggy with more conditions in play... + if ( + (leveltime > introtime + && M_UpdateUnlockablesAndExtraEmblems(true, false)) + || gamedata->deferredsave + ) + { + G_SaveGameData(); + } + } + } if (run) { @@ -1049,7 +1069,7 @@ void P_Ticker(boolean run) } player_t *player = &players[i]; - if (K_PlayerTallyActive(player) == true && player->tally.done == false) + if (player->spectator == false && K_PlayerTallyActive(player) == true && player->tally.done == false) { run_exit_countdown = false; break; @@ -1153,7 +1173,7 @@ void P_Ticker(boolean run) P_RunChaseCameras(); } - if (run) + if (run && !levelloading && leveltime) { K_TickDialogue(); } diff --git a/src/p_tick.h b/src/p_tick.h index 58c154a1a..ab36c2e51 100644 --- a/src/p_tick.h +++ b/src/p_tick.h @@ -43,6 +43,8 @@ void P_PreTicker(INT32 frames); void P_DoTeamscrambling(void); void P_RemoveThinkerDelayed(thinker_t *thinker); //killed +extern UINT32 thinker_era; + mobj_t *P_SetTarget2(mobj_t **mo, mobj_t *target #ifdef PARANOIA , const char *source_file, int source_line diff --git a/src/p_user.c b/src/p_user.c index b2d3b1889..2bf8b8193 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2824,7 +2824,7 @@ static void P_DeathThink(player_t *player) } } - if ((player->pflags & PF_ELIMINATED) && (gametyperules & GTR_BUMPERS)) + if ((player->pflags & PF_ELIMINATED) /*&& (gametyperules & GTR_BUMPERS)*/) { playerGone = true; } diff --git a/src/st_stuff.c b/src/st_stuff.c index ab0f45e19..ac670d5c6 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1250,8 +1250,6 @@ static void ST_overlayDrawer(void) } K_DrawMidVote(); - - K_DrawDialogue(); } void ST_DrawDemoTitleEntry(void) @@ -1558,6 +1556,8 @@ void ST_Drawer(void) if (stagetitle) ST_drawTitleCard(); + K_DrawDialogue(); + // Replay manual-save stuff if (demo.recording && multiplayer && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime) { diff --git a/src/y_inter.c b/src/y_inter.c index bc7264a57..5da755835 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1905,7 +1905,9 @@ 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) + // or tutorial skip material + || (nextmapoverride == NEXTMAP_TUTORIALCHALLENGE+1 || tutorialchallenge != TUTORIALSKIP_NONE)) { intertype = int_none; return;