From 11acf2a5a0a0583a58b93f6c03ec7726b5bd4938 Mon Sep 17 00:00:00 2001
From: havoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Date: Sat, 4 Apr 2009 14:53:35 +0000
Subject: [PATCH] it is now possible to have multiple map models loaded at once
 - removed the isworldmodel flag, and submodel searches now use the mapname as
 a second search key so submodels from multiple maps can coexist in memory
 this is a cleanup that I had wanted to do for a long time... added special
 modeldecompile support for submodels, they should now save to a name based on
 the current map as well as the submodel number

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@8854 d7cf8633-e32d-0410-b094-e92efae38249
---
 cl_main.c      |   2 +-
 cl_parse.c     |  40 +++++++++-------
 clvm_cmds.c    |   2 +-
 host_cmd.c     |   4 +-
 model_brush.c  | 113 ++++++++++++++++++-------------------------
 model_shared.c | 127 ++++++++++++++++++++++---------------------------
 model_shared.h |  14 +++---
 sv_main.c      |  16 +++++--
 8 files changed, 150 insertions(+), 168 deletions(-)

diff --git a/cl_main.c b/cl_main.c
index 675c3564..7b651685 100644
--- a/cl_main.c
+++ b/cl_main.c
@@ -461,7 +461,7 @@ static void CL_ModelIndexList_f(void)
 
 	while(cl.model_precache[i] && i != MAX_MODELS)
 	{ // Valid Model
-		if(cl.model_precache[i]->loaded || cl.model_precache[i]->isworldmodel)
+		if(cl.model_precache[i]->loaded || i == 1)
 			Con_Printf("%3i: %-30s %-8s %-10i\n", i, cl.model_precache[i]->name, cl.model_precache[i]->modeldatatypestring, cl.model_precache[i]->surfmesh.num_triangles);
 		else
 			Con_Printf("%3i: %-30s %-30s\n", i, cl.model_precache[i]->name, "--no local model found--");
diff --git a/cl_parse.c b/cl_parse.c
index a64e1ffd..28c4dda2 100644
--- a/cl_parse.c
+++ b/cl_parse.c
@@ -560,24 +560,24 @@ static void QW_CL_RequestNextDownload(void)
 		if (!sv.active)
 			Mod_ClearUsed();
 		for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++)
-			Mod_FindName(cl.model_name[i]);
+			Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL);
 		// precache any models used by the client (this also marks them used)
-		cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false);
-		cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false);
-		cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false);
-		cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false);
+		cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL);
+		cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL);
+		cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL);
+		cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL);
 		Mod_PurgeUnused();
 
 		// now we try to load everything that is new
 
 		// world model
-		cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, true);
+		cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, NULL);
 		if (cl.model_precache[1]->Draw == NULL)
 			Con_Printf("Map %s could not be found or downloaded\n", cl.model_name[1]);
 
 		// normal models
 		for (i = 2;i < MAX_MODELS && cl.model_name[i][0];i++)
-			if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, false))->Draw == NULL)
+			if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL))->Draw == NULL)
 				Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]);
 
 		// check memory integrity
@@ -1029,7 +1029,7 @@ void CL_BeginDownloads(qboolean aborteddownload)
 			if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw)
 				continue;
 			CL_KeepaliveMessage(true);
-			cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.loadmodel_current == 1);
+			cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.model_name[cl.loadmodel_current][0] == '*' ? cl.model_name[1] : NULL);
 			if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw && cl.loadmodel_current == 1)
 			{
 				// we now have the worldmodel so we can set up the game world
@@ -1102,7 +1102,7 @@ void CL_BeginDownloads(qboolean aborteddownload)
 			if (cl.model_precache[cl.downloadmodel_current] && cl.model_precache[cl.downloadmodel_current]->Draw)
 				continue;
 			CL_KeepaliveMessage(true);
-			if (strcmp(cl.model_name[cl.downloadmodel_current], "null") && !FS_FileExists(cl.model_name[cl.downloadmodel_current]))
+			if (cl.model_name[cl.downloadmodel_current][0] != '*' && strcmp(cl.model_name[cl.downloadmodel_current], "null") && !FS_FileExists(cl.model_name[cl.downloadmodel_current]))
 			{
 				if (cl.downloadmodel_current == 1)
 					Con_Printf("Map %s not found\n", cl.model_name[cl.downloadmodel_current]);
@@ -1116,7 +1116,7 @@ void CL_BeginDownloads(qboolean aborteddownload)
 					return;
 				}
 			}
