diff --git a/src/d_main.c b/src/d_main.c
index 1507b3668021c3c61bddaa945b0778731d3ee9ab..4d740cca340b60cc7074a345f9500d31c3d09ea3 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -997,6 +997,7 @@ void D_StartTitle(void)
 
 	gameaction = ga_nothing;
 	displayplayer = consoleplayer = 0;
+	gametype = -1;
 	G_SetGametype(GT_COOP);
 	paused = false;
 	advancedemo = false;
diff --git a/src/deh_soc.c b/src/deh_soc.c
index 30b2b42b84ad90306b4d71aa0f2d100b07328419..b6dfdb7ca6515388fbe4f422ad3f5409002231cf 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -1611,6 +1611,14 @@ void readlevelheader(MYFILE *f, INT32 num)
 
 				mapheaderinfo[num-1]->marathonnext = (INT16)i;
 			}
+			else if (fastcmp(word, "HUBLEVEL"))
+			{
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0')
+					i = M_MapNumber(word2[0], word2[1]);
+
+				mapheaderinfo[num-1]->hublevel = (INT16)i;
+			}
 			else if (fastcmp(word, "TYPEOFLEVEL"))
 			{
 				if (i) // it's just a number
diff --git a/src/doomstat.h b/src/doomstat.h
index 4b1ab80d7191a4ffd501c25ca53f1ee5bde1f4f5..74e3e52b9472fcc7dbfe3e686812d0689bfd086d 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -306,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 hublevel;             ///< 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.
diff --git a/src/g_game.c b/src/g_game.c
index 75f40c8ee238fd98d1af7078d630ff096cc5d0cb..a61202b4cdbf785767645e1ec35b4469b4b04073 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3477,16 +3477,16 @@ UINT32 gametypedefaultrules[NUMGAMETYPES] =
 //
 // Sets a new gametype, also setting gametype rules accordingly.
 //
-void G_SetGametype(INT16 gtype)
+void G_SetGametype(INT16 newgametype)
 {
-	if (gametype != gtype)
+	if (newgametype >= 0 && newgametype < gametypecount && gametype != newgametype)
 	{
 		if (gametype == -1)
-			lastgametype = gtype;
+			lastgametype = newgametype;
 		else
 			lastgametype = gametype;
 
-		gametype = gtype;
+		gametype = newgametype;
 		gametyperules = gametypedefaultrules[gametype];
 	}
 }
@@ -3888,6 +3888,21 @@ boolean G_IsHubAvailable(void)
 	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] && mapheaderinfo[gamemap-1]->hublevel)
+	{
+		if (!M_MapLocked(mapheaderinfo[gamemap-1]->hublevel, serverGamedata))
+			currenthubmap = mapheaderinfo[gamemap-1]->hublevel;
+	}
+}
+
 /** 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.
@@ -4086,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
@@ -4117,8 +4133,6 @@ INT16 G_GetNextMap(boolean ignoretokens, boolean silent)
 			newmapnum = 1100-1; // No infinite loop for you
 	}
 
-	INT16 gametype_to_use;
-
 	if (nextgametype >= 0 && nextgametype < gametypecount)
 		gametype_to_use = nextgametype;
 	else
@@ -4139,7 +4153,7 @@ 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)
+			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)
@@ -5219,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 890fcd71e1b475e8059eda6ba76413ad1739347f..3c0878858f5b1298045c20ad7582076a99f16e45 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -184,6 +184,7 @@ 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);
@@ -198,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);
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_maplib.c b/src/lua_maplib.c
index c946b10ce220fd28e2415d46f68233fad4bc7f0b..3c4f899faccbbe1651f472198e633523ae6a821b 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_hublevel,
 	mapheaderinfo_keywords,
 	mapheaderinfo_musname,
 	mapheaderinfo_mustrack,
@@ -2849,6 +2850,7 @@ static const char *const mapheaderinfo_opt[] = {
 	"typeoflevel",
 	"nextlevel",
 	"marathonnext",
+	"hublevel",
 	"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_hublevel:
+		lua_pushinteger(L, header->hublevel);
+		break;
 	case mapheaderinfo_keywords:
 		lua_pushstring(L, header->keywords);
 		break;
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 4b44aaa9af4a3530b83086a403c13deed66e7ca9..897e6dc27ce04cf8fef86b8eec99f8fa1a671c5c 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -1741,9 +1741,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 				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)
+			else if ((maptol & TOL_HUB) && (mapheaderinfo[mapnum-1]->typeoflevel & TOL_HUB) == 0)
 			{
 				newgametype = lastgametype;
 			}
@@ -2086,6 +2084,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	INT32 resetplayer = 1;
 	UINT8 skipprecutscene, FLS;
 	INT16 mapnumber;
+	UINT8 newgametype;
 
 	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
@@ -2105,12 +2104,9 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 		ultimatemode = false;
 
 	resetplayer = ((flags & (1<<1)) == 0);
-	gametype = READUINT8(*cp);
+	newgametype = READUINT8(*cp);
 
-	if (gametype < 0 || gametype >= gametypecount)
-		gametype = lastgametype;
-	else
-		G_SetGametype(gametype);
+	G_SetGametype(newgametype);
 
 	if (gametype != lastgametype)
 		D_GameTypeChanged();
@@ -4257,7 +4253,7 @@ static void TimeLimit_OnChange(void)
   */
 void D_GameTypeChanged(void)
 {
-	if (netgame && lastgametype != -1)
+	if (netgame)
 	{
 		CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), Gametype_Names[lastgametype], Gametype_Names[gametype]);
 	}
diff --git a/src/p_setup.c b/src/p_setup.c
index 36d7d48d28d21b012d57b2477f4ff954ddb35101..cac70b703e8503577c3a6eda5e2455d47a88cee0 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]->hublevel = 0;
 	mapheaderinfo[num]->marathonnext = 0;
 	mapheaderinfo[num]->startrings = 0;
 	mapheaderinfo[num]->sstimer = 90;
@@ -7853,10 +7854,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	maptol = mapheaderinfo[gamemap-1]->typeoflevel;
 	gametyperules = gametypedefaultrules[gametype];
 
-	// Set the current hub map if it was unlocked
-	if ((maptol & TOL_HUB) && !M_MapLocked(gamemap, serverGamedata))
-		currenthubmap = gamemap;
-
 	// clear the target on map change, since the object will be invalidated
 	P_SetTarget(&ticcmd_ztargetfocus[0], NULL);
 	P_SetTarget(&ticcmd_ztargetfocus[1], NULL);