From 746da46321bcda48ed8763d43159bea660abea5b Mon Sep 17 00:00:00 2001
From: Eidolon <furyhunter600@gmail.com>
Date: Tue, 2 Jan 2024 21:00:18 -0600
Subject: [PATCH] Read staff ghosts from pk3 directory

---
 assets/CMakeLists.txt                   |  10 ++-
 src/d_main.cpp                          |   9 +-
 src/d_netfil.c                          |   5 +-
 src/doomstat.h                          |   6 +-
 src/g_demo.c                            |   2 +-
 src/k_menufunc.c                        |   8 +-
 src/menus/play-local-race-time-attack.c |  10 ++-
 src/p_setup.cpp                         | 107 ++++++++++++++++++------
 8 files changed, 118 insertions(+), 39 deletions(-)

diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt
index 42243c9c10..1b397ba08c 100644
--- a/assets/CMakeLists.txt
+++ b/assets/CMakeLists.txt
@@ -27,13 +27,19 @@ list(TRANSFORM SRB2_ASSETS_DOCS PREPEND "${SRB2_ASSET_DIRECTORY_ABSOLUTE}")
 ####################
 
 set(SRB2_ASSETS_GAME
-	"main.kart"
+	"bios.pk3"
 	"gfx.pk3"
-	"textures.pk3"
+	"textures_General.pk3"
+	"textures_OriginalZones.pk3"
+	"textures_SEGAZones.pk3"
 	"chars.pk3"
 	"maps.pk3"
 	"followers.pk3"
 	"patch.pk3"
+	"scripts.pk3"
+	"staffghosts.pk3"
+	"unlocks.pk3"
+	"shaders.pk3"
 )
 list(TRANSFORM SRB2_ASSETS_GAME PREPEND "/")
 list(TRANSFORM SRB2_ASSETS_GAME PREPEND "${SRB2_ASSET_DIRECTORY_ABSOLUTE}")
diff --git a/src/d_main.cpp b/src/d_main.cpp
index 56fab25d36..c16fa88879 100644
--- a/src/d_main.cpp
+++ b/src/d_main.cpp
@@ -116,6 +116,8 @@ extern "C" consvar_t cv_lua_profile;
 #ifdef USE_PATCH_FILE
 #define ASSET_HASH_PATCH_PK3				"00000000000000000000000000000000"
 #endif
+#define ASSET_HASH_STAFFGHOSTS_PK3			"00000000000000000000000000000000"
+#define ASSET_HASH_SHADERS_PK3				"00000000000000000000000000000000"
 
 // Version numbers for netplay :upside_down_face:
 int    VERSION;
@@ -989,7 +991,7 @@ void D_SRB2Loop(void)
 		// Fully completed frame made.
 		finishprecise = I_GetPreciseTime();
 
-		// Use the time before sleep for frameskip calculations: 
+		// Use the time before sleep for frameskip calculations:
 		// post-sleep time is literally being intentionally wasted
 		deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
 		deltatics = deltasecs * NEWTICRATE;
@@ -1258,6 +1260,7 @@ static void IdentifyVersion(void)
 #if defined(DEVELOP) && defined(UNLOCKTESTING)
 	D_AddFile(startupiwads, va(pandf,srb2waddir,"unlocks.pk3"));
 #endif
+	D_AddFile(startupiwads, va(pandf,srb2waddir,"staffghosts.pk3"));
 	D_AddFile(startupiwads, va(pandf,srb2waddir,"shaders.pk3"));
 
 #if !defined (HAVE_SDL) || defined (HAVE_MIXER)
@@ -1585,6 +1588,9 @@ void D_SRB2Main(void)
 	mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_CHARS_PK3);				// chars.pk3
 	mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_FOLLOWERS_PK3);			// followers.pk3
 	mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_MAPS_PK3);					// maps.pk3
+	mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_UNLOCKS_PK3);				// unlocks.pk3
+	mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_STAFFGHOSTS_PK3);			// staffghosts.pk3
+	mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_SHADERS_PK3);				// shaders.pk3
 #else
 #ifdef USE_PATCH_FILE
 	mainwads++;	// scripts.pk3
@@ -1599,6 +1605,7 @@ void D_SRB2Main(void)
 #ifdef UNLOCKTESTING
 	mainwads++; // unlocks.pk3
 #endif
+	mainwads++; // staffghosts.pk3
 
 #endif //ifndef DEVELOP
 	mainwads++; // shaders.pk3
