From 13eeec99a290e0fd6c33cbf4d3eabd73d9cb4f01 Mon Sep 17 00:00:00 2001
From: TimeServ <timeserv@users.sourceforge.net>
Date: Thu, 13 Sep 2007 22:27:56 +0000
Subject: [PATCH] removed gl_loadmd2/gl_loadmd3, added r_replacemodels,
 r_particlesdesc is now separated by spaces and ;

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@2658 fc73d0e0-1445-4013-8a0c-d673dee63da5
---
 engine/client/m_options.c |  46 +++---
 engine/client/r_part.c    |  35 ++---
 engine/client/renderer.c  |  21 +--
 engine/common/common.c    |  36 +++++
 engine/common/common.h    |   1 +
 engine/gl/gl_model.c      | 303 ++++++++++++++++++++------------------
 engine/sw/sw_model.c      | 210 +++++++++++++-------------
 7 files changed, 338 insertions(+), 314 deletions(-)

diff --git a/engine/client/m_options.c b/engine/client/m_options.c
index 72d7643a2..41837c941 100644
--- a/engine/client/m_options.c
+++ b/engine/client/m_options.c
@@ -218,7 +218,6 @@ void M_Menu_Audio_Speakers_f (void)
 
 	info->card = sndcardinfo;
 
-
 	menu->selecteditem = NULL;
 }
 
@@ -362,29 +361,28 @@ typedef struct {
 } presetinfo_t;
 presetinfo_t preset[] =
 {
-	{"r_presetname",		{"286",		"fast",		"default",	"nice",		"realtime"}},
-	{"gl_texturemode",		{"nn",		"ln",		"ln",		"ll",		"ll"}},
-	{"r_particlesdesc",		{"none",	"highfps",	"spikeset",	"spikeset",	"spikeset"}},
-	{"r_stains",			{"0",		"0",		"0.75",		"0.75",		"0.75"}},
-	{"r_drawflat",			{"1",		"0",		"0",		"0",		"0"}},
-	{"r_nolerp",			{"1",		"1",		"0",		"0",		"0"}},
-	{"r_nolightdir",		{"1",		"0",		"0",		"0",		"0"}},
-	{"r_dynamic",			{"0",		"0",		"1",		"1",		"1"}},
-	{"r_bloom",				{"0",		"0",		"0",		"0",		"1"}},
-	{"gl_flashblend",		{"0",		"1",		"0",		"1",		"2"}},
-	{"gl_bump",				{"0",		"0",		"0",		"1",		"1"}},
-	{"gl_specular",			{"0",		"0",		"0",		"1",		"1"}},
-	{"r_loadlit",			{"0",		"1",		"1",		"2",		"2"}},
-	{"r_fastsky",			{"1",		"1",		"0",		"0",		"0"}},
-	{"r_waterlayers",		{"0",		"2",		"3",		"4",		"4"}},
-	{"r_shadows",			{"0",		"0",		"0",		"1",		"1"}},
-	{"r_shadow_realtime_world",{"0",	"0",		"0",		"0",		"1"}},
-	{"gl_detail",			{"0",		"0",		"0",		"1",		"1"}},
-	{"gl_load24bit",		{"0",		"0",		"1",		"1",		"1"}},
-	{"gl_loadmd2",			{"0",		"0",		"1",		"1",		"1"}},
-	{"gl_loadmd3",			{"0",		"0",		"0",		"1",		"1"}},
-	{"r_waterwarp",			{"0",		"-1",		"1",		"1",		"1"}},
-	{"r_lightstylesmooth",	{"0",		"0",		"0",		"1",		"1"}},
+	{"r_presetname",		{"286",		"fast",		"default",			"nice",				"realtime"}},
+	{"gl_texturemode",		{"nn",		"ln",		"ln",				"ll",				"ll"}},
+	{"r_particlesdesc",		{"none",	"highfps",	"spikeset tsshaft",	"spikeset tsshaft",	"spikeset tsshaft"}},
+	{"r_stains",			{"0",		"0",		"0.75",				"0.75",				"0.75"}},
+	{"r_drawflat",			{"1",		"0",		"0",				"0",				"0"}},
+	{"r_nolerp",			{"1",		"1",		"0",				"0",				"0"}},
+	{"r_nolightdir",		{"1",		"0",		"0",				"0",				"0"}},
+	{"r_dynamic",			{"0",		"0",		"1",				"1",				"1"}},
+	{"r_bloom",				{"0",		"0",		"0",				"0",				"1"}},
+	{"gl_flashblend",		{"0",		"1",		"0",				"1",				"2"}},
+	{"gl_bump",				{"0",		"0",		"0",				"1",				"1"}},
+	{"gl_specular",			{"0",		"0",		"0",				"1",				"1"}},
+	{"r_loadlit",			{"0",		"1",		"1",				"2",				"2"}},
+	{"r_fastsky",			{"1",		"1",		"0",				"0",				"0"}},
+	{"r_waterlayers",		{"0",		"2",		"3",				"4",				"4"}},
+	{"r_shadows",			{"0",		"0",		"0",				"1",				"1"}},
+	{"r_shadow_realtime_world",{"0",	"0",		"0",				"0",				"1"}},
+	{"gl_detail",			{"0",		"0",		"0",				"1",				"1"}},
+	{"gl_load24bit",		{"0",		"0",		"1",				"1",				"1"}},
+	{"r_replacemodels",		{"",		"",			"md3 md2",			"md3 md2",			"md3 md2"}},
+	{"r_waterwarp",			{"0",		"-1",		"1",				"1",				"1"}},
+	{"r_lightstylesmooth",	{"0",		"0",		"0",				"1",				"1"}},
 	{NULL}
 };
 static void ApplyPreset (int presetnum)
