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;
 }
 
 //