diff --git a/src/d_netfil.c b/src/d_netfil.c
index cb6897a8eb..7a01b43ef6 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -592,13 +592,13 @@ INT32 CL_CheckFiles(void)
 	{
 		if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK)
 			downloadrequired = true;
-		
+
 		if (fileneeded[i].status != FS_OPEN)
 			filestoload++;
 
 		if (fileneeded[i].status != FS_NOTCHECKED) //since we're running this over multiple tics now, its possible for us to come across files checked in previous tics
 			continue;
-		
+
 		CONS_Debug(DBG_NETPLAY, "searching for '%s' ", fileneeded[i].filename);
 
 		// Check in already loaded files
@@ -1438,6 +1438,7 @@ void PT_FileFragment(void)
 		|| !strcmp(filename, "scripts.pk3")
 		|| !strcmp(filename, "sounds.pk3")
 		|| !strcmp(filename, "music.pk3")
+		|| !strcmp(filename, "staffghosts.pk3")
 		)
 	{
 		I_Error("Tried to download \"%s\"", filename);
diff --git a/src/doomstat.h b/src/doomstat.h
index b0fdd3beea..738a3a22e5 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -443,10 +443,11 @@ struct unloaded_cupheader_t
 extern unloaded_cupheader_t *unloadedcupheaders;
 
 #define MAXMAPLUMPNAME 64 // includes \0, for cleaner savedata
-#define MAXSTAFF 3
 
 struct staffbrief_t
 {
+	UINT16 wad;
+	UINT16 lump;
 	char name[16];
 	tic_t time;
 	tic_t lap;
@@ -480,7 +481,8 @@ struct mapheader_t
 
 	// Staff Ghost information
 	UINT8 ghostCount;					///< Count of valid staff ghosts
-	staffbrief_t *ghostBrief[MAXSTAFF];	///< Mallocated array of names for each staff ghost
+	UINT32 ghostBriefSize;              ///< Size of ghostBrief vector allocation
+	staffbrief_t **ghostBrief;			///< Valid staff ghosts, pointers are owned
 
 	recorddata_t records;				///< Stores completion/record attack data
 
diff --git a/src/g_demo.c b/src/g_demo.c
index 5de64ce402..49503c5e0f 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -3908,7 +3908,7 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer)
 	UINT16 ghostversion;
 	UINT8 flags;
 	INT32 i;
-	staffbrief_t temp;
+	staffbrief_t temp = {0};
 	staffbrief_t *ret = NULL;
 
 	temp.name[0] = '\0';
diff --git a/src/k_menufunc.c b/src/k_menufunc.c
index 7b7c817a7c..1864740ed8 100644
--- a/src/k_menufunc.c
+++ b/src/k_menufunc.c
@@ -57,7 +57,7 @@ static boolean noFurtherInput = false;
 // CONSOLE VARIABLES AND THEIR POSSIBLE VALUES GO HERE.
 // ==========================================================================
 
-CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {MAXSTAFF-1, "MAX"}, {0, NULL}};
+CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {999, "MAX"}, {0, NULL}};
 
 // ==========================================================================
 // CVAR ONCHANGE EVENTS GO HERE
@@ -436,8 +436,8 @@ void M_PlayMenuJam(void)
 
 	const boolean trulystarted = M_GameTrulyStarted();
 	const boolean profilemode = (
-		trulystarted 
-		&& optionsmenu.profilemenu 
+		trulystarted
+		&& optionsmenu.profilemenu
 		&& !optionsmenu.resetprofilemenu
 	);
 
@@ -487,7 +487,7 @@ void M_PlayMenuJam(void)
 			"KEYGEN",
 			"LOSERC",
 		};