diff --git a/engine/client/r_part.c b/engine/client/r_part.c
index 23f4935a3..73e1af5f7 100644
--- a/engine/client/r_part.c
+++ b/engine/client/r_part.c
@@ -129,7 +129,7 @@ void R_ParticlesDesc_Callback(struct cvar_s *var, char *oldvalue);
 void R_Rockettrail_Callback(struct cvar_s *var, char *oldvalue);
 void R_Grenadetrail_Callback(struct cvar_s *var, char *oldvalue);
 
-cvar_t r_particlesdesc = SCVARFC("r_particlesdesc", "spikeset;tsshaft", CVAR_SEMICHEAT, R_ParticlesDesc_Callback);
+cvar_t r_particlesdesc = SCVARFC("r_particlesdesc", "spikeset tsshaft", CVAR_SEMICHEAT, R_ParticlesDesc_Callback);
 
 cvar_t r_part_rain_quantity = SCVAR("r_part_rain_quantity", "1");
 
@@ -728,6 +728,12 @@ void P_ParticleEffect_f(void)
 			else
 				ptype->spawnmode = SM_BOX;
 
+			if (Cmd_Argc()>2)
+			{
+				if (Cmd_Argc()>3)
+					ptype->spawnparam2 = atof(Cmd_Argv(3));
+				ptype->spawnparam1 = atof(Cmd_Argv(2));
+			}
 		}
 		else if (!strcmp(var, "type"))
 		{
@@ -1538,9 +1544,11 @@ void R_ParticlesDesc_Callback(struct cvar_s *var, char *oldvalue)
 {
 	extern model_t	mod_known[];
 	extern int		mod_numknown;
+	qboolean		first;
 
 	model_t *mod;
 	int i;
+	char *c;
 
 	if (cls.state == ca_disconnected)
 		return; // don't bother parsing while disconnected
@@ -1566,28 +1574,11 @@ void R_ParticlesDesc_Callback(struct cvar_s *var, char *oldvalue)
 
 	f_modified_particles = false;
 
+	first = true;
+	for (c = COM_ParseStringSet(var->string); com_token[0]; c = COM_ParseStringSet(c))
 	{
-		char name[32];
-		int len;
-		qboolean first = true;
-
-		char *oldsemi;		
-		char *semi;
-		oldsemi = r_particlesdesc.string;
-		semi = strchr(oldsemi, ';');
-		while (semi)
-		{
-			len = (int)(semi - oldsemi) + 1;
-			if (len > sizeof(name))
-				len = sizeof(name);
-			Q_strncpyz(name, oldsemi, len);
-			P_LoadParticleSet(name, first);
-			first = false;
-			oldsemi = semi + 1;
-			semi = strchr(oldsemi, ';');
-		}
-		Q_strncpyz(name, oldsemi, sizeof(name));
-		P_LoadParticleSet(name, first);
+		P_LoadParticleSet(com_token, first);
+		first = false;
 	}
 }
 
diff --git a/engine/client/renderer.c b/engine/client/renderer.c
index 8b8cc54d1..ec52e9493 100644
--- a/engine/client/renderer.c
+++ b/engine/client/renderer.c
@@ -133,6 +133,9 @@ cvar_t r_wateralpha							= SCVAR  ("r_wateralpha", "1");
 cvar_t r_waterwarp							= SCVARF ("r_waterwarp", "1",
 												CVAR_ARCHIVE);
 
+cvar_t r_replacemodels						= SCVARF ("r_replacemodels", "md3 md2", 
+												CVAR_ARCHIVE);
+
 //otherwise it would defeat the point.
 cvar_t scr_allowsnap						= SCVARF ("scr_allowsnap", "1",
 												CVAR_NOTFROMSERVER);
@@ -276,16 +279,6 @@ cvar_t gl_lightmap_shift					= SCVARF ("gl_lightmap_shift", "0",
 cvar_t gl_load24bit							= SCVARF ("gl_load24bit", "1",
 												CVAR_ARCHIVE);
 
-#ifdef MD2MODELS
-cvar_t gl_loadmd2							= SCVARF ("gl_loadmd2","1",
-												CVAR_ARCHIVE | CVAR_RENDERERLATCH);
-#endif
-
-#ifdef MD3MODELS
-cvar_t gl_loadmd3							= SCVARF ("gl_loadmd3","1",
-												CVAR_ARCHIVE | CVAR_RENDERERLATCH);
-#endif
-
 cvar_t gl_max_size							= SCVAR  ("gl_max_size", "1024");
 cvar_t gl_maxshadowlights					= SCVARF ("gl_maxshadowlights", "2",
 												CVAR_ARCHIVE);
@@ -457,12 +450,6 @@ void GLRenderer_Init(void)
 	Cvar_Register (&gl_overbright_all, GRAPHICALNICETIES);
 	Cvar_Register (&gl_dither, GRAPHICALNICETIES);
 	Cvar_Register (&r_fb_bmodels, GRAPHICALNICETIES);
-#ifdef MD2MODELS
-	Cvar_Register (&gl_loadmd2, GRAPHICALNICETIES);
-#endif
-#ifdef MD3MODELS
-	Cvar_Register (&gl_loadmd3, GRAPHICALNICETIES);
-#endif
 
 	Cvar_Register (&gl_ati_truform, GRAPHICALNICETIES);
 	Cvar_Register (&gl_ati_truform_type, GRAPHICALNICETIES);
@@ -684,6 +671,8 @@ void Renderer_Init(void)
 
 	Cvar_Register (&r_fb_models, GRAPHICALNICETIES);
 
+	Cvar_Register (&r_replacemodels, GRAPHICALNICETIES);
+
 //bulletens
 	Cvar_Register(&bul_nowater, BULLETENVARS);
 	Cvar_Register(&bul_rippleamount, BULLETENVARS);
diff --git a/engine/common/common.c b/engine/common/common.c
index 471892008..46c3a0566 100644
--- a/engine/common/common.c
+++ b/engine/common/common.c
@@ -1928,6 +1928,42 @@ skipwhite:
 	return data;
 }
 
+char *COM_ParseStringSet (char *data)
+{
+	int	c;
+	int	len;
+
+	len = 0;
+	com_token[0] = 0;
+
+	if (!data)
+		return NULL;
+
+// skip whitespace and semicolons
+	while ( (c = *data) <= ' ' || c == ';' )
+	{
+		if (c == 0)
+			return NULL;			// end of file;
+		data++;
+	}
+
+// parse a regular word
+	do
+	{
+		if (len >= TOKENSIZE-1)
+			return data;
+
+		com_token[len] = c;
+		data++;
+		len++;
+		c = *data;
+	} while (c>32 && c != ';');
+
+	com_token[len] = 0;
+	return data;
+}
+
+
 char *COM_ParseOut (char *data, char *out, int outlen)
 {
 	int		c;
diff --git a/engine/common/common.h b/engine/common/common.h
index 09cdad07c..0f6c1c1db 100644
--- a/engine/common/common.h
+++ b/engine/common/common.h
@@ -235,6 +235,7 @@ extern com_tokentype_t com_tokentype;
 extern	qboolean	com_eof;
 
 char *COM_Parse (char *data);
+char *COM_ParseStringSet (char *data);
 char *COM_ParseCString (char *data);
 char *COM_StringParse (char *data, qboolean expandmacros, qboolean qctokenize);
 char *COM_ParseToken (const char *data, const char *punctuation);
diff --git a/engine/gl/gl_model.c b/engine/gl/gl_model.c
index dc49a7fc4..bb2e1563f 100644
--- a/engine/gl/gl_model.c
+++ b/engine/gl/gl_model.c
@@ -40,18 +40,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #endif
 
 extern cvar_t r_shadow_bumpscale_basetexture;
+extern cvar_t r_replacemodels;
+
 extern int gl_bumpmappingpossible;
 qboolean isnotmap = true;	//used to not warp ammo models.
 
-#if defined(RGLQUAKE) || defined(D3DQUAKE)
-#ifdef MD2MODELS
-extern cvar_t gl_loadmd2;
-#endif
-#ifdef MD3MODELS
-extern cvar_t gl_loadmd3;
-#endif
-#endif
-
 #ifndef SWQUAKE
 model_t	*loadmodel;
 char	loadname[32];	// for hunk tags
@@ -435,6 +428,10 @@ model_t *GLMod_LoadModel (model_t *mod, qboolean crash)
 	void	*d;
 	unsigned *buf = NULL;
 	qbyte	stackbuf[1024];		// avoid dirtying the cache heap
+	char mdlbase[MAX_QPATH];
+	qboolean lastload = false;
+	char *replstr;
+	qboolean doomsprite = false;
 
 	char *ext;
 
@@ -468,66 +465,6 @@ model_t *GLMod_LoadModel (model_t *mod, qboolean crash)
 //
 // load the file
 //
-	//look for a replacement, but not for q1 sprites
-	ext = COM_FileExtension(mod->name);
-	if (gl_load24bit.value && Q_strcasecmp(ext, "spr") && Q_strcasecmp(ext, "sp2"))	
-	{
-		char mdlbase[MAX_QPATH];
-		COM_StripExtension(mod->name, mdlbase, sizeof(mdlbase));
-#ifdef MD3MODELS
-		if (gl_loadmd3.value && !buf)
-			buf = (unsigned *)COM_LoadStackFile (va("%s.md3", mdlbase), stackbuf, sizeof(stackbuf));
-#endif
-#ifdef MD2MODELS
-		if (gl_loadmd2.value && !buf)
-			buf = (unsigned *)COM_LoadStackFile (va("%s.md2", mdlbase), stackbuf, sizeof(stackbuf));
-#endif
-	}
-	if (!buf)
-	{
-		buf = (unsigned *)COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf));
-		if (!buf)
-		{
-			ext = COM_FileExtension(mod->name);
-#ifdef DOOMWADS
-			if (!stricmp(ext, "dsp"))
-			{
-				mod->needload = false;
-				GLMod_LoadDoomSprite(mod);
-				P_DefaultTrail(mod);
-				return mod;
-			}
-#endif
-
-couldntload:
-
-			if (crash)
-				Host_EndGame ("Mod_NumForName: %s not found or couldn't load", mod->name);
-
-			mod->type = mod_dummy;
-			mod->mins[0] = -16;
-			mod->mins[1] = -16;
-			mod->mins[2] = -16;
-			mod->maxs[0] = 16;
-			mod->maxs[1] = 16;
-			mod->maxs[2] = 16;
-			mod->needload = true;
-			P_DefaultTrail(mod);
-			return mod;
-			return NULL;
-		}
-	}
-	
-//
-// allocate a new model
-//
-	COM_FileBase (mod->name, loadname, sizeof(loadname));
-	Validation_IncludeFile(mod->name, (char *)buf, com_filesize);
-
-//
-// fill it in
-//
-
 	// set necessary engine flags for loading purposes
 	if (!strcmp(mod->name, "progs/player.mdl"))
 		mod->engineflags |= MDLF_PLAYER | MDLF_DOCRC;