-			cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, false, cl.downloadmodel_current == 1);
+			cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, false, cl.model_name[cl.downloadmodel_current][0] == '*' ? cl.model_name[1] : NULL);
 			if (cl.downloadmodel_current == 1)
 			{
 				// we now have the worldmodel so we can set up the game world
@@ -1445,6 +1445,10 @@ static void CL_SignonReply (void)
 		break;
 
 	case 4:
+		// after the level has been loaded, we shouldn't need the shaders, and
+		// if they are needed again they will be automatically loaded...
+		Mod_FreeQ3Shaders();
+
 		Con_ClearNotify();
 		if (COM_CheckParm("-profilegameonly"))
 			Sys_AllowProfiling(true);
@@ -1471,6 +1475,8 @@ void CL_ParseServerInfo (void)
 	{
 		SCR_BeginLoadingPlaque();
 		S_StopAllSounds();
+		// free q3 shaders so that any newly downloaded shaders will be active
+		Mod_FreeQ3Shaders();
 	}
 
 	// check memory integrity
@@ -1631,12 +1637,12 @@ void CL_ParseServerInfo (void)
 		if (!sv.active)
 			Mod_ClearUsed();
 		for (i = 1;i < nummodels;i++)
-			Mod_FindName(cl.model_name[i]);
+			Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL);
 		// precache any models used by the client (this also marks them used)
-		cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false);
-		cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false);
-		cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false);
-		cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false);
+		cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL);
+		cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL);
+		cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL);
+		cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL);
 		Mod_PurgeUnused();
 
 		// do the same for sounds
@@ -2593,7 +2599,7 @@ void CL_ParseTempEntity(void)
 
 	// LordHavoc: for compatibility with the Nehahra movie...
 		case TE_LIGHTNING4NEH:
-			CL_ParseBeam(Mod_ForName(MSG_ReadString(), true, false, false), false);
+			CL_ParseBeam(Mod_ForName(MSG_ReadString(), true, false, NULL), false);
 			break;
 
 		case TE_LAVASPLASH:
@@ -3659,7 +3665,7 @@ void CL_ParseServerMessage(void)
 					{
 						if (i >= 1 && i < MAX_MODELS)
 						{
-							dp_model_t *model = Mod_ForName(s, false, false, i == 1);
+							dp_model_t *model = Mod_ForName(s, false, false, s[0] == '*' ? cl.model_name[1] : NULL);
 							if (!model)
 								Con_DPrintf("svc_precache: Mod_ForName(\"%s\") failed\n", s);
 							cl.model_precache[i] = model;
diff --git a/clvm_cmds.c b/clvm_cmds.c
index 8b6e1480..da4832ed 100644
--- a/clvm_cmds.c
+++ b/clvm_cmds.c
@@ -393,7 +393,7 @@ void VM_CL_precache_model (void)
 		}
 	}
 	PRVM_G_FLOAT(OFS_RETURN) = 0;
-	m = Mod_ForName(name, false, false, false);
+	m = Mod_ForName(name, false, false, name[0] == '*' ? cl.model_name[1] : NULL);
 	if(m && m->loaded)
 	{
 		for (i = 0;i < MAX_MODELS;i++)
diff --git a/host_cmd.c b/host_cmd.c
index d77264f1..6e3a2abc 100644
--- a/host_cmd.c
+++ b/host_cmd.c
@@ -933,7 +933,7 @@ void Host_Loadgame_f (void)
 					if (i >= 0 && i < MAX_MODELS)
 					{
 						strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
-						sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, false);
+						sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.modelname : NULL);
 					}
 					else
 						Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
@@ -2084,7 +2084,7 @@ void Host_Viewmodel_f (void)
 	if (!e)
 		return;
 
