diff --git a/src/d_main.c b/src/d_main.c
index d75a4d5013d9a92e7abccad7211721b3fa1f00b5..40d9319fd867bb2b57443c0817b825910e369019 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1377,6 +1377,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();
 
@@ -1728,9 +1731,8 @@ void D_SRB2Main(void)
 
 			if (newgametype != -1)
 			{
-				j = gametype;
 				G_SetGametype(newgametype);
-				D_GameTypeChanged(j);
+				D_GameTypeChanged();
 			}
 		}
 
diff --git a/src/doomstat.h b/src/doomstat.h
index b5b2984407cc7cf03d213de8cb70f3bab720fc88..d05620582a06256dce0af266764c71e8941f992b 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;
 
@@ -407,6 +407,8 @@ enum GameType
 
 	GT_CTF, // capture the flag
 
+	GT_HUB,
+
 	GT_FIRSTFREESLOT,
 	GT_LASTFREESLOT = GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1,
 	NUMGAMETYPES
@@ -474,7 +476,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..ea29d3091618195a3a11538cc12187d522c9663b 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2353,7 +2353,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 +3133,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 +3420,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_TEAMMATCH",
 
-	"GT_MATCH", // GT_MATCH
-	"GT_TEAMMATCH", // GT_TEAMMATCH
+	"GT_TAG",
+	"GT_HIDEANDSEEK",
 
-	"GT_TAG", // GT_TAG
-	"GT_HIDEANDSEEK", // GT_HIDEANDSEEK
+	"GT_CTF",
 
-	"GT_CTF" // GT_CTF
+	"GT_HUB"
 };
 
 // Gametype rules
@@ -3461,15 +3465,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)
 {
-	gametype = gtype;
-	gametyperules = gametypedefaultrules[gametype];
+	if (gametype != gtype)
+	{
+		if (gametype == -1)
+			lastgametype = gtype;
+		else
+			lastgametype = gametype;
+
+		gametype = gtype;
+		gametyperules = gametypedefaultrules[gametype];
+	}
 }
 
 //
@@ -3627,6 +3642,8 @@ UINT32 gametypetol[NUMGAMETYPES] =
 	TOL_TAG, // Hide and Seek
 
 	TOL_CTF, // CTF
+
+	TOL_HUB // Hub
 };
 
 tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = {
@@ -3645,6 +3662,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 +3755,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
 //
@@ -3840,7 +3873,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];
 }
 
@@ -4051,6 +4088,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 && lastgametype != -1)
+				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));
diff --git a/src/g_game.h b/src/g_game.h
index f72ea6b41b1b29b4b263b9039c0ad588bc78de17..d9436b69ce013791f230dc863130f70ca553ddeb 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -206,6 +206,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/m_menu.c b/src/m_menu.c
index 37d191a0df84158e31d0d782d6f4b2d6b819eadc..3e597682c5686aeb02e8409a4fa7f526e1790b21 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -646,6 +646,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[] =
@@ -5089,24 +5090,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;
 
@@ -9567,7 +9550,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_netcmd.c b/src/netcode/d_netcmd.c
index 94170fa0df401064f5c1a2f9569cfceb0b0c490e..4214425c454a183b3ceddc0360b2aef4ecd7330b 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,22 @@ 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)
+				&& lastgametype != -1
+				&& (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 +1855,8 @@ static void Command_Map_f(void)
 
 	INT32 newgametype = gametype;
 
+	UINT32 tolflags;
+
 	INT32 d;
 
 	if (client && !IsPlayerAdmin(consoleplayer))
@@ -1965,15 +1984,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,7 +2083,7 @@ 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;
 
@@ -2076,7 +2115,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 		G_SetGametype(gametype);
 
 	if (gametype != lastgametype)
-		D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype
+		D_GameTypeChanged();
 
 	skipprecutscene = ((flags & (1<<2)) != 0);
 
@@ -3955,22 +3994,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 +4240,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 +4257,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)
+	if (netgame && lastgametype != -1)
 	{
-		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 +4323,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/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;
 }
 
 //