@@ -545,121 +482,193 @@ couldntload:
 	else if (!strcmp(mod->name, "progs/eyes.mdl"))
 		mod->engineflags |= MDLF_DOCRC;
 
-// call the apropriate loader
+	// call the apropriate loader
 	mod->needload = false;
-	
-	switch (LittleLong(*(unsigned *)buf))
+
+	// get string used for replacement tokens
+	ext = COM_FileExtension(mod->name);
+	if (!Q_strcasecmp(ext, "spr") || !Q_strcasecmp(ext, "sp2"))
+		replstr = NULL; // sprite
+	else if (!Q_strcasecmp(ext, "dsp")) // doom sprite
 	{
-//The binary 3d mesh model formats
-	case IDPOLYHEADER:
-		if (!Mod_LoadQ1Model(mod, buf))
-			goto couldntload;
-		break;
+		replstr = NULL;
+		doomsprite = true;
+	}
+	else // assume models
+		replstr = r_replacemodels.string;
+
+	// gl_load24bit 0 disables all replacements
+	if (gl_load24bit.value)
+		replstr = NULL;
+
+	COM_StripExtension(mod->name, mdlbase, sizeof(mdlbase));
+
+	while (1)
+	{
+		for (replstr = COM_ParseStringSet(replstr); com_token[0] && !buf; replstr = COM_ParseStringSet(replstr))
+			buf = (unsigned *)COM_LoadStackFile (va("%s.%s", mdlbase, com_token), stackbuf, sizeof(stackbuf));
+
+		if (!buf)
+		{
+			if (lastload) // only load unreplaced file once
+				break;
+			lastload = true;
+			buf = (unsigned *)COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf));
+			if (!buf)
+			{
+#ifdef DOOMWADS
+				if (doomsprite) // special case needed for doom sprites
+				{
+					mod->needload = false;
+					GLMod_LoadDoomSprite(mod);
+					P_DefaultTrail(mod);
+					return mod;
+				}
+#endif
+				break; // failed to load unreplaced file and nothing left
+			}
+		}
 	