-	m = Mod_ForName (Cmd_Argv(1), false, true, false);
+	m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
 	if (!m || !m->loaded || !m->Draw)
 	{
 		Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
diff --git a/model_brush.c b/model_brush.c
index 723f4dab..f652f221 100644
--- a/model_brush.c
+++ b/model_brush.c
@@ -27,7 +27,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 
 //cvar_t r_subdivide_size = {CVAR_SAVE, "r_subdivide_size", "128", "how large water polygons should be (smaller values produce more polygons which give better warping effects)"};
-cvar_t halflifebsp = {0, "halflifebsp", "0", "indicates the current map is hlbsp format (useful to know because of different bounding box sizes)"};
 cvar_t r_novis = {0, "r_novis", "0", "draws whole level, see also sv_cullentities_pvs 0"};
 cvar_t r_picmipworld = {CVAR_SAVE, "r_picmipworld", "1", "whether gl_picmip shall apply to world textures too"};
 cvar_t r_nosurftextures = {0, "r_nosurftextures", "0", "pretends there was no texture lump found in the q1bsp/hlbsp loading (useful for debugging this rare case)"};
@@ -54,7 +53,6 @@ static texture_t mod_q1bsp_texture_water;
 void Mod_BrushInit(void)
 {
 //	Cvar_RegisterVariable(&r_subdivide_size);
-	Cvar_RegisterVariable(&halflifebsp);
 	Cvar_RegisterVariable(&r_novis);
 	Cvar_RegisterVariable(&r_picmipworld);
 	Cvar_RegisterVariable(&r_nosurftextures);
@@ -1487,17 +1485,14 @@ static void Mod_Q1BSP_LoadTextures(lump_t *l)
 			// LordHavoc: HL sky textures are entirely different than quake
 			if (!loadmodel->brush.ishlbsp && !strncmp(tx->name, "sky", 3) && mtwidth == 256 && mtheight == 128)
 			{
-				if (loadmodel->isworldmodel)
+				data = loadimagepixelsbgra(tx->name, false, false);
+				if (data && image_width == 256 && image_height == 128)
 				{
-					data = loadimagepixelsbgra(tx->name, false, false);
-					if (data && image_width == 256 && image_height == 128)
-					{
-						R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4);
-						Mem_Free(data);
-					}
-					else if (mtdata != NULL)
-						R_Q1BSP_LoadSplitSky(mtdata, mtwidth, mtheight, 1);
+					R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4);
+					Mem_Free(data);
 				}
+				else if (mtdata != NULL)
+					R_Q1BSP_LoadSplitSky(mtdata, mtwidth, mtheight, 1);
 			}
 			else
 			{
@@ -3447,9 +3442,13 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 	mod->brush.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint;
 	mod->brush.RoundUpToHullSize = Mod_Q1BSP_RoundUpToHullSize;
 	mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf;
-
-	if (loadmodel->isworldmodel)
-		Cvar_SetValue("halflifebsp", mod->brush.ishlbsp);
+	mod->Draw = R_Q1BSP_Draw;
+	mod->DrawDepth = R_Q1BSP_DrawDepth;
+	mod->DrawDebug = R_Q1BSP_DrawDebug;
+	mod->GetLightInfo = R_Q1BSP_GetLightInfo;
+	mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
+	mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
+	mod->DrawLight = R_Q1BSP_DrawLight;
 
 // load into heap
 
@@ -3555,21 +3554,26 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 		if (i > 0)
 		{
 			char name[10];
-			// LordHavoc: only register submodels if it is the world
-			// (prevents external bsp models from replacing world submodels with
-			//  their own)
-			if (!loadmodel->isworldmodel)
-				continue;
 			// duplicate the basic information
 			dpsnprintf(name, sizeof(name), "*%i", i);
-			mod = Mod_FindName(name);
+			mod = Mod_FindName(name, loadmodel->name);
 			// copy the base model to this one
 			*mod = *loadmodel;
 			// rename the clone back to its proper name
 			strlcpy(mod->name, name, sizeof(mod->name));
+			mod->brush.parentmodel = loadmodel;
 			// textures and memory belong to the main model
 			mod->texturepool = NULL;
 			mod->mempool = NULL;
+			mod->brush.TraceLineOfSight = NULL;
+			mod->brush.GetPVS = NULL;
+			mod->brush.FatPVS = NULL;
+			mod->brush.BoxTouchingPVS = NULL;
+			mod->brush.BoxTouchingLeafPVS = NULL;
+			mod->brush.BoxTouchingVisibleLeafs = NULL;
+			mod->brush.FindBoxClusters = NULL;
+			mod->brush.LightPoint = NULL;
+			mod->brush.AmbientSoundLevelsForPoint = NULL;
 		}
 
 		mod->brush.submodel = i;
@@ -3593,29 +3597,6 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 		mod->sortedmodelsurfaces = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int);
 		Mod_MakeSortedSurfaces(mod);
 
-		// this gets altered below if sky or water is used
-		mod->DrawSky = NULL;
-		mod->DrawAddWaterPlanes = NULL;
-		mod->Draw = R_Q1BSP_Draw;
-		mod->DrawDepth = R_Q1BSP_DrawDepth;
-		mod->DrawDebug = R_Q1BSP_DrawDebug;
-		mod->GetLightInfo = R_Q1BSP_GetLightInfo;
-		mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
-		mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
-		mod->DrawLight = R_Q1BSP_DrawLight;
-		if (i != 0)
-		{
-			mod->brush.TraceLineOfSight = NULL;
-			mod->brush.GetPVS = NULL;
-			mod->brush.FatPVS = NULL;
-			mod->brush.BoxTouchingPVS = NULL;
-			mod->brush.BoxTouchingLeafPVS = NULL;
-			mod->brush.BoxTouchingVisibleLeafs = NULL;
-			mod->brush.FindBoxClusters = NULL;
-			mod->brush.LightPoint = NULL;
-			mod->brush.AmbientSoundLevelsForPoint = NULL;
-		}
-
 		// copy the submodel bounds, then enlarge the yaw and rotated bounds according to radius
 		// (previously this code measured the radius of the vertices of surfaces in the submodel, but that broke submodels that contain only CLIP brushes, which do not produce surfaces)
 		VectorCopy(bm->mins, mod->normalmins);