-		
+
 		if (refMenu != NULL && NotCurrentlyPlaying(overridetotrack[override - 1]))
 		{
 			Music_Remap("menu", overridetotrack[override - 1]);
diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c
index 5f1ed6954a..3d5225ea63 100644
--- a/src/menus/play-local-race-time-attack.c
+++ b/src/menus/play-local-race-time-attack.c
@@ -335,13 +335,21 @@ void M_HandleStaffReplay(INT32 choice)
 {
 	if (choice == 2)
 	{
+		mapheader_t *mapheader;
+		staffbrief_t *staffbrief;
+		const char* lumpname = NULL;
 		restoreMenu = &PLAY_TimeAttackDef;
 
 		M_ClearMenus(true);
 		demo.loadfiles = false;
 		demo.ignorefiles = true; // Just assume that record attack replays have the files needed
 
-		G_DoPlayDemo(va("%s/GHOST_%u", mapheaderinfo[levellist.choosemap]->lumpname, cv_dummystaff.value+1));
+		mapheader = mapheaderinfo[levellist.choosemap];
+		staffbrief = mapheader->ghostBrief[cv_dummystaff.value];
+
+		lumpname = W_CheckNameForNumPwad(staffbrief->wad, staffbrief->lump);
+
+		G_DoPlayDemo(lumpname);
 		return;
 	}
 
diff --git a/src/p_setup.cpp b/src/p_setup.cpp
index 6ebb828294..b85bde86e0 100644
--- a/src/p_setup.cpp
+++ b/src/p_setup.cpp
@@ -12,6 +12,11 @@
 /// \brief Do all the WAD I/O, get map description, set up initial state and misc. LUTs
 
 #include <algorithm>
+#include <string>
+
+#include <fmt/format.h>
+
+#include "cxxutil.hpp"
 
 #include "doomdef.h"
 #include "d_main.h"
@@ -475,6 +480,18 @@ static void P_ClearSingleMapHeaderInfo(INT16 num)
 
 	mapheaderinfo[num]->customopts = NULL;
 	mapheaderinfo[num]->numCustomOptions = 0;
+
+	if (mapheaderinfo[num]->ghostBrief != NULL)
+	{
+		for (int i = 0; i < mapheaderinfo[num]->ghostCount; i++)
+		{
+			Z_Free(mapheaderinfo[num]->ghostBrief[i]);
+		}
+		Z_Free(mapheaderinfo[num]->ghostBrief);
+	}
+	mapheaderinfo[num]->ghostBrief = NULL;
+	mapheaderinfo[num]->ghostCount = 0;
+	mapheaderinfo[num]->ghostBriefSize = 0;
 }
 
 /** Allocates a new map-header structure.
@@ -525,6 +542,8 @@ void P_AllocMapHeader(INT16 i)
 		mapheaderinfo[i]->thumbnailPic = NULL;
 		mapheaderinfo[i]->minimapPic = NULL;
 		mapheaderinfo[i]->ghostCount = 0;
+		mapheaderinfo[i]->ghostBriefSize = 0;
+		mapheaderinfo[i]->ghostBrief = NULL;
 		mapheaderinfo[i]->cup = NULL;
 		mapheaderinfo[i]->followers = NULL;
 		nummapheaders++;
@@ -7806,24 +7825,30 @@ static void P_LoadRecordGhosts(void)
 	// Staff Attack ghosts
 	if (cv_ghost_staff.value)
 	{
-		char *defdemoname;
-		virtlump_t *vLump;
-
 		for (i = mapheaderinfo[gamemap-1]->ghostCount; i > 0; i--)
 		{
 			savebuffer_t buf = {0};
 
-			defdemoname = va("GHOST_%u", i);
-			vLump = vres_Find(curmapvirt, defdemoname);
-			if (vLump == NULL)
+			staffbrief_t* ghostbrief = mapheaderinfo[gamemap-1]->ghostBrief[i - 1];
+			const char* lumpname = W_CheckNameForNumPwad(ghostbrief->wad, ghostbrief->lump);
+			size_t lumplength = W_LumpLengthPwad(ghostbrief->wad, ghostbrief->lump);
+			if (lumplength == 0)
 			{
-				CONS_Alert(CONS_ERROR, M_GetText("Failed to read virtlump '%s'.\n"), defdemoname);
+				if (lumpname)
+				{
+					CONS_Alert(CONS_ERROR, M_GetText("Failed to read staff ghost lump '%s'.\n"), lumpname);
+				}
+				else
+				{
+					CONS_Alert(CONS_ERROR, M_GetText("Failed to read staff ghost lump for map '%s'.\n"), mapheaderinfo[gamemap-1]->lumpname);
+				}
+
 				continue;
 			}
 
-			P_SaveBufferZAlloc(&buf, vLump->size, PU_LEVEL, NULL);
-			memcpy(buf.buffer, vLump->data, vLump->size);
-			G_AddGhost(&buf, defdemoname);
+			P_SaveBufferZAlloc(&buf, lumplength, PU_LEVEL, NULL);
+			W_ReadLumpPwad(ghostbrief->wad, ghostbrief->lump, buf.buffer);
+			G_AddGhost(&buf, (char*)lumpname);
 		}
 	}
 
@@ -8833,10 +8858,8 @@ UINT8 P_InitMapData(void)
 	INT32 i, j;
 	lumpnum_t maplump;
 	virtres_t *virtmap;
-	virtlump_t *minimap, *thumbnailPic, *ghost;
+	virtlump_t *minimap, *thumbnailPic;
 	char *name;
-	char buffer[9];
-	sprintf(buffer, "GHOST_x");
 
 	for (i = 0; i < nummapheaders; ++i)
 	{
@@ -8950,23 +8973,55 @@ UINT8 P_InitMapData(void)
 				mapheaderinfo[i]->ghostBrief[mapheaderinfo[i]->ghostCount] = NULL;
 			}
 
-			while (mapheaderinfo[i]->ghostCount < MAXSTAFF)
+			for (INT32 wadindex = 0; wadindex < numwadfiles; wadindex++)
 			{
-				buffer[6] = '1' + mapheaderinfo[i]->ghostCount;
+				if (wadfiles[wadindex]->type != RET_PK3)
+				{
+					continue;
+				}
+				std::string ghostdirname = fmt::format("staffghosts/{}/", mapheaderinfo[i]->lumpname);
 
-				ghost = vres_Find(virtmap, buffer);
-				if (ghost == NULL)
-					break;
+				UINT16 lumpstart = W_CheckNumForFolderStartPK3(ghostdirname.c_str(), wadindex, 0);
+				if (lumpstart == INT16_MAX)
+				{
+					continue;
+				}
+				UINT16 lumpend = W_CheckNumForFolderEndPK3(ghostdirname.c_str(), wadindex, lumpstart);
+				if (lumpend == INT16_MAX)
+				{
+					continue;
+				}
 
-				mapheaderinfo[i]->ghostBrief[mapheaderinfo[i]->ghostCount] = G_GetStaffGhostBrief(ghost->data);
-				if (mapheaderinfo[i]->ghostBrief[mapheaderinfo[i]->ghostCount] == NULL)
-					break;
-				/*CONS_Printf("name is %s, time is %d, lap is %d\n",
-					mapheaderinfo[i]->ghostBrief[mapheaderinfo[i]->ghostCount]->name,
-					mapheaderinfo[i]->ghostBrief[mapheaderinfo[i]->ghostCount]->time/TICRATE,
-					mapheaderinfo[i]->ghostBrief[mapheaderinfo[i]->ghostCount]->lap/TICRATE);*/
+				for (UINT16 lumpnum = lumpstart; lumpnum < lumpend; lumpnum++)
+				{
+					if (W_IsLumpFolder(wadindex, lumpnum))
+					{
+						continue;
+					}
+
+					size_t lumplength = W_LumpLengthPwad(wadindex, lumpnum);
+					UINT8* ghostdata = static_cast<UINT8*>(Z_Malloc(lumplength, PU_STATIC, nullptr));
+					auto ghostdata_finalizer = srb2::finally([=]() { Z_Free(ghostdata); });
 
-				mapheaderinfo[i]->ghostCount++;
+					W_ReadLumpPwad(wadindex, lumpnum, ghostdata);
+					staffbrief_t* briefghost = G_GetStaffGhostBrief(ghostdata);
+					if (briefghost == nullptr)
+					{
+						continue;
+					}
+					briefghost->wad = wadindex;
+					briefghost->lump = lumpnum;
+
+					// Resize ghostBrief if needed
+					if (mapheaderinfo[i]->ghostBriefSize < static_cast<UINT32>(mapheaderinfo[i]->ghostCount + 1))
+					{
+						UINT32 newsize = mapheaderinfo[i]->ghostBriefSize + 4;
+						mapheaderinfo[i]->ghostBrief = static_cast<staffbrief_t**>(Z_Realloc(mapheaderinfo[i]->ghostBrief, sizeof(staffbrief_t*) * newsize, PU_STATIC, NULL));
+						mapheaderinfo[i]->ghostBriefSize = newsize;
+					}
+					mapheaderinfo[i]->ghostBrief[mapheaderinfo[i]->ghostCount] = briefghost;
+					mapheaderinfo[i]->ghostCount++;
+				}
 			}
 
 			vres_Free(virtmap);
-- 
GitLab