+//
+// allocate a new model
+//
+		COM_FileBase (mod->name, loadname, sizeof(loadname));
+
+//
+// fill it in
+//
+		
+		switch (LittleLong(*(unsigned *)buf))
+		{
+//The binary 3d mesh model formats
+		case IDPOLYHEADER:
+			if (!Mod_LoadQ1Model(mod, buf))
+				continue;
+			break;
+		
 #ifdef MD2MODELS
-	case MD2IDALIASHEADER:
-		if (!Mod_LoadQ2Model(mod, buf))
-			goto couldntload;
-		break;
+		case MD2IDALIASHEADER:
+			if (!Mod_LoadQ2Model(mod, buf))
+				continue;
+			break;
 #endif
 
 #ifdef MD3MODELS
-	case MD3_IDENT:
-		if (!Mod_LoadQ3Model (mod, buf))
-			goto couldntload;
-		break;
+		case MD3_IDENT:
+			if (!Mod_LoadQ3Model (mod, buf))
+				continue;
+			break;
 #endif
 
 #ifdef HALFLIFEMODELS
-	case (('T'<<24)+('S'<<16)+('D'<<8)+'I'):
-		if (!Mod_LoadHLModel (mod, buf))
-			goto couldntload;
-		break;
+		case (('T'<<24)+('S'<<16)+('D'<<8)+'I'):
+			if (!Mod_LoadHLModel (mod, buf))
+				continue;
+			break;
 #endif
 
 //Binary skeletal model formats
 #ifdef ZYMOTICMODELS
-	case (('O'<<24)+('M'<<16)+('Y'<<8)+'Z'):
-		if (!Mod_LoadZymoticModel(mod, buf))
-			goto couldntload;
-		break;
-	case (('K'<<24)+('R'<<16)+('A'<<8)+'D'):
-		if (!Mod_LoadDarkPlacesModel(mod, buf))
-			goto couldntload;
-		break;
+		case (('O'<<24)+('M'<<16)+('Y'<<8)+'Z'):
+			if (!Mod_LoadZymoticModel(mod, buf))
+				continue;
+			break;
+		case (('K'<<24)+('R'<<16)+('A'<<8)+'D'):
+			if (!Mod_LoadDarkPlacesModel(mod, buf))
+				continue;
+			break;
 #endif
 
 
 //Binary Sprites
 #ifdef SP2MODELS
-	case IDSPRITE2HEADER:
-		if (!GLMod_LoadSprite2Model (mod, buf))
-			goto couldntload;
-		break;
+		case IDSPRITE2HEADER:
+			if (!GLMod_LoadSprite2Model (mod, buf))
+				continue;
+			break;
 #endif
 
-	case IDSPRITEHEADER:
-		if (!GLMod_LoadSpriteModel (mod, buf))
-			goto couldntload;
-		break;
+		case IDSPRITEHEADER:
+			if (!GLMod_LoadSpriteModel (mod, buf))
+				continue;
+			break;
 
 
-//Binary Map formats
+	//Binary Map formats
 #ifdef Q2BSPS
-	case ('R'<<0)+('B'<<8)+('S'<<16)+('P'<<24):
-	case IDBSPHEADER:	//looks like id switched to have proper ids
-		if (!Mod_LoadQ2BrushModel (mod, buf))
-			goto couldntload;
-		break;
+		case ('R'<<0)+('B'<<8)+('S'<<16)+('P'<<24):
+		case IDBSPHEADER:	//looks like id switched to have proper ids
+			if (!Mod_LoadQ2BrushModel (mod, buf))
+				continue;
+			break;
 #endif
 #ifdef DOOMWADS
-	case (('D'<<24)+('A'<<16)+('W'<<8)+'I'):	//the id is hacked by the FS .wad loader (main wad).
-	case (('D'<<24)+('A'<<16)+('W'<<8)+'P'):	//the id is hacked by the FS .wad loader (patch wad).
-		if (!Mod_LoadDoomLevel (mod))
-			goto couldntload;
-		break;
+		case (('D'<<24)+('A'<<16)+('W'<<8)+'I'):	//the id is hacked by the FS .wad loader (main wad).
+		case (('D'<<24)+('A'<<16)+('W'<<8)+'P'):	//the id is hacked by the FS .wad loader (patch wad).
+			if (!Mod_LoadDoomLevel (mod))
+				continue;
+			break;
 #endif
 
-	case 30:	//hl
-	case 29:	//q1
-	case 28:	//prerel
-		if (!GLMod_LoadBrushModel (mod, buf))
-			goto couldntload;
-		break;
+		case 30:	//hl
+		case 29:	//q1
+		case 28:	//prerel
+			if (!GLMod_LoadBrushModel (mod, buf))
+				continue;
+			break;
 
-//Text based misc types.
-	default:
-		//check for text based headers
-		COM_Parse((char*)buf);
+	//Text based misc types.
+		default:
+			//check for text based headers
+			COM_Parse((char*)buf);
 #ifdef MD5MODELS
-		if (!strcmp(com_token, "MD5Version"))	//doom3 format, text based, skeletal
-		{
-			if (!Mod_LoadMD5MeshModel (mod, buf))
-				goto couldntload;
-			break;
-		}
-		if (!strcmp(com_token, "EXTERNALANIM"))	//custom format, text based, specifies skeletal models to load and which md5anim files to use.
-		{
-			if (!Mod_LoadCompositeAnim (mod, buf))
-				goto couldntload;
-			break;
-		}
+			if (!strcmp(com_token, "MD5Version"))	//doom3 format, text based, skeletal
+			{
+				if (!Mod_LoadMD5MeshModel (mod, buf))
+					continue;
+				break;
+			}
+			if (!strcmp(com_token, "EXTERNALANIM"))	//custom format, text based, specifies skeletal models to load and which md5anim files to use.
+			{
+				if (!Mod_LoadCompositeAnim (mod, buf))
+					continue;
+				break;
+			}
 #endif
 #ifdef TERRAIN
-		if (!strcmp(com_token, "terrain"))	//custom format, text based.
-		{
-			if (!GL_LoadHeightmapModel(mod, buf))
-				goto couldntload;
-			break;
-		}
+			if (!strcmp(com_token, "terrain"))	//custom format, text based.
+			{
+				if (!GL_LoadHeightmapModel(mod, buf))
+					continue;
+				break;
+			}
 #endif
 
-		Con_Printf(S_ERROR "Unrecognised model format %i loading %s\n", LittleLong(*(unsigned *)buf), mod->name);
-		goto couldntload;
+			Con_Printf(S_WARNING "Unrecognised model format %i\n", LittleLong(*(unsigned *)buf));
+			continue;
+		}
+
+		P_DefaultTrail(mod);
+		Validation_IncludeFile(mod->name, (char *)buf, com_filesize);
+
+		return mod;
 	}
 