@@ -3636,18 +3617,25 @@ void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 		mod->radius = modelradius;
 		mod->radius2 = modelradius * modelradius;
 
+		// this gets altered below if sky or water is used
+		mod->DrawSky = NULL;
+		mod->DrawAddWaterPlanes = NULL;
+
 		// scan surfaces for sky and water and flag the submodel as possessing these features or not
 		// build lightstyle lists for quick marking of dirty lightmaps when lightstyles flicker
 		if (mod->nummodelsurfaces)
 		{
 			for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++)
-			{
-				// we only need to have a drawsky function if it is used(usually only on world model)
 				if (surface->texture->basematerialflags & MATERIALFLAG_SKY)
-					mod->DrawSky = R_Q1BSP_DrawSky;
+					break;
+			if (j < mod->nummodelsurfaces)
+				mod->DrawSky = R_Q1BSP_DrawSky;
+
+			for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++)
 				if (surface->texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION))
-					mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes;
-			}
+					break;
+			if (j < mod->nummodelsurfaces)
+				mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes;
 
 			// build lightstyle update chains
 			// (used to rapidly mark lightmapupdateflags on many surfaces
@@ -4097,9 +4085,6 @@ void static Mod_Q2BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 	i = LittleLong(header->version);
 	if (i != Q2BSPVERSION)
 		Host_Error("Mod_Q2BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q2BSPVERSION);
-	mod->brush.ishlbsp = false;
-	if (loadmodel->isworldmodel)
-		Cvar_SetValue("halflifebsp", mod->brush.ishlbsp);
 
 	mod_base = (unsigned char *)header;
 
@@ -5827,9 +5812,6 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 	i = LittleLong(header->version);
 	if (i != Q3BSPVERSION && i != Q3BSPVERSION_IG && i != Q3BSPVERSION_LIVE)
 		Host_Error("Mod_Q3BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q3BSPVERSION);
-	mod->brush.ishlbsp = false;
-	if (loadmodel->isworldmodel)
-		Cvar_SetValue("halflifebsp", mod->brush.ishlbsp);
 
 	mod->soundfromcenter = true;
 	mod->TraceBox = Mod_Q3BSP_TraceBox;
@@ -5845,6 +5827,8 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 	mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters;
 	mod->brush.LightPoint = Mod_Q3BSP_LightPoint;
 	mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation;
+	mod->brush.AmbientSoundLevelsForPoint = NULL;
+	mod->brush.RoundUpToHullSize = NULL;
 	mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf;
 	mod->Draw = R_Q1BSP_Draw;
 	mod->DrawDepth = R_Q1BSP_DrawDepth;
@@ -5853,7 +5837,6 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 	mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
 	mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
 	mod->DrawLight = R_Q1BSP_DrawLight;
-	mod->DrawAddWaterPlanes = NULL;
 
 	mod_base = (unsigned char *)header;
 
@@ -5952,16 +5935,14 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 		if (i > 0)
 		{
 			char name[10];
-			// LordHavoc: only register submodels if it is the world
-			// (prevents external bsp models from replacing world submodels with
-			//  their own)
-			if (!loadmodel->isworldmodel)
-				continue;
 			// duplicate the basic information
 			dpsnprintf(name, sizeof(name), "*%i", i);
-			mod = Mod_FindName(name);
+			mod = Mod_FindName(name, loadmodel->name);
+			// copy the base model to this one
 			*mod = *loadmodel;
+			// rename the clone back to its proper name
 			strlcpy(mod->name, name, sizeof(mod->name));
+			mod->brush.parentmodel = loadmodel;
 			// textures and memory belong to the main model
 			mod->texturepool = NULL;
 			mod->mempool = NULL;
@@ -5973,7 +5954,7 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 			mod->brush.BoxTouchingVisibleLeafs = NULL;
 			mod->brush.FindBoxClusters = NULL;
 			mod->brush.LightPoint = NULL;
-			mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation;
+			mod->brush.AmbientSoundLevelsForPoint = NULL;
 		}
 		mod->brush.submodel = i;
 
@@ -6025,21 +6006,21 @@ void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend)
 		mod->radius = modelradius;
 		mod->radius2 = modelradius * modelradius;
 
+		// this gets altered below if sky or water is used
+		mod->DrawSky = NULL;
+		mod->DrawAddWaterPlanes = NULL;
+
 		for (j = 0;j < mod->nummodelsurfaces;j++)
 			if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & MATERIALFLAG_SKY)
 				break;
 		if (j < mod->nummodelsurfaces)
 			mod->DrawSky = R_Q1BSP_DrawSky;
