diff --git a/src/d_main.c b/src/d_main.c index d75a4d5013d9a92e7abccad7211721b3fa1f00b5..4d740cca340b60cc7074a345f9500d31c3d09ea3 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -984,6 +984,8 @@ void D_StartTitle(void) memset(&luabanks, 0, sizeof(luabanks)); lastmaploaded = 0; pickedchar = R_SkinAvailable(cv_skin.string); + currenthubmap = defaulthubmap; + hubmapenabled = currenthubmap != 0; // In case someone exits out at the same time they start a time attack run, // reset modeattacking @@ -995,6 +997,7 @@ void D_StartTitle(void) gameaction = ga_nothing; displayplayer = consoleplayer = 0; + gametype = -1; G_SetGametype(GT_COOP); paused = false; advancedemo = false; @@ -1377,6 +1380,9 @@ void D_SRB2Main(void) CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n"); Z_Init(); + gametype = -1; + G_SetGametype(GT_COOP); + clientGamedata = M_NewGameDataStruct(); serverGamedata = M_NewGameDataStruct(); @@ -1521,6 +1527,8 @@ void D_SRB2Main(void) wipegamestate = gamestate; savedata.lives = 0; // flag this as not-used + currenthubmap = defaulthubmap; + hubmapenabled = currenthubmap != 0; //------------------------------------------------ COMMAND LINE PARAMS @@ -1728,9 +1736,8 @@ void D_SRB2Main(void) if (newgametype != -1) { - j = gametype; G_SetGametype(newgametype); - D_GameTypeChanged(j); + D_GameTypeChanged(); } } diff --git a/src/deh_soc.c b/src/deh_soc.c index 343beb3012676b93256af35c0bd57ff6ba7e9076..5f741744be75b2642a55f8d6c4fe25cafb7fb6ec 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1584,6 +1584,7 @@ void readlevelheader(MYFILE *f, INT32 num) else if (fastcmp(word2, "EVALUATION")) i = 1101; else if (fastcmp(word2, "CREDITS")) i = 1102; else if (fastcmp(word2, "ENDING")) i = 1103; + else if (fastcmp(word2, "HUB")) i = 1104; else // Support using the actual map name, // i.e., Nextlevel = AB, Nextlevel = FZ, etc. @@ -1610,6 +1611,14 @@ void readlevelheader(MYFILE *f, INT32 num) mapheaderinfo[num-1]->marathonnext = (INT16)i; } + else if (fastcmp(word, "HUBMAP")) + { + // Convert to map number + if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0') + i = M_MapNumber(word2[0], word2[1]); + + mapheaderinfo[num-1]->hubmap = (INT16)i; + } else if (fastcmp(word, "TYPEOFLEVEL")) { if (i) // it's just a number @@ -3981,6 +3990,20 @@ void readmaincfg(MYFILE *f) tutorialmap = (INT16)value; } + else if (fastcmp(word, "DEFAULTHUBMAP")) + { + // Support using the actual map name, + // i.e., Level AB, Level FZ, etc. + + // Convert to map number + if (word2[0] >= 'A' && word2[0] <= 'Z') + value = M_MapNumber(word2[0], word2[1]); + else + value = get_number(word2); + + defaulthubmap = (INT16)value; + hubmapenabled = defaulthubmap != 0; + } else deh_warning("Maincfg: unknown word '%s'", word); } diff --git a/src/doomstat.h b/src/doomstat.h index b5b2984407cc7cf03d213de8cb70f3bab720fc88..e6acdaa784a37256389dde4ebc72aa2fcdce589e 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -96,7 +96,7 @@ extern boolean addedtogame; // true after the server has added you // Only true if >1 player. netgame => multiplayer but not (multiplayer=>netgame) extern boolean multiplayer; -extern INT16 gametype; +extern INT16 gametype, lastgametype; extern UINT32 gametyperules; extern INT16 gametypecount; @@ -145,6 +145,10 @@ extern INT16 titlemap; extern boolean hidetitlepics; extern INT16 bootmap; //bootmap for loading a map on startup +extern INT16 defaulthubmap; // default map number of hub +extern INT16 currenthubmap; // current map number of hub +extern boolean hubmapenabled; // hub map is enabled + extern INT16 tutorialmap; // map to load for tutorial extern boolean tutorialmode; // are we in a tutorial right now? extern INT32 tutorialgcs; // which control scheme is loaded? @@ -302,6 +306,7 @@ typedef struct UINT32 typeoflevel; ///< Combination of typeoflevel flags. INT16 nextlevel; ///< Map number of next level, or 1100-1102 to end. INT16 marathonnext; ///< See nextlevel, but for Marathon mode. Necessary to support hub worlds ala SUGOI. + INT16 hubmap; ///< Map number of the hub associated with this level. char keywords[32+1]; ///< Keywords separated by space to search for. 32 characters. char musname[6+1]; ///< Music track to play. "" for no music. UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore. @@ -407,6 +412,8 @@ enum GameType GT_CTF, // capture the flag + GT_HUB, + GT_FIRSTFREESLOT, GT_LASTFREESLOT = GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1, NUMGAMETYPES @@ -474,7 +481,7 @@ enum TypeOfLevel TOL_CTF = 0x40, ///< Capture the Flag // CTF default = 64 - // 0x80 was here + TOL_HUB = 0x80, ///< Hub TOL_2D = 0x0100, ///< 2D TOL_MARIO = 0x0200, ///< Mario diff --git a/src/g_game.c b/src/g_game.c index 1c186ae03149780b254c72f5243ed3d551350e52..837b95eef1373783b8b6bf6f3aaf06e15a3ff6ce 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -136,6 +136,10 @@ INT16 titlemap = 0; boolean hidetitlepics = false; INT16 bootmap; //bootmap for loading a map on startup +INT16 defaulthubmap = 0; // default map number of hub +INT16 currenthubmap = 0; // current map number of hub +boolean hubmapenabled = false; // hub map is enabled + INT16 tutorialmap = 0; // map to load for tutorial boolean tutorialmode = false; // are we in a tutorial right now? INT32 tutorialgcs = gcs_custom; // which control scheme is loaded? @@ -2353,7 +2357,7 @@ void G_Ticker(boolean run) memset(&luabanks, 0, sizeof(luabanks)); } - else if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE && players[consoleplayer].lives != INFLIVES) + else if (G_CanLoseLivesInGametype() && players[consoleplayer].playerstate == PST_LIVE && players[consoleplayer].lives != INFLIVES) players[consoleplayer].lives -= 1; G_DoReborn(consoleplayer); @@ -3133,7 +3137,7 @@ void G_DoReborn(INT32 playernum) return; //Exit function to avoid proccing other SP related mechanics } - if (countdowntimeup || (!(netgame || multiplayer) && (gametyperules & GTR_CAMPAIGN))) + if (countdowntimeup || (!(netgame || multiplayer) && ((gametyperules & GTR_CAMPAIGN) || (maptol & TOL_HUB)))) resetlevel = true; else if ((G_GametypeUsesCoopLives() || G_GametypeUsesCoopStarposts()) && (netgame || multiplayer) && !G_IsSpecialStage(gamemap)) { @@ -3420,23 +3424,27 @@ const char *Gametype_Names[NUMGAMETYPES] = "Tag", // GT_TAG "Hide & Seek", // GT_HIDEANDSEEK - "CTF" // GT_CTF + "CTF", // GT_CTF + + "Hub" // GT_HUB }; // For dehacked const char *Gametype_ConstantNames[NUMGAMETYPES] = { - "GT_COOP", // GT_COOP - "GT_COMPETITION", // GT_COMPETITION - "GT_RACE", // GT_RACE + "GT_COOP", + "GT_COMPETITION", + "GT_RACE", - "GT_MATCH", // GT_MATCH - "GT_TEAMMATCH", // GT_TEAMMATCH + "GT_MATCH", + "GT_TEAMMATCH", - "GT_TAG", // GT_TAG - "GT_HIDEANDSEEK", // GT_HIDEANDSEEK + "GT_TAG", + "GT_HIDEANDSEEK", - "GT_CTF" // GT_CTF + "GT_CTF", + + "GT_HUB" }; // Gametype rules @@ -3461,15 +3469,26 @@ UINT32 gametypedefaultrules[NUMGAMETYPES] = // CTF GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_TEAMFLAGS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_POWERSTONES|GTR_DEATHMATCHSTARTS|GTR_SPAWNINVUL|GTR_RESPAWNDELAY|GTR_PITYSHIELD, + + // Hub + GTR_LIVES|GTR_FRIENDLY|GTR_SPAWNENEMIES|GTR_ALLOWEXIT|GTR_CUTSCENES }; // -// Sets a new gametype. +// Sets a new gametype, also setting gametype rules accordingly. // -void G_SetGametype(INT16 gtype) +void G_SetGametype(INT16 newgametype) { - gametype = gtype; - gametyperules = gametypedefaultrules[gametype]; + if (newgametype >= 0 && newgametype < gametypecount && gametype != newgametype) + { + if (gametype == -1) + lastgametype = newgametype; + else + lastgametype = gametype; + + gametype = newgametype; + gametyperules = gametypedefaultrules[gametype]; + } } // @@ -3627,6 +3646,8 @@ UINT32 gametypetol[NUMGAMETYPES] = TOL_TAG, // Hide and Seek TOL_CTF, // CTF + + TOL_HUB // Hub }; tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { @@ -3645,6 +3666,8 @@ tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"TAG",TOL_TAG}, {"CTF",TOL_CTF}, + {"HUB",TOL_HUB}, + {"2D",TOL_2D}, {"MARIO",TOL_MARIO}, {"NIGHTS",TOL_NIGHTS}, @@ -3736,6 +3759,20 @@ boolean G_GametypeUsesLives(void) return false; } +// +// G_CanLoseLivesInGametype +// +// Returns true if player can lose lives in +// the current gametype. False otherwise. +// +boolean G_CanLoseLivesInGametype(void) +{ + if (maptol & TOL_HUB) + return false; + + return G_GametypeUsesLives(); +} + // // G_GametypeUsesCoopLives // @@ -3831,6 +3868,41 @@ boolean G_CompetitionGametype(void) return ((gametyperules & GTR_RACE) && (gametyperules & GTR_LIVES)); } +static boolean G_IsHubMapValid(INT16 mapnum) +{ + if (mapnum < 1 || mapnum > NUMMAPS) + return false; + + return mapheaderinfo[mapnum-1] && mapheaderinfo[mapnum-1]->typeoflevel & TOL_HUB; +} + +// Returns true if the current hub level can be warped to. +boolean G_IsHubAvailable(void) +{ + if (!hubmapenabled || marathonmode || modeattacking) + return false; + + if (M_MapLocked(currenthubmap, serverGamedata)) + return false; + + return G_IsHubMapValid(currenthubmap); +} + +// Updates the current hub map. Reading the function explains the function. +void G_UpdateCurrentHubMap(void) +{ + if (maptol & TOL_HUB) + { + if (!M_MapLocked(gamemap, serverGamedata)) + currenthubmap = gamemap; + } + else if (mapheaderinfo[gamemap-1] && G_IsHubMapValid(mapheaderinfo[gamemap-1]->hubmap)) + { + if (!M_MapLocked(mapheaderinfo[gamemap-1]->hubmap, serverGamedata)) + currenthubmap = mapheaderinfo[gamemap-1]->hubmap; + } +} + /** Get the typeoflevel flag needed to indicate support of a gametype. * In single-player, this always returns TOL_SP. * \param gametype The gametype for which support is desired. @@ -3840,7 +3912,11 @@ boolean G_CompetitionGametype(void) UINT32 G_TOLFlag(INT32 pgametype) { if (!multiplayer) - return TOL_SP; + { + if (pgametype != GT_HUB) + return TOL_SP; + } + return gametypetol[pgametype]; } @@ -4008,6 +4084,16 @@ static void G_HandleSaveLevel(void) } } +// Returns true if the hub is a valid next map. +static boolean G_IsHubValidNextMap(void) +{ + if (!hubmapenabled || marathonmode || modeattacking) + return false; + + // The hub is a valid next map even if it's not unlocked. + return G_IsHubMapValid(currenthubmap); +} + // // G_GetNextMap // @@ -4015,6 +4101,7 @@ INT16 G_GetNextMap(boolean ignoretokens, boolean silent) { INT32 i; INT16 newmapnum; + INT16 gametype_to_use; boolean spec = G_IsSpecialStage(gamemap); // go to next level @@ -4026,12 +4113,26 @@ INT16 G_GetNextMap(boolean ignoretokens, boolean silent) else { newmapnum = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1); + + if (newmapnum == 1104-1) // Going back to the hub + { + if (G_IsHubValidNextMap()) // Should handle Marathon Mode. + newmapnum = currenthubmap-1; + else + { + // The hub map isn't valid... Naughty! + // Just use the starting map instead. + if (marathonmode) + newmapnum = spmarathon_start-1; // Handled below + else + newmapnum = spstage_start-1; + } + } + if (marathonmode && newmapnum == spmarathon_start-1) newmapnum = 1100-1; // No infinite loop for you } - INT16 gametype_to_use; - if (nextgametype >= 0 && nextgametype < gametypecount) gametype_to_use = nextgametype; else @@ -4051,6 +4152,13 @@ INT16 G_GetNextMap(boolean ignoretokens, boolean silent) memset(visitedmap, 0, sizeof (visitedmap)); + // Allow warping to non-hub levels if in a hub level. + if (gametype_to_use == GT_HUB) + tolflag |= G_TOLFlag(lastgametype); + // Allow warping to hub levels if in a non-hub level. + else if (gametype_to_use != GT_HUB) + tolflag |= G_TOLFlag(GT_HUB); + while (!mapheaderinfo[cm] || !(mapheaderinfo[cm]->typeoflevel & tolflag)) { visitedmap[cm/8] |= (1<<(cm&7)); @@ -4092,7 +4200,7 @@ INT16 G_GetNextMap(boolean ignoretokens, boolean silent) if (newmapnum >= 1100-1 && newmapnum <= 1102-1 && !(gametyperules & GTR_CAMPAIGN)) newmapnum = (INT16)(spstage_start-1); - if (newmapnum < 0 || (newmapnum >= NUMMAPS && newmapnum < 1100-1) || newmapnum > 1103-1) + if (newmapnum < 0 || (newmapnum >= NUMMAPS && newmapnum < 1100-1) || newmapnum > 1104-1) I_Error("Followed map %d to invalid map %d\n", prevmap + 1, newmapnum + 1); if (!spec) @@ -5018,6 +5126,9 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 character, bo SplitScreen_OnChange(); } + currenthubmap = defaulthubmap; + hubmapenabled = currenthubmap != 0; + SetPlayerSkinByNum(consoleplayer, character); if (mapname) @@ -5122,6 +5233,8 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean maptol = mapheaderinfo[gamemap-1]->typeoflevel; globalweather = mapheaderinfo[gamemap-1]->weather; + G_UpdateCurrentHubMap(); + // Don't carry over custom music change to another map. mapmusflags |= MUSIC_RELOADRESET; diff --git a/src/g_game.h b/src/g_game.h index f72ea6b41b1b29b4b263b9039c0ad588bc78de17..3c0878858f5b1298045c20ad7582076a99f16e45 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -178,10 +178,14 @@ void G_SpawnPlayer(INT32 playernum); // A normal game starts at map 1, but a warp test can start elsewhere void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 character, boolean SSSG, boolean FLS); void G_DoLoadLevel(boolean resetplayer); + void G_StartTitleCard(void); void G_PreLevelTitleCard(void); boolean G_IsTitleCardAvailable(void); +boolean G_IsHubAvailable(void); +void G_UpdateCurrentHubMap(void); + // Can be called by the startup code or M_Responder, calls P_SetupLevel. void G_LoadGame(UINT32 slot, INT16 mapoverride); @@ -195,7 +199,7 @@ extern UINT32 gametypedefaultrules[NUMGAMETYPES]; extern UINT32 gametypetol[NUMGAMETYPES]; extern INT16 gametyperankings[NUMGAMETYPES]; -void G_SetGametype(INT16 gametype); +void G_SetGametype(INT16 newgametype); INT16 G_AddGametype(UINT32 rules); void G_AddGametypeConstant(INT16 gtype, const char *newgtconst); void G_UpdateGametypeSelections(void); @@ -206,6 +210,7 @@ void G_SetGametypeDescription(INT16 gtype, char *descriptiontext, UINT8 leftcolo INT32 G_GetGametypeByName(const char *gametypestr); boolean G_IsSpecialStage(INT32 mapnum); boolean G_GametypeUsesLives(void); +boolean G_CanLoseLivesInGametype(void); boolean G_GametypeUsesCoopLives(void); boolean G_GametypeUsesCoopStarposts(void); boolean G_GametypeHasTeams(void); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 7f2560bcc4adc2f398d87aa8066119ee9ec3b455..c4f8697c476027755ca3b8735bfc4b91caf28e0f 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2536,8 +2536,7 @@ static void HU_DrawRankings(void) UINT32 whiteplayer; // draw the current gametype in the lower right - if (gametype >= 0 && gametype < gametypecount) - V_DrawString(4, splitscreen ? 184 : 192, 0, Gametype_Names[gametype]); + V_DrawString(4, splitscreen ? 184 : 192, 0, Gametype_Names[gametype]); if (gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT)) { diff --git a/src/lua_baselib.c b/src/lua_baselib.c index ecd1ee55e648019fb883917ca361cc45ba8847b2..af0c3c575921eec3e89e23ce0740983341eec15c 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -4298,6 +4298,14 @@ static int lib_gCompetitionGametype(lua_State *L) return 1; } +static int lib_gIsHubAvailable(lua_State *L) +{ + //HUDSAFE + INLEVEL + lua_pushboolean(L, G_IsHubAvailable()); + return 1; +} + static int lib_gTicsToHours(lua_State *L) { tic_t rtic = luaL_checkinteger(L, 1); @@ -4645,6 +4653,7 @@ static luaL_Reg lib[] = { {"G_CoopGametype",lib_gCoopGametype}, {"G_TagGametype",lib_gTagGametype}, {"G_CompetitionGametype",lib_gCompetitionGametype}, + {"G_IsHubAvailable",lib_gIsHubAvailable}, {"G_TicsToHours",lib_gTicsToHours}, {"G_TicsToMinutes",lib_gTicsToMinutes}, {"G_TicsToSeconds",lib_gTicsToSeconds}, diff --git a/src/lua_maplib.c b/src/lua_maplib.c index c946b10ce220fd28e2415d46f68233fad4bc7f0b..171244742ec38dd60d17ecf4cb67497419a2dd28 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -2801,6 +2801,7 @@ enum mapheaderinfo_e mapheaderinfo_typeoflevel, mapheaderinfo_nextlevel, mapheaderinfo_marathonnext, + mapheaderinfo_hubmap, mapheaderinfo_keywords, mapheaderinfo_musname, mapheaderinfo_mustrack, @@ -2849,6 +2850,7 @@ static const char *const mapheaderinfo_opt[] = { "typeoflevel", "nextlevel", "marathonnext", + "hubmap", "keywords", "musname", "mustrack", @@ -2919,6 +2921,9 @@ static int mapheaderinfo_get(lua_State *L) case mapheaderinfo_marathonnext: lua_pushinteger(L, header->marathonnext); break; + case mapheaderinfo_hubmap: + lua_pushinteger(L, header->hubmap); + break; case mapheaderinfo_keywords: lua_pushstring(L, header->keywords); break; diff --git a/src/lua_script.c b/src/lua_script.c index 686555a16d6b09b98d839cefec0ac0fc876181ae..92ae3d89a6fef8d7085cf1eb94457a32f8d44828 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -180,6 +180,15 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"stoppedclock")) { lua_pushboolean(L, stoppedclock); return 1; + } else if (fastcmp(word,"defaulthubmap")) { + lua_pushinteger(L, defaulthubmap); + return 1; + } else if (fastcmp(word, "currenthubmap")) { + lua_pushinteger(L, currenthubmap); + return 1; + } else if (fastcmp(word, "hubmapenabled")) { + lua_pushboolean(L, hubmapenabled); + return 1; } else if (fastcmp(word,"netgame")) { lua_pushboolean(L, netgame); return 1; @@ -444,7 +453,17 @@ int LUA_PushGlobals(lua_State *L, const char *word) // See the above. int LUA_CheckGlobals(lua_State *L, const char *word) { - if (fastcmp(word, "redscore")) + if (fastcmp(word, "currenthubmap")) { + int mapnum = luaL_checkinteger(L, 2); + if (mapnum < 0 || mapnum > NUMMAPS) { + luaL_error(L, "map number %d out of range (1 - %d)", mapnum, NUMMAPS); + return 1; + } + currenthubmap = (INT16)mapnum; + } + else if (fastcmp(word, "hubmapenabled")) + hubmapenabled = luaL_checkboolean(L, 2); + else if (fastcmp(word, "redscore")) redscore = (UINT32)luaL_checkinteger(L, 2); else if (fastcmp(word, "bluescore")) bluescore = (UINT32)luaL_checkinteger(L, 2); diff --git a/src/m_menu.c b/src/m_menu.c index 37d191a0df84158e31d0d782d6f4b2d6b819eadc..1ad9bf5bd3e8fd60546e6139d0244f5af7867afd 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -254,6 +254,7 @@ static void M_SinglePlayerMenu(INT32 choice); static void M_Options(INT32 choice); static void M_SelectableClearMenus(INT32 choice); static void M_Retry(INT32 choice); +static void M_ReturnToHub(INT32 choice); static void M_EndGame(INT32 choice); static void M_MapChange(INT32 choice); static void M_ChangeLevel(INT32 choice); @@ -407,6 +408,9 @@ static void M_HandleVideoMode(INT32 choice); static void M_ResetCvars(void); +static boolean M_CanShowReturnToHub(void); +static boolean M_CanSelectReturnToHub(void); + // Consvar onchange functions static void Newgametype_OnChange(void); static void Dummymares_OnChange(void); @@ -545,6 +549,7 @@ typedef enum // --------------------- // Pause Menu MP Edition // --------------------- +// Caution: M_StartControlPanel recalculates the positions of all items after mpause_returntohub. static menuitem_t MPauseMenu[] = { {IT_STRING | IT_CALL, NULL, "Add-ons...", M_Addons, 8}, @@ -552,6 +557,7 @@ static menuitem_t MPauseMenu[] = {IT_STRING | IT_CALL, NULL, "Emblem Hints...", M_EmblemHints, 24}, {IT_STRING | IT_CALL, NULL, "Switch Gametype/Level...", M_MapChange, 32}, + {IT_STRING | IT_CALL, NULL, "Return to Hub", M_ReturnToHub, 48}, {IT_STRING | IT_CALL, NULL, "Continue", M_SelectableClearMenus,48}, {IT_STRING | IT_CALL, NULL, "Player 1 Setup", M_SetupMultiPlayer, 56}, // splitscreen @@ -575,6 +581,7 @@ typedef enum mpause_hints, mpause_switchmap, + mpause_returntohub, mpause_continue, mpause_psetupsplit, mpause_psetupsplit2, @@ -591,13 +598,14 @@ typedef enum // --------------------- // Pause Menu SP Edition // --------------------- +// Caution: M_StartControlPanel recalculates the positions of all items after spause_returntohub. static menuitem_t SPauseMenu[] = { - // Pandora's Box will be shifted up if both options are available {IT_CALL | IT_STRING, NULL, "Pandora's Box...", M_PandorasBox, 16}, {IT_CALL | IT_STRING, NULL, "Emblem Hints...", M_EmblemHints, 24}, {IT_CALL | IT_STRING, NULL, "Level Select...", M_PauseLevelSelect, 32}, + {IT_CALL | IT_STRING, NULL, "Return to Hub", M_ReturnToHub, 48}, {IT_CALL | IT_STRING, NULL, "Continue", M_SelectableClearMenus,48}, {IT_CALL | IT_STRING, NULL, "Retry", M_Retry, 56}, {IT_CALL | IT_STRING, NULL, "Options", M_Options, 64}, @@ -611,6 +619,7 @@ typedef enum spause_pandora = 0, spause_hints, spause_levelselect, + spause_returntohub, spause_continue, spause_retry, @@ -646,6 +655,7 @@ gtdesc_t gametypedesc[NUMGAMETYPES] = {{123, 123}, "Whoever's IT has to hunt down everyone else. If you get caught, you have to turn on your former friends!"}, {{150, 150}, "Try and find a good hiding place in these maps - we dare you."}, {{ 37, 153}, "Steal the flag from the enemy's base and bring it back to your own, but watch out - they could just as easily steal yours!"}, + {{182, 182}, "Walk through a selection of levels."} }; static menuitem_t MISC_ChangeLevelMenu[] = @@ -3638,6 +3648,8 @@ void M_Drawer(void) // void M_StartControlPanel(void) { + INT32 offset; + // time attack HACK if (modeattacking && demoplayback) { @@ -3710,12 +3722,26 @@ void M_StartControlPanel(void) // And emblem hints. SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED); - // Shift up Pandora's Box if both pandora and levelselect are active - /*if (SPauseMenu[spause_pandora].status != (IT_DISABLED) - && SPauseMenu[spause_levelselect].status != (IT_DISABLED)) - SPauseMenu[spause_pandora].alphaKey = 24; + // Shift down some selections if "Return to Hub" is available. + if (M_CanShowReturnToHub()) + { + SPauseMenu[spause_returntohub].status = M_CanSelectReturnToHub() ? (IT_STRING | IT_CALL) : IT_GRAYEDOUT; + + offset = 8; + } else - SPauseMenu[spause_pandora].alphaKey = 32;*/ + { + SPauseMenu[spause_returntohub].status = IT_DISABLED; + + offset = 0; + } + + SPauseMenu[spause_continue].alphaKey = 48 + offset; + SPauseMenu[spause_retry].alphaKey = 56 + offset; + SPauseMenu[spause_options].alphaKey = 64 + offset; + + SPauseMenu[spause_title].alphaKey = 80 + offset; + SPauseMenu[spause_quit].alphaKey = 88 + offset; currentMenu = &SPauseDef; itemOn = spause_continue; @@ -3757,6 +3783,38 @@ void M_StartControlPanel(void) MPauseMenu[mpause_spectate].status = IT_GRAYEDOUT; } + // Shift down some selections if "Return to Hub" is available. + if (M_CanShowReturnToHub()) + { + MPauseMenu[mpause_returntohub].status = M_CanSelectReturnToHub() ? (IT_STRING | IT_CALL) : IT_GRAYEDOUT; + + offset = 8; + } + else + { + MPauseMenu[mpause_returntohub].status = IT_DISABLED; + + offset = 0; + } + + if (splitscreen) + { + MPauseMenu[mpause_psetupsplit].alphaKey = 56 + offset; + MPauseMenu[mpause_psetupsplit2].alphaKey = 64 + offset; + } + else + { + MPauseMenu[mpause_switchteam].alphaKey = + MPauseMenu[mpause_entergame].alphaKey = + MPauseMenu[mpause_spectate].alphaKey = 56 + offset; + MPauseMenu[mpause_psetup].alphaKey = 64 + offset; + } + + MPauseMenu[mpause_continue].alphaKey = 48 + offset; + MPauseMenu[mpause_options].alphaKey = 72 + offset; + MPauseMenu[mpause_title].alphaKey = 88 + offset; + MPauseMenu[mpause_quit].alphaKey = 96 + offset; + MPauseMenu[mpause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata) && G_CoopGametype()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); currentMenu = &MPauseDef; @@ -3773,6 +3831,26 @@ void M_EndModeAttackRun(void) M_ModeAttackEndGame(0); } +// Hides "Return to Hub" if there is no current hub map, or it's locked. +// Also, hides it if you aren't the server. +static boolean M_CanShowReturnToHub(void) +{ + if (netgame && !server) + return false; + + // M_MapLocked doesn't do bounds checking (oops!) + return currenthubmap != 0 && !M_MapLocked(currenthubmap, serverGamedata); +} + +// Returns true if you are able to select "Return to Hub". +static boolean M_CanSelectReturnToHub(void) +{ + if (gamestate != GS_LEVEL) + return false; + + return G_IsHubAvailable() && gamemap != currenthubmap; +} + // // M_ClearMenus // @@ -5089,24 +5167,6 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt) if (G_IsSpecialStage(mapnum+1)) return false; - if (gt == GT_COOP && (mapheaderinfo[mapnum]->typeoflevel & TOL_COOP)) - return true; - - if (gt == GT_COMPETITION && (mapheaderinfo[mapnum]->typeoflevel & TOL_COMPETITION)) - return true; - - if (gt == GT_CTF && (mapheaderinfo[mapnum]->typeoflevel & TOL_CTF)) - return true; - - if ((gt == GT_MATCH || gt == GT_TEAMMATCH) && (mapheaderinfo[mapnum]->typeoflevel & TOL_MATCH)) - return true; - - if ((gt == GT_TAG || gt == GT_HIDEANDSEEK) && (mapheaderinfo[mapnum]->typeoflevel & TOL_TAG)) - return true; - - if (gt == GT_RACE && (mapheaderinfo[mapnum]->typeoflevel & TOL_RACE)) - return true; - if (gt >= 0 && gt < gametypecount && (mapheaderinfo[mapnum]->typeoflevel & gametypetol[gt])) return true; @@ -6931,6 +6991,24 @@ static void M_Retry(INT32 choice) M_StartMessage(M_GetText("Retry this act from the last starpost?\n\n(Press 'Y' to confirm)\n"),M_RetryResponse,MM_YESNO); } +static void M_ReturnToHubResponse(INT32 ch) +{ + if (ch != 'y' && ch != KEY_ENTER) + return; + + if (!M_CanSelectReturnToHub()) + return; + + M_ClearMenus(true); + D_MapChange(currenthubmap, gametype, false, false, 0, false, false); +} + +static void M_ReturnToHub(INT32 choice) +{ + (void)choice; + M_StartMessage(M_GetText("Return to the hub?\nAny progress in the\ncurrent act will be lost.\n\n(Press 'Y' to confirm)\n"),M_ReturnToHubResponse,MM_YESNO); +} + static void M_SelectableClearMenus(INT32 choice) { (void)choice; @@ -9567,7 +9645,9 @@ static void M_Statistics(INT32 choice) if (!mapheaderinfo[i] || mapheaderinfo[i]->lvlttl[0] == '\0') continue; - if (!(mapheaderinfo[i]->typeoflevel & TOL_SP) || (mapheaderinfo[i]->menuflags & LF2_HIDEINSTATS)) + if (!(mapheaderinfo[i]->typeoflevel & TOL_SP) + || (mapheaderinfo[i]->typeoflevel & TOL_HUB) + || (mapheaderinfo[i]->menuflags & LF2_HIDEINSTATS)) continue; if (!(clientGamedata->mapvisited[i] & MV_MAX)) diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index 6702e2591254349a9bd3dde6d08b7fa86ef7fb29..5f7ad68140c2c22d3eff07a68920fc6396385a95 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -815,6 +815,7 @@ void SV_StartSinglePlayerServer(void) server = true; netgame = false; multiplayer = false; + gametype = -1; G_SetGametype(GT_COOP); // no more tic the game with this settings! diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c index 94170fa0df401064f5c1a2f9569cfceb0b0c490e..897e6dc27ce04cf8fef86b8eec99f8fa1a671c5c 100644 --- a/src/netcode/d_netcmd.c +++ b/src/netcode/d_netcmd.c @@ -406,9 +406,10 @@ boolean timedemo_csv; char timedemo_csv_id[256]; boolean timedemo_quit; -INT16 gametype = GT_COOP; +INT16 gametype = -1; +INT16 lastgametype = -1; UINT32 gametyperules = 0; -INT16 gametypecount = (GT_CTF + 1); +INT16 gametypecount = (GT_HUB + 1); boolean splitscreen = false; boolean circuitmap = false; @@ -1731,6 +1732,20 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese SendNameAndColor2(); } } + + if (gametype == newgametype && mapheaderinfo[mapnum-1]) + { + // Change gametype to Hub if warping into a hub level + if ((gametyperules & GTR_CAMPAIGN) && (mapheaderinfo[mapnum-1]->typeoflevel & TOL_HUB)) + { + newgametype = GT_HUB; + } + // Change gametype back to the previous one if warping out of a hub level + else if ((maptol & TOL_HUB) && (mapheaderinfo[mapnum-1]->typeoflevel & TOL_HUB) == 0) + { + newgametype = lastgametype; + } + } } CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d ultmode=%d resetplayers=%d delay=%d skipprecutscene=%d\n", mapnum, newgametype, pultmode, resetplayers, delay, skipprecutscene); @@ -1838,6 +1853,8 @@ static void Command_Map_f(void) INT32 newgametype = gametype; + UINT32 tolflags; + INT32 d; if (client && !IsPlayerAdmin(consoleplayer)) @@ -1965,15 +1982,35 @@ static void Command_Map_f(void) // don't use a gametype the map doesn't support // G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer + tolflags = G_TOLFlag(newgametype); + + // Allow warping to non-hub levels if in a hub level. + if (newgametype == GT_HUB && lastgametype != -1) + tolflags |= G_TOLFlag(lastgametype); + // Allow warping to hub levels if in a non-hub level. + else if (newgametype != GT_HUB) + tolflags |= G_TOLFlag(GT_HUB); + if (!( mapheaderinfo[newmapnum-1] && - mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype) + mapheaderinfo[newmapnum-1]->typeoflevel & tolflags )) { if (prevent_cheat && !cv_skipmapcheck.value) { - CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), - (multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player")); + const char *gametype_name; + + if (!multiplayer && newgametype == GT_COOP) + { + gametype_name = M_GetText("Single Player"); + } + else + { + gametype_name = Gametype_Names[newgametype]; + } + + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), + realmapname, G_BuildMapName(newmapnum), gametype_name); Z_Free(realmapname); Z_Free(mapname); return; @@ -2044,9 +2081,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) { char mapname[MAX_WADPATH+1]; UINT8 flags; - INT32 resetplayer = 1, lastgametype; + INT32 resetplayer = 1; UINT8 skipprecutscene, FLS; INT16 mapnumber; + UINT8 newgametype; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -2066,17 +2104,12 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) ultimatemode = false; resetplayer = ((flags & (1<<1)) == 0); + newgametype = READUINT8(*cp); - lastgametype = gametype; - gametype = READUINT8(*cp); - - if (gametype < 0 || gametype >= gametypecount) - gametype = lastgametype; - else - G_SetGametype(gametype); + G_SetGametype(newgametype); if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype + D_GameTypeChanged(); skipprecutscene = ((flags & (1<<2)) != 0); @@ -3955,22 +3988,13 @@ static void Command_ModDetails_f(void) // static void Command_ShowGametype_f(void) { - const char *gametypestr = NULL; - - if (!(netgame || multiplayer)) // print "Single player" instead of "Co-op" + if (!(netgame || multiplayer) && gametype == GT_COOP) // print "Single player" instead of "Co-op" { - CONS_Printf(M_GetText("Current gametype is %s\n"), M_GetText("Single player")); + CONS_Printf(M_GetText("Current gametype is %s\n"), M_GetText("Single Player")); return; } - // get name string for current gametype - if (gametype >= 0 && gametype < gametypecount) - gametypestr = Gametype_Names[gametype]; - - if (gametypestr) - CONS_Printf(M_GetText("Current gametype is %s\n"), gametypestr); - else // string for current gametype was not found above (should never happen) - CONS_Printf(M_GetText("Unknown gametype set (%d)\n"), gametype); + CONS_Printf(M_GetText("Current gametype is %s\n"), Gametype_Names[gametype]); } /** Plays the intro. @@ -4210,7 +4234,14 @@ static void TimeLimit_OnChange(void) // Some people might like to use them together. It works. } else if (netgame || multiplayer) - CONS_Printf(M_GetText("Time limit disabled\n")); + { + if (timelimitintics != 0) + { + CONS_Printf(M_GetText("Time limit disabled\n")); + } + + timelimitintics = 0; + } } /** Adjusts certain settings to match a changed gametype. @@ -4220,20 +4251,13 @@ static void TimeLimit_OnChange(void) * \author Graue <graue@oceanbase.org> * \todo Get rid of the hardcoded stuff, ugly stuff, etc. */ -void D_GameTypeChanged(INT32 lastgametype) +void D_GameTypeChanged(void) { if (netgame) { - const char *oldgt = NULL, *newgt = NULL; - - if (lastgametype >= 0 && lastgametype < gametypecount) - oldgt = Gametype_Names[lastgametype]; - if (gametype >= 0 && lastgametype < gametypecount) - newgt = Gametype_Names[gametype]; - - if (oldgt && newgt) - CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt); + CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), Gametype_Names[lastgametype], Gametype_Names[gametype]); } + // Only do the following as the server, not as remote admin. // There will always be a server, and this only needs to be done once. if (server && (multiplayer || netgame)) @@ -4293,10 +4317,6 @@ void D_GameTypeChanged(INT32 lastgametype) break; } } - else if (!multiplayer && !netgame) - { - G_SetGametype(GT_COOP); - } // reset timelimit and pointlimit in race/coop, prevent stupid cheats if (server) diff --git a/src/netcode/d_netcmd.h b/src/netcode/d_netcmd.h index c11575575fc025bcce4ad755651e3b77493233db..a9a91d8b22c3bbe2c7e5cec7ee05eb85e666b002 100644 --- a/src/netcode/d_netcmd.h +++ b/src/netcode/d_netcmd.h @@ -203,7 +203,7 @@ boolean EnsurePlayerNameIsGood(char *name, INT32 playernum); void D_SendPlayerConfig(void); void Command_ExitGame_f(void); void Command_Retry_f(void); -void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore +void D_GameTypeChanged(void); void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pultmode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect); boolean IsPlayerAdmin(INT32 playernum); void SetAdminPlayer(INT32 playernum); diff --git a/src/p_inter.c b/src/p_inter.c index 27e612154dc87c1b88fec2e42e890af0d27c382e..06884beef63a4e88321a0e3e9c90d409a3ec6597 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2691,7 +2691,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget if ((target->player->lives <= 1) && (netgame || multiplayer) && G_GametypeUsesCoopLives() && (cv_cooplives.value == 0)) ; else if ((!target->player->bot || target->player->bot == BOT_MPAI) && !target->player->spectator && (target->player->lives != INFLIVES) - && G_GametypeUsesLives()) + && G_CanLoseLivesInGametype()) { if (!(target->player->pflags & PF_FINISHED)) target->player->lives -= 1; // Lose a life Tails 03-11-2000 diff --git a/src/p_saveg.c b/src/p_saveg.c index 650622f59f8130a896257748eb56a4116e963f7e..e37c2713aed355094a1e96331831e165c3d0d8fd 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4788,6 +4788,8 @@ static void P_NetArchiveMisc(save_t *save_p, boolean resending) else P_WriteINT16(save_p, gamestate); P_WriteINT16(save_p, gametype); + P_WriteINT16(save_p, currenthubmap); + P_WriteUINT8(save_p, hubmapenabled); { UINT32 pig = 0; @@ -4886,6 +4888,8 @@ static inline boolean P_NetUnArchiveMisc(save_t *save_p, boolean reloading) G_SetGamestate(P_ReadINT16(save_p)); gametype = P_ReadINT16(save_p); + currenthubmap = P_ReadINT16(save_p); + hubmapenabled = P_ReadUINT8(save_p); { UINT32 pig = P_ReadUINT32(save_p); diff --git a/src/p_setup.c b/src/p_setup.c index c2b8f2db2e367cc23fcdb704f23fe572360eea81..9ede7adfe0eee61d20f0bc8b2e3c6fcfc4583085 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -355,6 +355,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i) mapheaderinfo[num]->actnum = 0; mapheaderinfo[num]->typeoflevel = 0; mapheaderinfo[num]->nextlevel = (INT16)(i + 1); + mapheaderinfo[num]->hubmap = 0; mapheaderinfo[num]->marathonnext = 0; mapheaderinfo[num]->startrings = 0; mapheaderinfo[num]->sstimer = 90; diff --git a/src/p_spec.c b/src/p_spec.c index 93809cbb4b68118e4fc4032186c5e772435daba2..7d0a51b2fad03d2c790a95710b9e85025c7edb14 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -4550,7 +4550,7 @@ static void P_ProcessExitSector(player_t *player, mtag_t sectag) P_SetupSignExit(player); - if (!G_CoopGametype()) + if (!(G_CoopGametype() || (maptol & TOL_HUB))) return; // Custom exit! diff --git a/src/st_stuff.c b/src/st_stuff.c index 4fdacd51ada1871c4a711f91749e0d9b39872796..4541b0adb3e590c096a85e8b730775dad51a116e 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -968,7 +968,7 @@ static void ST_drawLivesArea(void) } else { - if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)) && !(stplyr->pflags & PF_FINISHED)) + if (G_CanLoseLivesInGametype() && stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)) && !(stplyr->pflags & PF_FINISHED)) livescount++; if (livescount > 99) livescount = 99; diff --git a/src/y_inter.c b/src/y_inter.c index d7e644567eb14030e1bbb569434e1db460d8b56c..e2343b530dc1d1b81aadf48a542cef7ba1fd70d2 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -165,7 +165,22 @@ static INT32 tallydonetic = -1; static INT32 endtic = -1; intertype_t intertype = int_none; -intertype_t intermissiontypes[NUMGAMETYPES]; +intertype_t intermissiontypes[NUMGAMETYPES] = +{ + int_coop, // GT_COOP + int_comp, // GT_COMPETITION + int_race, // GT_RACE + + int_match, // GT_MATCH + int_teammatch, // GT_TEAMMATCH + + int_match, // GT_TAG + int_match, // GT_HIDEANDSEEK + + int_ctf, // GT_CTF + + int_none // GT_HUB +}; static huddrawlist_h luahuddrawlist_intermission; @@ -1275,25 +1290,11 @@ void Y_Ticker(void) // void Y_DetermineIntermissionType(void) { - // set to int_none initially - intertype = int_none; + intertype = intermissiontypes[gametype]; - if (intermissiontypes[gametype] != int_none) - intertype = intermissiontypes[gametype]; - else if (gametype == GT_COOP) - intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop; - else if (gametype == GT_TEAMMATCH) - intertype = int_teammatch; - else if (gametype == GT_MATCH - || gametype == GT_TAG - || gametype == GT_HIDEANDSEEK) - intertype = int_match; - else if (gametype == GT_RACE) - intertype = int_race; - else if (gametype == GT_COMPETITION) - intertype = int_comp; - else if (gametype == GT_CTF) - intertype = int_ctf; + // Special case for special stages + if (intertype == int_coop && G_IsSpecialStage(gamemap)) + intertype = int_spec; } //