-	P_DefaultTrail(mod);
+couldntload:
+	if (crash)
+		Host_EndGame ("Mod_NumForName: %s not found or couldn't load", mod->name);
 
+	Con_Printf(S_ERROR "Unable to load or replace %s\n", mod->name);
+	mod->type = mod_dummy;
+	mod->mins[0] = -16;
+	mod->mins[1] = -16;
+	mod->mins[2] = -16;
+	mod->maxs[0] = 16;
+	mod->maxs[1] = 16;
+	mod->maxs[2] = 16;
+	mod->needload = true;
+	mod->engineflags = 0;
+	P_DefaultTrail(mod);
 	return mod;
 }
 
diff --git a/engine/sw/sw_model.c b/engine/sw/sw_model.c
index 0cae034fb..bd2b753b4 100644
--- a/engine/sw/sw_model.c
+++ b/engine/sw/sw_model.c
@@ -274,9 +274,16 @@ Loads a model into the cache
 */
 model_t *SWMod_LoadModel (model_t *mod, qboolean crash)
 {
+	extern cvar_t r_replacemodels;
+
 	void	*d;
 	unsigned *buf = NULL;
 	qbyte	stackbuf[1024];		// avoid dirtying the cache heap
+	char mdlbase[MAX_QPATH];
+	qboolean lastload = false;
+	char *replstr;
+//	qboolean doomsprite = false;
+
 	char *ext;
 
 	if (!mod->needload)
@@ -305,59 +312,6 @@ model_t *SWMod_LoadModel (model_t *mod, qboolean crash)
 //
 // because the world is so huge, load it one piece at a time
 //
-
-	//look for a replacement
-	ext = COM_FileExtension(mod->name);
-#ifndef CLIENTONLY
-	if (!isDedicated && (!Q_strcasecmp(ext, "mdl") || !Q_strcasecmp(ext, "bsp")))
-	{
-		char mdlbase[MAX_QPATH];
-		COM_StripExtension(mod->name, mdlbase, sizeof(mdlbase));
-
-		if (!buf)
-			buf = (unsigned *)COM_LoadStackFile (va("%s.md3", mdlbase), stackbuf, sizeof(stackbuf));
-		if (!buf)
-			buf = (unsigned *)COM_LoadStackFile (va("%s.md2", mdlbase), stackbuf, sizeof(stackbuf));
-	}
-#endif
-	if (!buf)
-	{
-//
-// load the file
-//
-		buf = (unsigned *)COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf));
-		if (!buf)
-		{
-			if (crash)
-				Host_EndGame ("Mod_NumForName: %s not found or couldn't load", mod->name);
-
-			mod->type = mod_dummy;
-			mod->mins[0] = -16;
-			mod->mins[1] = -16;
-			mod->mins[2] = -16;
-			mod->maxs[0] = 16;
-			mod->maxs[1] = 16;
-			mod->maxs[2] = 16;
-			mod->needload = true;
-			P_DefaultTrail(mod);
-			return mod;
-		}
-	}
-	
-//
-// allocate a new model
-//
-	COM_FileBase (mod->name, loadname, sizeof(loadname));
-	
-	loadmodel = mod;
-#ifndef SERVERONLY
-	if (cl.model_precache[1])	//not the world.
-		Validation_IncludeFile(mod->name, (char *)buf, com_filesize);
-#endif
-//
-// fill it in
-//
-
 	// set necessary engine flags for loading purposes
 	if (!strcmp(mod->name, "progs/player.mdl"))
 	{
@@ -377,73 +331,119 @@ model_t *SWMod_LoadModel (model_t *mod, qboolean crash)
 	else if (!strcmp(mod->name, "progs/eyes.mdl"))
 		mod->engineflags |= MDLF_DOCRC;
 
-
-// call the apropriate loader
+	// call the apropriate loader
 	mod->needload = false;
-	
-	switch (LittleLong(*(unsigned *)buf))
+
+	// get string used for replacement tokens
+	ext = COM_FileExtension(mod->name);
+	if (isDedicated)
+		replstr = NULL;
+	else if (!Q_strcasecmp(ext, "spr") || !Q_strcasecmp(ext, "sp2"))
+		replstr = NULL; // sprite
+	else if (!Q_strcasecmp(ext, "dsp")) // doom sprite
+		replstr = NULL;
+	else // assume models
+		replstr = r_replacemodels.string;
+
+	COM_StripExtension(mod->name, mdlbase, sizeof(mdlbase));
+
+	while (1)
 	{
-#ifndef SERVERONLY
-	case IDPOLYHEADER:
-		if (!SWMod_LoadAliasModel (mod, buf))
-			goto couldntload;
-		break;
+		for (replstr = COM_ParseStringSet(replstr); com_token[0] && !buf; replstr = COM_ParseStringSet(replstr))
+			buf = (unsigned *)COM_LoadStackFile (va("%s.%s", mdlbase, com_token), stackbuf, sizeof(stackbuf));
 
-	case MD2IDALIASHEADER:
-		if (!SWMod_LoadAlias2Model (mod, buf))
-			goto couldntload;
-		break;
-
-	case MD3_IDENT:
-		if (!SWMod_LoadAlias3Model (mod, buf))
-			goto couldntload;
-		break;
-
-	case IDSPRITEHEADER:
-		if (!SWMod_LoadSpriteModel (mod, buf))
-			goto couldntload;
-		break;
+		if (!buf)
+		{
+			if (lastload) // only load unreplaced file once
+				break;
+			lastload = true;
+			buf = (unsigned *)COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf));
+			if (!buf) // we would attempt Doom sprites here, but SW doesn't support them
+				break; // failed to load unreplaced file and nothing left
+		}
 	