-		else
-			mod->DrawSky = NULL;
 
 		for (j = 0;j < mod->nummodelsurfaces;j++)
 			if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION))
 				break;
 		if (j < mod->nummodelsurfaces)
 			mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes;
-		else
-			mod->DrawAddWaterPlanes = NULL;
 	}
 }
 
diff --git a/model_shared.c b/model_shared.c
index 664f0dab..c8cbc433 100644
--- a/model_shared.c
+++ b/model_shared.c
@@ -54,13 +54,10 @@ static void mod_start(void)
 	int nummodels = Mem_ExpandableArray_IndexRange(&models);
 	dp_model_t *mod;
 
-	// parse the Q3 shader files
-	Mod_LoadQ3Shaders();
-
 	for (i = 0;i < nummodels;i++)
 		if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*')
 			if (mod->used)
-				Mod_LoadModel(mod, true, false, mod->isworldmodel);
+				Mod_LoadModel(mod, true, false);
 }
 
 static void mod_shutdown(void)
@@ -73,7 +70,7 @@ static void mod_shutdown(void)
 		if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && (mod->loaded || mod->mempool))
 			Mod_UnloadModel(mod);
 
-	Mem_FreePool (&q3shaders_mem);
+	Mod_FreeQ3Shaders();
 }
 
 static void mod_newmap(void)
@@ -152,14 +149,14 @@ void Mod_RenderInit(void)
 void Mod_UnloadModel (dp_model_t *mod)
 {
 	char name[MAX_QPATH];
-	qboolean isworldmodel;
 	qboolean used;
+	dp_model_t *parentmodel;
 
 	if (developer_loading.integer)
 		Con_Printf("unloading model %s\n", mod->name);
 
 	strlcpy(name, mod->name, sizeof(name));
-	isworldmodel = mod->isworldmodel;
+	parentmodel = mod->brush.parentmodel;
 	used = mod->used;
 	if (mod->surfmesh.ebo3i)
 		R_Mesh_DestroyBufferObject(mod->surfmesh.ebo3i);
@@ -174,7 +171,7 @@ void Mod_UnloadModel (dp_model_t *mod)
 	memset(mod, 0, sizeof(dp_model_t));
 	// restore the fields we want to preserve
 	strlcpy(mod->name, name, sizeof(mod->name));
-	mod->isworldmodel = isworldmodel;
+	mod->brush.parentmodel = parentmodel;
 	mod->used = used;
 	mod->loaded = false;
 }
@@ -191,7 +188,7 @@ Mod_LoadModel
 Loads a model
 ==================
 */
-dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk, qboolean isworldmodel)
+dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk)
 {
 	int num;
 	unsigned int crc;
@@ -205,9 +202,6 @@ dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk, q
 	
 	if (!strcmp(mod->name, "null"))
 	{
-		if (mod->isworldmodel != isworldmodel)
-			mod->loaded = false;
-
 		if(mod->loaded)
 			return mod;
 
@@ -217,7 +211,6 @@ dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk, q
 		if (developer_loading.integer)
 			Con_Printf("loading model %s\n", mod->name);
 
-		mod->isworldmodel = isworldmodel;
 		mod->used = true;
 		mod->crc = -1;
 		mod->loaded = false;
@@ -246,12 +239,6 @@ dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk, q
 
 	// even if the model is loaded it still may need reloading...
 
-	// if the model is a worldmodel and is being referred to as a
-	// non-worldmodel here, then it needs reloading to get rid of the
-	// submodels
-	if (mod->isworldmodel != isworldmodel)
-		mod->loaded = false;
-
 	// if it is not loaded or checkdisk is true we need to calculate the crc
 	if (!mod->loaded || checkdisk)
 	{
@@ -283,7 +270,6 @@ dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk, q
 		Mod_UnloadModel(mod);
 
 	// load the model
-	mod->isworldmodel = isworldmodel;
 	mod->used = true;
 	mod->crc = crc;
 	// errors can prevent the corresponding mod->loaded = true;
@@ -298,15 +284,9 @@ dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk, q
 	VectorSet(mod->rotatedmins, -mod->radius, -mod->radius, -mod->radius);
 	VectorSet(mod->rotatedmaxs, mod->radius, mod->radius, mod->radius);
 
-	// if we're loading a worldmodel, then this is a level change
-	if (mod->isworldmodel)
+	if (!q3shaders_mem)
 	{
-		// clear out any stale submodels or worldmodels lying around
-		// if we did this clear before now, an error might abort loading and
-		// leave things in a bad state
-		Mod_RemoveStaleWorldModels(mod);
-		// reload q3 shaders, to make sure they are ready to go for this level
-		// (including any models loaded for this level)
+		// load q3 shaders for the first time, or after a level change
 		Mod_LoadQ3Shaders();
 	}
 
@@ -373,35 +353,21 @@ void Mod_PurgeUnused(void)
 	}
 }
 
-// only used during loading!
-void Mod_RemoveStaleWorldModels(dp_model_t *skip)
-{
-	int i;
-	int nummodels = Mem_ExpandableArray_IndexRange(&models);
-	dp_model_t *mod;
-	for (i = 0;i < nummodels;i++)
-	{
-		if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->isworldmodel && mod->loaded && skip != mod)
-		{
-			Mod_UnloadModel(mod);
-			mod->isworldmodel = false;
-			mod->used = false;
-		}
-	}
-}
-
 /*
 ==================
 Mod_FindName
 
 ==================
 */
