From 445dc5639cb97a00d4042233cbc97ec4e7b0f1f1 Mon Sep 17 00:00:00 2001
From: Lactozilla <jp6781615@gmail.com>
Date: Sat, 15 Feb 2025 21:38:41 -0300
Subject: [PATCH] Add HUBLEVEL for maps

---
 src/d_main.c           |  1 +
 src/deh_soc.c          |  8 ++++++++
 src/doomstat.h         |  1 +
 src/g_game.c           | 30 +++++++++++++++++++++++-------
 src/g_game.h           |  3 ++-
 src/hu_stuff.c         |  3 +--
 src/lua_maplib.c       |  5 +++++
 src/netcode/d_clisrv.c |  1 +
 src/netcode/d_netcmd.c | 14 +++++---------
 src/p_setup.c          |  5 +----
 10 files changed, 48 insertions(+), 23 deletions(-)

diff --git a/src/d_main.c b/src/d_main.c
index 1507b36680..4d740cca34 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 30b2b42b84..b6dfdb7ca6 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 4b1ab80d71..74e3e52b94 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 75f40c8ee2..a61202b4cd 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 890fcd71e1..3c0878858f 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 7f2560bcc4..c4f8697c47 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 c946b10ce2..3c4f899fac 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 6702e25912..5f7ad68140 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 4b44aaa9af..897e6dc27c 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 36d7d48d28..cac70b703e 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);
-- 
GitLab