-	case IDSPRITE2HEADER:
-		if (!SWMod_LoadSprite2Model (mod, buf))
-			goto couldntload;
-		break;
+//
+// allocate a new model
+//
+		COM_FileBase (mod->name, loadname, sizeof(loadname));
+			
+		loadmodel = mod;
+//
+// fill it in
+//
+			
+		switch (LittleLong(*(unsigned *)buf))
+		{
+#ifndef SERVERONLY
+		case IDPOLYHEADER:
+			if (!SWMod_LoadAliasModel (mod, buf))
+				continue;
+			break;
+
+		case MD2IDALIASHEADER:
+			if (!SWMod_LoadAlias2Model (mod, buf))
+				continue;
+			break;
+
+		case MD3_IDENT:
+			if (!SWMod_LoadAlias3Model (mod, buf))
+				continue;
+			break;
+
+		case IDSPRITEHEADER:
+			if (!SWMod_LoadSpriteModel (mod, buf))
+				continue;
+			break;
+			
+		case IDSPRITE2HEADER:
+			if (!SWMod_LoadSprite2Model (mod, buf))
+				continue;
+			break;
 #endif
 #ifdef Q2BSPS
-	case IDBSPHEADER:	//looks like id switched to have proper ids
-		if (!Mod_LoadQ2BrushModel (mod, buf))
-			goto couldntload;
-		break;
+		case IDBSPHEADER:	//looks like id switched to have proper ids
+			if (!Mod_LoadQ2BrushModel (mod, buf))
+				continue;
+			break;
 #endif
 
-	case BSPVERSIONHL:
-	case BSPVERSION:	//hmm.
-	case BSPVERSIONPREREL:
-		if (!SWMod_LoadBrushModel (mod, buf))
-			goto couldntload;
-		break;
+		case BSPVERSIONHL:
+		case BSPVERSION:	//hmm.
+		case BSPVERSIONPREREL:
+			if (!SWMod_LoadBrushModel (mod, buf))
+				continue;
+			break;
 
-	default:	//some telejano mods can do this
-		Con_Printf(S_ERROR "model %s, unrecognized format %i\n", mod->name, LittleLong(*(unsigned *)buf));
-couldntload:
+		default:	//some telejano mods can do this
+			Con_Printf(S_WARNING "Unrecognized format %i\n", LittleLong(*(unsigned *)buf));
+			continue;
+		}
 