-dp_model_t *Mod_FindName(const char *name)
+dp_model_t *Mod_FindName(const char *name, const char *parentname)
 {
 	int i;
 	int nummodels;
 	dp_model_t *mod;
 
+	if (!parentname)
+		parentname = "";
+
 	// if we're not dedicatd, the renderer calls will crash without video
 	Host_StartVideo();
 
@@ -413,7 +379,7 @@ dp_model_t *Mod_FindName(const char *name)
 	// search the currently loaded models
 	for (i = 0;i < nummodels;i++)
 	{
-		if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && !strcmp(mod->name, name))
+		if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && !strcmp(mod->name, name) && ((!mod->brush.parentmodel && !parentname[0]) || (mod->brush.parentmodel && parentname[0] && !strcmp(mod->brush.parentmodel->name, parentname))))
 		{
 			mod->used = true;
 			return mod;
@@ -423,6 +389,10 @@ dp_model_t *Mod_FindName(const char *name)
 	// no match found, create a new one
 	mod = (dp_model_t *) Mem_ExpandableArray_AllocRecord(&models);
 	strlcpy(mod->name, name, sizeof(mod->name));
+	if (parentname[0])
+		mod->brush.parentmodel = Mod_FindName(parentname, NULL);
+	else
+		mod->brush.parentmodel = NULL;
 	mod->loaded = false;
 	mod->used = true;
 	return mod;
@@ -435,12 +405,12 @@ Mod_ForName
 Loads in a model for the given name
 ==================
 */
-dp_model_t *Mod_ForName(const char *name, qboolean crash, qboolean checkdisk, qboolean isworldmodel)
+dp_model_t *Mod_ForName(const char *name, qboolean crash, qboolean checkdisk, const char *parentname)
 {
 	dp_model_t *model;
-	model = Mod_FindName(name);
-	if (model->name[0] != '*' && (!model->loaded || checkdisk))
-		Mod_LoadModel(model, crash, checkdisk, isworldmodel);
+	model = Mod_FindName(name, parentname);
+	if (!model->loaded || checkdisk)
+		Mod_LoadModel(model, crash, checkdisk);
 	return model;
 }
 
@@ -458,7 +428,7 @@ void Mod_Reload(void)
 	dp_model_t *mod;
 	for (i = 0;i < nummodels;i++)
 		if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*' && mod->used)
-			Mod_LoadModel(mod, true, true, mod->isworldmodel);
+			Mod_LoadModel(mod, true, true);
 }
 
 unsigned char *mod_base;
@@ -479,8 +449,15 @@ static void Mod_Print(void)
 
 	Con_Print("Loaded models:\n");
 	for (i = 0;i < nummodels;i++)