-		if (crash)
-			Host_EndGame ("Mod_NumForName: %s not found or couldn't load", mod->name);
-
-		mod->type = mod_dummy;
-		mod->mins[0] = -16;
-		mod->mins[1] = -16;
-		mod->mins[2] = -16;
-		mod->maxs[0] = 16;
-		mod->maxs[1] = 16;
-		mod->maxs[2] = 16;
-		mod->needload = true;
 		P_DefaultTrail(mod);
+
+#ifndef SERVERONLY
+		if (cl.model_precache[1])	//not the world.
+			Validation_IncludeFile(mod->name, (char *)buf, com_filesize);
+#endif
+
 		return mod;
 	}
 
-	P_DefaultTrail(mod);
+couldntload:
+	if (crash)
+		Host_EndGame ("Mod_NumForName: %s not found or couldn't load", mod->name);
 
+	Con_Printf(S_ERROR "Unable to load or replace %s\n", mod->name);
+	mod->type = mod_dummy;
+	mod->mins[0] = -16;
+	mod->mins[1] = -16;
+	mod->mins[2] = -16;
+	mod->maxs[0] = 16;
+	mod->maxs[1] = 16;
+	mod->maxs[2] = 16;
+	mod->needload = true;
+	mod->engineflags = 0;
+	P_DefaultTrail(mod);
 	return mod;
 }