-		if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0])
-			Con_Printf("%4iK %s\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name);
+	{
+		if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*')
+		{
+			if (mod->brush.numsubmodels)
+				Con_Printf("%4iK %s (%i submodels)\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name, mod->brush.numsubmodels);
+			else
+				Con_Printf("%4iK %s\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name);
+		}
+	}
 }
 
 /*
@@ -491,7 +468,7 @@ Mod_Precache
 static void Mod_Precache(void)
 {
 	if (Cmd_Argc() == 2)
-		Mod_ForName(Cmd_Argv(1), false, true, cl.worldmodel && !strcasecmp(Cmd_Argv(1), cl.worldmodel->name));
+		Mod_ForName(Cmd_Argv(1), false, true, Cmd_Argv(1)[0] == '*' ? cl.model_name[1] : NULL);
 	else
 		Con_Print("usage: modelprecache <filename>\n");
 }
@@ -1219,18 +1196,9 @@ q3wavefunc_t Mod_LoadQ3Shaders_EnumerateWaveFunc(const char *s)
 	return Q3WAVEFUNC_NONE;
 }
 
-static void Q3Shaders_Clear()
+void Mod_FreeQ3Shaders(void)
 {
-	/* Just clear out everything... */
-	Mem_FreePool (&q3shaders_mem);
-	/* ...and alloc the structs again. */
-	q3shaders_mem = Mem_AllocPool("q3shaders", 0, NULL);
-	q3shader_data = (q3shader_data_t*)Mem_Alloc (q3shaders_mem,
-		sizeof (q3shader_data_t));
-	Mem_ExpandableArray_NewArray (&q3shader_data->hash_entries,
-		q3shaders_mem, sizeof (q3shader_hash_entry_t), 256);
-	Mem_ExpandableArray_NewArray (&q3shader_data->char_ptrs,
-		q3shaders_mem, sizeof (char**), 256);
+	Mem_FreePool(&q3shaders_mem);
 }
 
 static void Q3Shader_AddToHash (q3shaderinfo_t* shader)
@@ -1280,7 +1248,15 @@ void Mod_LoadQ3Shaders(void)
 	int numparameters;
 	char parameter[TEXTURE_MAXFRAMES + 4][Q3PATHLENGTH];
 
-	Q3Shaders_Clear();
+	Mod_FreeQ3Shaders();
+
+	q3shaders_mem = Mem_AllocPool("q3shaders", 0, NULL);
+	q3shader_data = (q3shader_data_t*)Mem_Alloc (q3shaders_mem,
+		sizeof (q3shader_data_t));
+	Mem_ExpandableArray_NewArray (&q3shader_data->hash_entries,
+		q3shaders_mem, sizeof (q3shader_hash_entry_t), 256);
+	Mem_ExpandableArray_NewArray (&q3shader_data->char_ptrs,
+		q3shaders_mem, sizeof (char**), 256);
 
 	search = FS_Search("scripts/*.shader", true, false);
 	if (!search)
@@ -1774,8 +1750,12 @@ void Mod_LoadQ3Shaders(void)
 
 q3shaderinfo_t *Mod_LookupQ3Shader(const char *name)
 {
-	unsigned short hash = CRC_Block_CaseInsensitive ((const unsigned char *)name, strlen (name));
-	q3shader_hash_entry_t* entry = q3shader_data->hash + (hash % Q3SHADER_HASH_SIZE);
+	unsigned short hash;
+	q3shader_hash_entry_t* entry;
+	if (!q3shaders_mem)
+		Mod_LoadQ3Shaders();
+	hash = CRC_Block_CaseInsensitive ((const unsigned char *)name, strlen (name));
+	entry = q3shader_data->hash + (hash % Q3SHADER_HASH_SIZE);
 	while (entry != NULL)
 	{
 		if (strcasecmp (entry->shader.name, name) == 0)
@@ -2558,7 +2538,14 @@ static void Mod_Decompile_f(void)
 	strlcpy(inname, Cmd_Argv(1), sizeof(inname));
 	FS_StripExtension(inname, basename, sizeof(basename));
 
-	mod = Mod_ForName(inname, false, true, cl.worldmodel && !strcasecmp(inname, cl.worldmodel->name));
+	mod = Mod_ForName(inname, false, true, inname[0] == '*' ? cl.model_name[1] : NULL);
+	if (mod->brush.submodel)
+	{
+		// if we're decompiling a submodel, be sure to give it a proper name based on its parent
+		FS_StripExtension(cl.model_name[1], outname, sizeof(outname));
+		dpsnprintf(basename, sizeof(basename), "%s/%s", outname, mod->name);
+		outname[0] = 0;
+	}
 	if (!mod)
 	{
 		Con_Print("No such model\n");
diff --git a/model_shared.h b/model_shared.h
index 3476a4a8..6935b862 100644
--- a/model_shared.h
+++ b/model_shared.h
@@ -604,14 +604,15 @@ typedef struct model_brush_s
 	// string of entity definitions (.map format)
 	char *entities;
 
-	// if non-zero this is a submodel
+	// if not NULL this is a submodel
+	struct model_s *parentmodel;
 	// (this is the number of the submodel, an index into submodels)
 	int submodel;
 
 	// number of submodels in this map (just used by server to know how many
 	// submodels to load)
 	int numsubmodels;
-	// pointers to each of the submodels if .isworldmodel is true
+	// pointers to each of the submodels
 	struct model_s **submodels;
 
 	int num_planes;
@@ -797,8 +798,6 @@ typedef struct model_s
 	qboolean		loaded;
 	// set if the model is used in current map, models which are not, are purged
 	qboolean		used;
-	// true if this is the world model (I.E. defines what sky to use, and may contain submodels)
-	qboolean		isworldmodel;
 	// CRC of the file this model was loaded from, to reload if changed
 	unsigned int	crc;
 	// mod_brush, mod_alias, mod_sprite
@@ -910,9 +909,9 @@ extern cvar_t r_fullbrights;
 
 void Mod_Init (void);
 void Mod_Reload (void);
-dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk, qboolean isworldmodel);
-dp_model_t *Mod_FindName (const char *name);
-dp_model_t *Mod_ForName (const char *name, qboolean crash, qboolean checkdisk, qboolean isworldmodel);
+dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk);
+dp_model_t *Mod_FindName (const char *name, const char *parentname);
+dp_model_t *Mod_ForName (const char *name, qboolean crash, qboolean checkdisk, const char *parentname);
 void Mod_UnloadModel (dp_model_t *mod);
 
 void Mod_ClearUsed(void);
@@ -941,6 +940,7 @@ shadowmesh_t *Mod_ShadowMesh_Finish(mempool_t *mempool, shadowmesh_t *firstmesh,
 void Mod_ShadowMesh_CalcBBox(shadowmesh_t *firstmesh, vec3_t mins, vec3_t maxs, vec3_t center, float *radius);
 void Mod_ShadowMesh_Free(shadowmesh_t *mesh);
 
+void Mod_FreeQ3Shaders(void);
 void Mod_LoadQ3Shaders(void);
 q3shaderinfo_t *Mod_LookupQ3Shader(const char *name);
 qboolean Mod_LoadTextureFromQ3Shader(texture_t *texture, const char *name, qboolean warnmissing, qboolean fallback, int defaulttexflags);
diff --git a/sv_main.c b/sv_main.c
index db271517..b7fbd4df 100644
--- a/sv_main.c
+++ b/sv_main.c
@@ -157,6 +157,7 @@ cvar_t cutscene = {0, "cutscene", "1", "enables cutscenes in nehahra, can be use
 cvar_t sv_autodemo_perclient = {CVAR_SAVE, "sv_autodemo_perclient", "0", "set to 1 to enable autorecorded per-client demos (they'll start to record at the beginning of a match); set it to 2 to also record client->server packets (for debugging)"};
 cvar_t sv_autodemo_perclient_nameformat = {CVAR_SAVE, "sv_autodemo_perclient_nameformat", "sv_autodemos/%Y-%m-%d_%H-%M", "The format of the sv_autodemo_perclient filename, followed by the map name, the client number and the IP address + port number, separated by underscores (the date is encoded using strftime escapes)" };
 
+cvar_t halflifebsp = {0, "halflifebsp", "0", "indicates the current map is hlbsp format (useful to know because of different bounding box sizes)"};
 
 server_t sv;
 server_static_t svs;
@@ -438,6 +439,8 @@ void SV_Init (void)
 	Cvar_RegisterVariable (&sv_autodemo_perclient);
 	Cvar_RegisterVariable (&sv_autodemo_perclient_nameformat);
 
+	Cvar_RegisterVariable (&halflifebsp);
+
 	// any special defaults for gamemodes go here
 	if (gamemode == GAME_HIPNOTIC)
 	{
@@ -2399,7 +2402,7 @@ int SV_ModelIndex(const char *s, int precachemode)
 				if (precachemode == 1)
 					Con_Printf("SV_ModelIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename);
 				strlcpy(sv.model_precache[i], filename, sizeof(sv.model_precache[i]));
-				sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, false);
+				sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.modelname : NULL);
 				if (sv.state != ss_loading)
 				{
 					MSG_WriteByte(&sv.reliable_datagram, svc_precache);
@@ -2701,7 +2704,7 @@ void SV_SaveSpawnparms (void)
 
 /*
 ================
-SV_/pawnServer
+SV_SpawnServer
 
 This is called at the start of each level
 ================
@@ -2743,7 +2746,10 @@ void SV_SpawnServer (const char *server)
 		SV_VM_End();
 	}
 
-	worldmodel = Mod_ForName(modelname, false, true, true);
+	// free q3 shaders so that any newly downloaded shaders will be active
+	Mod_FreeQ3Shaders();
+
+	worldmodel = Mod_ForName(modelname, false, true, NULL);
 	if (!worldmodel || !worldmodel->TraceBox)
 	{
 		Con_Printf("Couldn't load map %s\n", modelname);
@@ -2800,6 +2806,8 @@ void SV_SpawnServer (const char *server)
 	// level's data which is no longer valiud
 	cls.signon = 0;
 
+	Cvar_SetValue("halflifebsp", worldmodel->brush.ishlbsp);
+
 	if(*sv_random_seed.string)
 	{
 		srand(sv_random_seed.integer);
@@ -2867,7 +2875,7 @@ void SV_SpawnServer (const char *server)
 	for (i = 1;i < sv.worldmodel->brush.numsubmodels;i++)
 	{
 		dpsnprintf(sv.model_precache[i+1], sizeof(sv.model_precache[i+1]), "*%i", i);
-		sv.models[i+1] = Mod_ForName (sv.model_precache[i+1], false, false, false);
+		sv.models[i+1] = Mod_ForName (sv.model_precache[i+1], false, false, sv.modelname);
 	}
 
 //
-- 
2.39.5