PageRenderTime 906ms CodeModel.GetById 101ms app.highlight 545ms RepoModel.GetById 119ms app.codeStats 1ms

/codemp/cgame/cg_servercmds.c

https://github.com/Stoiss/jaMME
C | 1840 lines | 1390 code | 280 blank | 170 comment | 385 complexity | a77039e49a668af86d705d0a01ea5790 MD5 | raw file
   1// Copyright (C) 1999-2000 Id Software, Inc.
   2//
   3// cg_servercmds.c -- reliably sequenced text commands sent by the server
   4// these are processed at snapshot transition time, so there will definately
   5// be a valid snapshot this frame
   6
   7#include "cg_local.h"
   8#include "ui/menudef.h"
   9#include "cg_lights.h"
  10#include "ghoul2/G2.h"
  11#include "ui/ui_public.h"
  12
  13
  14#define SSF_GRAPPLE_SWING		(1<<0)
  15#define SSF_SCOREBOARD_LARGE	(1<<1)
  16#define SSF_SCOREBOARD_KD		(1<<2)
  17#define SSF_CHAT_FILTERS		(1<<3)
  18#define SSF_FIXED_WEAP_ANIMS	(1<<4)
  19#define SSF_MERC_FLAMETHOWER	(1<<5)
  20#define JAPLUS_SERVER_FLAGS (SSF_GRAPPLE_SWING|SSF_SCOREBOARD_KD|SSF_MERC_FLAMETHOWER)
  21
  22
  23/*
  24=================
  25CG_ParseScores
  26
  27=================
  28*/
  29qboolean Server_Supports( unsigned int supportFlag )
  30{
  31	return !!(cg.japlus.SSF & supportFlag );
  32}
  33
  34static ID_INLINE int GetScoreOffset( void ) {
  35	return Server_Supports( SSF_SCOREBOARD_KD ) ? 15 : 14;
  36}
  37
  38static void CG_ParseScores( void ) {
  39	if (cg.japlus.detected && cg_japlusFix.integer) {
  40		int		i=0, scoreIndex=0, powerups=0, readScores=0;
  41		int		scoreOffset = GetScoreOffset();
  42
  43		if ( Server_Supports( SSF_SCOREBOARD_LARGE ) )
  44			readScores = Com_Clampi( 0, MAX_CLIENTS, atoi( CG_Argv( 1 ) ) );
  45		else
  46			readScores = Com_Clampi( 0, MAX_CLIENT_SCORE_SEND, atoi( CG_Argv( 1 ) ) );
  47		cg.numScores = readScores;
  48
  49		cg.teamScores[0] = atoi( CG_Argv( 2 ) );
  50		cg.teamScores[1] = atoi( CG_Argv( 3 ) );
  51
  52		memset( cg.scores, 0, sizeof( cg.scores ) );
  53		for ( i=0, scoreIndex=0; i<readScores; i++ )
  54		{
  55			cg.scores[scoreIndex].client			= atoi( CG_Argv( i * scoreOffset +  4 ) );
  56			if ( cg.scores[scoreIndex].client < 0 || cg.scores[scoreIndex].client >= MAX_CLIENTS )
  57				continue;
  58			cg.scores[scoreIndex].score				= atoi( CG_Argv( i * scoreOffset +  5 ) );
  59			cg.scores[scoreIndex].ping				= atoi( CG_Argv( i * scoreOffset +  6 ) );
  60			cg.scores[scoreIndex].time				= atoi( CG_Argv( i * scoreOffset +  7 ) );
  61			cg.scores[scoreIndex].scoreFlags		= atoi( CG_Argv( i * scoreOffset +  8 ) );
  62			powerups								= atoi( CG_Argv( i * scoreOffset +  9 ) );
  63			cg.scores[scoreIndex].accuracy			= atoi( CG_Argv( i * scoreOffset + 10 ) );
  64			cg.scores[scoreIndex].impressiveCount	= atoi( CG_Argv( i * scoreOffset + 11 ) );
  65			cg.scores[scoreIndex].excellentCount	= atoi( CG_Argv( i * scoreOffset + 12 ) );
  66			cg.scores[scoreIndex].guantletCount		= atoi( CG_Argv( i * scoreOffset + 13 ) );
  67			cg.scores[scoreIndex].defendCount		= atoi( CG_Argv( i * scoreOffset + 14 ) );
  68			cg.scores[scoreIndex].assistCount		= atoi( CG_Argv( i * scoreOffset + 15 ) );
  69			cg.scores[scoreIndex].perfect			= atoi( CG_Argv( i * scoreOffset + 16 ) );
  70			cg.scores[scoreIndex].captures			= atoi( CG_Argv( i * scoreOffset + 17 ) );
  71
  72			if ( Server_Supports( SSF_SCOREBOARD_KD ) )
  73				cg.scores[scoreIndex].deaths		= atoi( CG_Argv( i * scoreOffset + 18 ) );
  74
  75			cgs.clientinfo[ cg.scores[scoreIndex].client ].score = cg.scores[scoreIndex].score;
  76			cgs.clientinfo[ cg.scores[scoreIndex].client ].powerups	= powerups;
  77
  78			cg.scores[scoreIndex].team = cgs.clientinfo[ cg.scores[scoreIndex].client ].team;
  79
  80			scoreIndex++;
  81		}
  82
  83		CG_SetScoreSelection( NULL );
  84	} else {
  85		int		i, powerups, readScores;
  86
  87		cg.numScores = atoi( CG_Argv( 1 ) );
  88
  89		readScores = cg.numScores;
  90
  91		if (readScores > MAX_CLIENT_SCORE_SEND)
  92		{
  93			readScores = MAX_CLIENT_SCORE_SEND;
  94		}
  95
  96		if ( cg.numScores > MAX_CLIENTS ) {
  97			cg.numScores = MAX_CLIENTS;
  98		}
  99
 100		cg.numScores = readScores;
 101
 102		cg.teamScores[0] = atoi( CG_Argv( 2 ) );
 103		cg.teamScores[1] = atoi( CG_Argv( 3 ) );
 104
 105		memset( cg.scores, 0, sizeof( cg.scores ) );
 106		for ( i = 0 ; i < readScores ; i++ ) {
 107			//
 108			cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) );
 109			cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) );
 110			cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) );
 111			cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) );
 112			cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) );
 113			powerups = atoi( CG_Argv( i * 14 + 9 ) );
 114			cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10));
 115			cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11));
 116			cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12));
 117			cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13));
 118			cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14));
 119			cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15));
 120			cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16));
 121			cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17));
 122
 123			if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) {
 124				cg.scores[i].client = 0;
 125			}
 126			cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score;
 127			cgs.clientinfo[ cg.scores[i].client ].powerups = powerups;
 128
 129			cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team;
 130		}
 131		CG_SetScoreSelection(NULL);
 132	}
 133}
 134
 135/*
 136=================
 137CG_ParseTeamInfo
 138
 139=================
 140*/
 141static void CG_ParseTeamInfo( void ) {
 142	int		i;
 143	int		client;
 144
 145	numSortedTeamPlayers = atoi( CG_Argv( 1 ) );
 146	if( numSortedTeamPlayers < 0 || numSortedTeamPlayers > TEAM_MAXOVERLAY )
 147	{
 148		CG_Error( "CG_ParseTeamInfo: numSortedTeamPlayers out of range (%d)", numSortedTeamPlayers );
 149		return;
 150	}
 151
 152	for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) {
 153		client = atoi( CG_Argv( i * 6 + 2 ) );
 154		if( client < 0 || client >= MAX_CLIENTS )
 155		{
 156			CG_Error( "CG_ParseTeamInfo: bad client number: %d", client );
 157			return;
 158		}
 159
 160		sortedTeamPlayers[i] = client;
 161
 162		cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) );
 163		cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) );
 164		cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) );
 165		cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) );
 166		cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) );
 167	}
 168}
 169
 170/*
 171================
 172CG_ParseServerinfo
 173
 174This is called explicitly when the gamestate is first received,
 175and whenever the server updates any serverinfo flagged cvars
 176================
 177*/
 178void CG_ParseServerinfo( void ) {
 179	const char *info = NULL, *tinfo = NULL;
 180	char *mapname;
 181	int i;
 182
 183	info = CG_ConfigString( CS_SERVERINFO );
 184
 185	cgs.debugMelee = atoi( Info_ValueForKey( info, "g_debugMelee" ) ); //trap_Cvar_GetHiddenVarValue("g_iknowkungfu");
 186	cgs.stepSlideFix = atoi( Info_ValueForKey( info, "g_stepSlideFix" ) );
 187
 188	cgs.noSpecMove = atoi( Info_ValueForKey( info, "g_noSpecMove" ) );
 189
 190	trap_Cvar_Set("bg_fighterAltControl", Info_ValueForKey( info, "bg_fighterAltControl" ));
 191
 192	cgs.siegeTeamSwitch = atoi( Info_ValueForKey( info, "g_siegeTeamSwitch" ) );
 193
 194	cgs.showDuelHealths = atoi( Info_ValueForKey( info, "g_showDuelHealths" ) );
 195
 196	cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) );
 197	trap_Cvar_Set("g_gametype", va("%i", cgs.gametype));
 198	cgs.needpass = atoi( Info_ValueForKey( info, "needpass" ) );
 199	cgs.jediVmerc = atoi( Info_ValueForKey( info, "g_jediVmerc" ) );
 200	cgs.wDisable = atoi( Info_ValueForKey( info, "wdisable" ) );
 201	cgs.fDisable = atoi( Info_ValueForKey( info, "fdisable" ) );
 202	cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) );
 203	cgs.duel_fraglimit = atoi( Info_ValueForKey( info, "duel_fraglimit" ) );
 204	cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) );
 205
 206	// reset fraglimit warnings
 207	i = atoi( Info_ValueForKey( info, "fraglimit" ) );
 208	if ( cgs.fraglimit < i )
 209		cg.fraglimitWarnings &= ~(1|2|4);
 210	cgs.fraglimit = i;
 211
 212	// reset timelimit warnings
 213	i = atoi( Info_ValueForKey( info, "timelimit" ) );
 214	if ( cgs.timelimit != i )
 215		cg.timelimitWarnings &= ~(1|2);
 216	cgs.timelimit = i;
 217
 218	cgs.maxclients = Com_Clampi( 0, MAX_CLIENTS, atoi( Info_ValueForKey( info, "sv_maxclients" ) ) );
 219
 220	cg.japlus.detected = qfalse;
 221
 222	CG_Printf("\n");
 223	if (!Q_stricmpn(Info_ValueForKey(info, "gamename"), "JA+ Mod", 7) 
 224		|| !Q_stricmpn(Info_ValueForKey(info, "gamename"), "^4U^3A^5Galaxy", 14)) {	//uag :s
 225		cg.japlus.detected = qtrue;
 226		cg.japlus.SSF = JAPLUS_SERVER_FLAGS;
 227		CG_Printf("JA+ demo detected\n");
 228		Com_Printf("Server support hints: 0x%X\n", cg.japlus.SSF);
 229	} else if (!Q_stricmpn(Info_ValueForKey(info, "gamename"), "MakerMod", 8)) {
 230		CG_Printf("MakerMod demo detected\n");
 231	} else if (!Q_stricmpn(Info_ValueForKey(info, "gamename"), "Lugormod", 8)) {
 232		CG_Printf("Lugormod demo detected\n");
 233	} else {
 234		CG_Printf("Base/Unknown demo detected\n");
 235	}
 236	CG_Printf( "\n" );
 237
 238	mapname = Info_ValueForKey( info, "mapname" );
 239
 240	//rww - You must do this one here, Info_ValueForKey always uses the same memory pointer.
 241	trap_Cvar_Set ( "ui_about_mapname", mapname );
 242
 243	Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname );
 244//	Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) );
 245//	trap_Cvar_Set("g_redTeam", cgs.redTeam);
 246//	Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) );
 247//	trap_Cvar_Set("g_blueTeam", cgs.blueTeam);
 248
 249	trap_Cvar_Set ( "ui_about_gametype", va("%i", cgs.gametype ) );
 250	trap_Cvar_Set ( "ui_about_fraglimit", va("%i", cgs.fraglimit ) );
 251	trap_Cvar_Set ( "ui_about_duellimit", va("%i", cgs.duel_fraglimit ) );
 252	trap_Cvar_Set ( "ui_about_capturelimit", va("%i", cgs.capturelimit ) );
 253	trap_Cvar_Set ( "ui_about_timelimit", va("%i", cgs.timelimit ) );
 254	trap_Cvar_Set ( "ui_about_maxclients", va("%i", cgs.maxclients ) );
 255	trap_Cvar_Set ( "ui_about_dmflags", va("%i", cgs.dmflags ) );
 256	trap_Cvar_Set ( "ui_about_hostname", Info_ValueForKey( info, "sv_hostname" ) );
 257	trap_Cvar_Set ( "ui_about_needpass", Info_ValueForKey( info, "g_needpass" ) );
 258	trap_Cvar_Set ( "ui_about_botminplayers", Info_ValueForKey ( info, "bot_minplayers" ) );
 259
 260	//Set the siege teams based on what the server has for overrides.
 261	trap_Cvar_Set("cg_siegeTeam1", Info_ValueForKey(info, "g_siegeTeam1"));
 262	trap_Cvar_Set("cg_siegeTeam2", Info_ValueForKey(info, "g_siegeTeam2"));
 263
 264	tinfo = CG_ConfigString( CS_TERRAINS + 1 );
 265	if ( !tinfo || !*tinfo )
 266	{
 267		cg.mInRMG = qfalse;
 268	}
 269	else
 270	{
 271		int weather = 0;
 272
 273		cg.mInRMG = qtrue;
 274		trap_Cvar_Set("RMG", "1");
 275
 276		weather = atoi( Info_ValueForKey( info, "RMG_weather" ) );
 277
 278		trap_Cvar_Set("RMG_weather", va("%i", weather));
 279
 280		if (weather == 1 || weather == 2)
 281		{
 282			cg.mRMGWeather = qtrue;
 283		}
 284		else
 285		{
 286			cg.mRMGWeather = qfalse;
 287		}
 288	}
 289
 290	//Raz: Fix bogus vote strings
 291	Q_strncpyz( cgs.voteString, CG_ConfigString( CS_VOTE_STRING ), sizeof( cgs.voteString ) );
 292
 293	//Raz: Synchronise our expected snaps/sec with the server's framerate
 294	//		OpenJK servers will try to match us to the sv_fps too (sv_client.cpp -> SV_UserinfoChanged)
 295	i = atoi( Info_ValueForKey( info, "sv_fps" ) );
 296	if ( i )
 297		trap_Cvar_Set( "snaps", va( "%i", i ) );
 298}
 299
 300/*
 301==================
 302CG_ParseWarmup
 303==================
 304*/
 305static void CG_ParseWarmup( void ) {
 306	const char	*info;
 307	int			warmup;
 308
 309	info = CG_ConfigString( CS_WARMUP );
 310
 311	warmup = atoi( info );
 312	cg.warmupCount = -1;
 313
 314	cg.warmup = warmup;
 315}
 316
 317//Raz: This is a reverse map of flag statuses as seen in g_team.c
 318//static char ctfFlagStatusRemap[] = { '0', '1', '*', '*', '2' };
 319static char ctfFlagStatusRemap[] = { 	
 320	FLAG_ATBASE,
 321	FLAG_TAKEN,			// CTF
 322	// server doesn't use FLAG_TAKEN_RED or FLAG_TAKEN_BLUE
 323	// which was originally for 1-flag CTF.
 324	FLAG_DROPPED
 325};
 326
 327/*
 328================
 329CG_SetConfigValues
 330
 331Called on load to set the initial values from configure strings
 332================
 333*/
 334void CG_SetConfigValues( void ) {
 335	const char *s;
 336	const char *str;
 337
 338	cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) );
 339	cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) );
 340	cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
 341	if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) {
 342		int redflagId = 0, blueflagId = 0;
 343		s = CG_ConfigString( CS_FLAGSTATUS );
 344		redflagId = s[0] - '0';
 345		blueflagId = s[1] - '0';
 346		// fix: proper flag statuses mapping for dropped flag
 347		if ( redflagId >= 0 && redflagId < ARRAY_LEN( ctfFlagStatusRemap ) ) 
 348			cgs.redflag = ctfFlagStatusRemap[redflagId];
 349		if ( blueflagId >= 0 && blueflagId < ARRAY_LEN( ctfFlagStatusRemap ) ) 
 350			cgs.blueflag = ctfFlagStatusRemap[blueflagId];
 351	}
 352	cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
 353
 354	// Track who the jedi master is
 355	cgs.jediMaster = atoi ( CG_ConfigString ( CS_CLIENT_JEDIMASTER ) );
 356	cgs.duelWinner = atoi ( CG_ConfigString ( CS_CLIENT_DUELWINNER ) );
 357
 358	str = CG_ConfigString(CS_CLIENT_DUELISTS);
 359
 360	if (str && str[0])
 361	{
 362		char buf[64];
 363		int c = 0;
 364		int i = 0;
 365
 366		while (str[i] && str[i] != '|')
 367		{
 368			buf[c] = str[i];
 369			c++;
 370			i++;
 371		}
 372		buf[c] = 0;
 373
 374		cgs.duelist1 = atoi ( buf );
 375		c = 0;
 376
 377		i++;
 378		while (str[i])
 379		{
 380			buf[c] = str[i];
 381			c++;
 382			i++;
 383		}
 384		buf[c] = 0;
 385
 386		cgs.duelist2 = atoi ( buf );
 387	}
 388}
 389
 390/*
 391=====================
 392CG_ShaderStateChanged
 393=====================
 394*/
 395void CG_ShaderStateChanged(void) {
 396	char originalShader[MAX_QPATH];
 397	char newShader[MAX_QPATH];
 398	char timeOffset[16];
 399	const char *o;
 400	char *n,*t;
 401
 402	o = CG_ConfigString( CS_SHADERSTATE );
 403	while (o && *o) {
 404		n = strstr(o, "=");
 405		if (n && *n) {
 406			strncpy(originalShader, o, n-o);
 407			originalShader[n-o] = 0;
 408			n++;
 409			t = strstr(n, ":");
 410			if (t && *t) {
 411				strncpy(newShader, n, t-n);
 412				newShader[t-n] = 0;
 413			} else {
 414				break;
 415			}
 416			t++;
 417			o = strstr(t, "@");
 418			if (o) {
 419				strncpy(timeOffset, t, o-t);
 420				timeOffset[o-t] = 0;
 421				o++;
 422				trap_R_RemapShader( originalShader, newShader, timeOffset );
 423			}
 424		} else {
 425			break;
 426		}
 427	}
 428}
 429
 430extern char *cg_customSoundNames[MAX_CUSTOM_SOUNDS];
 431extern const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS];
 432extern const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS];
 433extern const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS];
 434extern const char *cg_customDuelSoundNames[MAX_CUSTOM_DUEL_SOUNDS];
 435
 436static const char *GetCustomSoundForType(int setType, int index)
 437{
 438	switch (setType)
 439	{
 440	case 1:
 441		return cg_customSoundNames[index];
 442	case 2:
 443		return cg_customCombatSoundNames[index];
 444	case 3:
 445		return cg_customExtraSoundNames[index];
 446	case 4:
 447		return cg_customJediSoundNames[index];
 448	case 5:
 449		return bg_customSiegeSoundNames[index];
 450	case 6:
 451		return cg_customDuelSoundNames[index];
 452	default:
 453		assert(0);
 454		return NULL;
 455	}
 456}
 457
 458void SetCustomSoundForType(clientInfo_t *ci, int setType, int index, sfxHandle_t sfx)
 459{
 460	switch (setType)
 461	{
 462	case 1:
 463		ci->sounds[index] = sfx;
 464		break;
 465	case 2:
 466		ci->combatSounds[index] = sfx;
 467		break;
 468	case 3:
 469		ci->extraSounds[index] = sfx;
 470		break;
 471	case 4:
 472		ci->jediSounds[index] = sfx;
 473		break;
 474	case 5:
 475		ci->siegeSounds[index] = sfx;
 476		break;
 477	case 6:
 478		ci->duelSounds[index] = sfx;
 479		break;
 480	default:
 481		assert(0);
 482		break;
 483	}
 484}
 485
 486static void CG_RegisterCustomSounds(clientInfo_t *ci, int setType, const char *psDir)
 487{
 488	int iTableEntries = 0;
 489	int i;
 490
 491	switch (setType)
 492	{
 493	case 1:
 494		iTableEntries = MAX_CUSTOM_SOUNDS;
 495		break;
 496	case 2:
 497		iTableEntries = MAX_CUSTOM_COMBAT_SOUNDS;
 498		break;
 499	case 3:
 500		iTableEntries = MAX_CUSTOM_EXTRA_SOUNDS;
 501		break;
 502	case 4:
 503		iTableEntries = MAX_CUSTOM_JEDI_SOUNDS;
 504		break;
 505	case 5:
 506		iTableEntries = MAX_CUSTOM_SIEGE_SOUNDS;
 507	default:
 508		assert(0);
 509		return;
 510	}
 511
 512	for ( i = 0 ; i<iTableEntries; i++ ) 
 513	{
 514		sfxHandle_t hSFX;
 515		const char *s = GetCustomSoundForType(setType, i);
 516
 517		if ( !s ) 
 518		{
 519			break;
 520		}
 521
 522		s++;
 523		hSFX = trap_S_RegisterSound( va("sound/chars/%s/misc/%s.mp3", psDir, s) );	//all of them have .mp3?
 524
 525		if (hSFX == 0)
 526		{
 527			char modifiedSound[MAX_QPATH];
 528			char *p;
 529
 530			strcpy(modifiedSound, s);
 531			p = strchr(modifiedSound,'.');
 532
 533			if (p)
 534			{
 535				char testNumber[2];
 536				p--;
 537
 538				//before we destroy it.. we want to see if this is actually a number.
 539				//If it isn't a number then don't try decrementing and registering as
 540				//it will only cause a disk hit (we don't try precaching such files)
 541				testNumber[0] = *p;
 542				testNumber[1] = 0;
 543				if (atoi(testNumber))
 544				{
 545					*p = 0;
 546
 547					strcat(modifiedSound, "1.wav");
 548
 549					hSFX = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", psDir, modifiedSound) );
 550				}
 551			}
 552		}
 553		
 554		SetCustomSoundForType(ci, setType, i, hSFX);
 555	}
 556}
 557
 558void CG_PrecacheNPCSounds(const char *str)
 559{
 560	char sEnd[MAX_QPATH];
 561	char pEnd[MAX_QPATH];
 562	int i = 0;
 563	int j = 0;
 564	int k = 0;
 565
 566	k = 2;
 567
 568	while (str[k])
 569	{
 570		pEnd[k-2] = str[k];
 571		k++;
 572	}
 573	pEnd[k-2] = 0;
 574
 575	while (i < 4) //4 types
 576	{ //It would be better if we knew what type this actually was (extra, combat, jedi, etc).
 577	  //But that would require extra configstring indexing and that is a bad thing.
 578
 579		while (j < MAX_CUSTOM_SOUNDS)
 580		{
 581			const char *s = GetCustomSoundForType(i+1, j);
 582
 583			if (s && s[0])
 584			{ //whatever it is, try registering it under this folder.
 585				k = 1;
 586				while (s[k])
 587				{
 588					sEnd[k-1] = s[k];
 589					k++;
 590				}
 591				sEnd[k-1] = 0;
 592
 593				trap_S_ShutUp(qtrue);
 594				trap_S_RegisterSound( va("sound/chars/%s/misc/%s.mp3", pEnd, sEnd) );	//all of them have .mp3?
 595				trap_S_ShutUp(qfalse);
 596			}
 597			else
 598			{ //move onto the next set
 599				break;
 600			}
 601
 602			j++;
 603		}
 604
 605		j = 0;
 606		i++;
 607	}
 608}
 609
 610void CG_HandleNPCSounds(centity_t *cent)
 611{
 612	if (!cent->npcClient)
 613	{
 614		return;
 615	}
 616
 617	//standard
 618	if (cent->currentState.csSounds_Std)
 619	{
 620		const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Std );
 621
 622		if (s && s[0])
 623		{
 624			char sEnd[MAX_QPATH];
 625			int i = 2;
 626			int j = 0;
 627
 628			//Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates
 629			//it is an NPC custom sound dir.
 630			while (s[i])
 631			{
 632				sEnd[j] = s[i];
 633				j++;
 634				i++;
 635			}
 636			sEnd[j] = 0;
 637
 638			CG_RegisterCustomSounds(cent->npcClient, 1, sEnd);
 639		}
 640	}
 641	else
 642	{
 643		memset(&cent->npcClient->sounds, 0, sizeof(cent->npcClient->sounds));
 644	}
 645
 646	//combat
 647	if (cent->currentState.csSounds_Combat)
 648	{
 649		const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Combat );
 650
 651		if (s && s[0])
 652		{
 653			char sEnd[MAX_QPATH];
 654			int i = 2;
 655			int j = 0;
 656
 657			//Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates
 658			//it is an NPC custom sound dir.
 659			while (s[i])
 660			{
 661				sEnd[j] = s[i];
 662				j++;
 663				i++;
 664			}
 665			sEnd[j] = 0;
 666
 667			CG_RegisterCustomSounds(cent->npcClient, 2, sEnd);
 668		}
 669	}
 670	else
 671	{
 672		memset(&cent->npcClient->combatSounds, 0, sizeof(cent->npcClient->combatSounds));
 673	}
 674
 675	//extra
 676	if (cent->currentState.csSounds_Extra)
 677	{
 678		const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Extra );
 679
 680		if (s && s[0])
 681		{
 682			char sEnd[MAX_QPATH];
 683			int i = 2;
 684			int j = 0;
 685
 686			//Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates
 687			//it is an NPC custom sound dir.
 688			while (s[i])
 689			{
 690				sEnd[j] = s[i];
 691				j++;
 692				i++;
 693			}
 694			sEnd[j] = 0;
 695
 696			CG_RegisterCustomSounds(cent->npcClient, 3, sEnd);
 697		}
 698	}
 699	else
 700	{
 701		memset(&cent->npcClient->extraSounds, 0, sizeof(cent->npcClient->extraSounds));
 702	}
 703
 704	//jedi
 705	if (cent->currentState.csSounds_Jedi)
 706	{
 707		const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Jedi );
 708
 709		if (s && s[0])
 710		{
 711			char sEnd[MAX_QPATH];
 712			int i = 2;
 713			int j = 0;
 714
 715			//Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates
 716			//it is an NPC custom sound dir.
 717			while (s[i])
 718			{
 719				sEnd[j] = s[i];
 720				j++;
 721				i++;
 722			}
 723			sEnd[j] = 0;
 724
 725			CG_RegisterCustomSounds(cent->npcClient, 4, sEnd);
 726		}
 727	}
 728	else
 729	{
 730		memset(&cent->npcClient->jediSounds, 0, sizeof(cent->npcClient->jediSounds));
 731	}
 732}
 733
 734int CG_HandleAppendedSkin(char *modelName);
 735void CG_CacheG2AnimInfo(char *modelName);
 736
 737// nmckenzie: DUEL_HEALTH - fixme - we could really clean this up immensely with some helper functions.
 738void SetDuelistHealthsFromConfigString ( const char *str ) {
 739	char buf[64];
 740	int c = 0;
 741	int i = 0;
 742
 743	while (str[i] && str[i] != '|')
 744	{
 745		buf[c] = str[i];
 746		c++;
 747		i++;
 748	}
 749	buf[c] = 0;
 750
 751	cgs.duelist1health = atoi ( buf );
 752
 753	c = 0;
 754	i++;
 755	while (str[i] && str[i] != '|')
 756	{
 757		buf[c] = str[i];
 758		c++;
 759		i++;
 760	}
 761	buf[c] = 0;
 762
 763	cgs.duelist2health = atoi ( buf );
 764
 765	c = 0;
 766	i++;
 767	if ( str[i] == '!' )
 768	{	// we only have 2 duelists, apparently.
 769		cgs.duelist3health = -1;
 770		return;
 771	}
 772
 773	while (str[i] && str[i] != '|')
 774	{
 775		buf[c] = str[i];
 776		c++;
 777		i++;
 778	}
 779	buf[c] = 0;
 780
 781	cgs.duelist3health = atoi ( buf );
 782}
 783
 784/*
 785================
 786CG_ConfigStringModified
 787
 788================
 789*/
 790extern int cgSiegeRoundState;
 791extern int cgSiegeRoundTime;
 792void CG_ParseSiegeObjectiveStatus(const char *str);
 793void CG_ParseWeatherEffect(const char *str);
 794extern void CG_ParseSiegeState(const char *str); //cg_main.c
 795extern int cg_beatingSiegeTime;
 796extern int cg_siegeWinTeam;
 797static void CG_ConfigStringModified( void ) {
 798	const char	*str;
 799	int		num;
 800
 801	num = atoi( CG_Argv( 1 ) );
 802
 803	// get the gamestate from the client system, which will have the
 804	// new configstring already integrated
 805	trap_GetGameState( &cgs.gameState );
 806
 807	// look up the individual string that was modified
 808	str = CG_ConfigString( num );
 809
 810	// do something with it if necessary
 811	if ( num == CS_MUSIC ) {
 812		CG_StartMusic( qtrue );
 813	} else if ( num == CS_SERVERINFO ) {
 814		CG_ParseServerinfo();
 815	} else if ( num == CS_WARMUP ) {
 816		CG_ParseWarmup();
 817	} else if ( num == CS_SCORES1 ) {
 818		cgs.scores1 = atoi( str );
 819	} else if ( num == CS_SCORES2 ) {
 820		cgs.scores2 = atoi( str );
 821	} else if ( num == CS_CLIENT_JEDIMASTER ) {
 822		cgs.jediMaster = atoi ( str );
 823	}
 824	else if ( num == CS_CLIENT_DUELWINNER )
 825	{
 826		cgs.duelWinner = atoi ( str );
 827	}
 828	else if ( num == CS_CLIENT_DUELISTS )
 829	{
 830		char buf[64];
 831		int c = 0;
 832		int i = 0;
 833
 834		while (str[i] && str[i] != '|')
 835		{
 836			buf[c] = str[i];
 837			c++;
 838			i++;
 839		}
 840		buf[c] = 0;
 841
 842		cgs.duelist1 = atoi ( buf );
 843		c = 0;
 844
 845		i++;
 846		while (str[i] && str[i] != '|')
 847		{
 848			buf[c] = str[i];
 849			c++;
 850			i++;
 851		}
 852		buf[c] = 0;
 853
 854		cgs.duelist2 = atoi ( buf );
 855
 856		if (str[i])
 857		{
 858			c = 0;
 859			i++;
 860
 861			while (str[i])
 862			{
 863				buf[c] = str[i];
 864				c++;
 865				i++;
 866			}
 867			buf[c] = 0;
 868
 869			cgs.duelist3 = atoi(buf);
 870		}
 871	}
 872	else if ( num == CS_CLIENT_DUELHEALTHS ) {	// nmckenzie: DUEL_HEALTH
 873		SetDuelistHealthsFromConfigString(str);
 874	}
 875	else if ( num == CS_LEVEL_START_TIME ) {
 876		cgs.levelStartTime = atoi( str );
 877	} else if ( num == CS_VOTE_TIME ) {
 878		cgs.voteTime = atoi( str );
 879		cgs.voteModified = qtrue;
 880	} else if ( num == CS_VOTE_YES ) {
 881		cgs.voteYes = atoi( str );
 882		cgs.voteModified = qtrue;
 883	} else if ( num == CS_VOTE_NO ) {
 884		cgs.voteNo = atoi( str );
 885		cgs.voteModified = qtrue;
 886	} else if ( num == CS_VOTE_STRING ) {
 887		Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) );
 888	} else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) {
 889		cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str );
 890		cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue;
 891	} else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) {
 892		cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str );
 893		cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue;
 894	} else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) {
 895		cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str );
 896		cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue;
 897	} else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) {
 898		Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) );
 899	} else if ( num == CS_INTERMISSION ) {
 900		cg.intermissionStarted = atoi( str );
 901	} else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) {
 902		char modelName[MAX_QPATH];
 903		strcpy(modelName, str);
 904		if (strstr(modelName, ".glm") || modelName[0] == '$')
 905		{ //Check to see if it has a custom skin attached.
 906			CG_HandleAppendedSkin(modelName);
 907			CG_CacheG2AnimInfo(modelName);
 908		}
 909
 910		if (modelName[0] != '$' && modelName[0] != '@')
 911		{ //don't register vehicle names and saber names as models.
 912			cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( modelName );
 913		}
 914		else
 915		{
 916            cgs.gameModels[ num-CS_MODELS ] = 0;
 917		}
 918// GHOUL2 Insert start
 919		/*
 920	} else if ( num >= CS_CHARSKINS && num < CS_CHARSKINS+MAX_CHARSKINS ) {
 921		cgs.skins[ num-CS_CHARSKINS ] = trap_R_RegisterSkin( str );
 922		*/
 923		//rww - removed and replaced with CS_G2BONES
 924// Ghoul2 Insert end
 925	} else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS && (str[0] || str[1] == '$')) {
 926		if ( str[0] != '*' ) {	// player specific sounds don't register here
 927			cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str );
 928		}
 929		else if (str[1] == '$')
 930		{ //an NPC soundset
 931			CG_PrecacheNPCSounds(str);
 932		}
 933	} else if ( num >= CS_EFFECTS && num < CS_EFFECTS+MAX_FX ) {
 934		if (str[0] == '*')
 935		{ //it's a special global weather effect
 936			CG_ParseWeatherEffect(str);
 937			cgs.gameEffects[ num-CS_EFFECTS] = 0;
 938		}
 939		else
 940		{
 941			cgs.gameEffects[ num-CS_EFFECTS] = trap_FX_RegisterEffect( str );
 942		}
 943	}
 944	else if ( num >= CS_SIEGE_STATE && num < CS_SIEGE_STATE+1 )
 945	{
 946		if (str[0])
 947		{
 948			CG_ParseSiegeState(str);
 949		}
 950	}
 951	else if ( num >= CS_SIEGE_WINTEAM && num < CS_SIEGE_WINTEAM+1 )
 952	{
 953		if (str[0])
 954		{
 955			cg_siegeWinTeam = atoi(str);
 956		}
 957	}
 958	else if ( num >= CS_SIEGE_OBJECTIVES && num < CS_SIEGE_OBJECTIVES+1 )
 959	{
 960		CG_ParseSiegeObjectiveStatus(str);
 961	}
 962	else if (num >= CS_SIEGE_TIMEOVERRIDE && num < CS_SIEGE_TIMEOVERRIDE+1)
 963	{
 964		cg_beatingSiegeTime = atoi(str);
 965		CG_SetSiegeTimerCvar ( cg_beatingSiegeTime );
 966	}
 967	else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS )
 968	{
 969		CG_NewClientInfo( num - CS_PLAYERS, qtrue);
 970		CG_BuildSpectatorString();
 971	} else if ( num == CS_FLAGSTATUS ) {
 972		if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) {
 973			// format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped
 974			int redflagId = str[0] - '0', blueflagId = str[1] - '0';
 975			//Raz: improved flag status remapping
 976			if ( redflagId >= 0 && redflagId < ARRAY_LEN( ctfFlagStatusRemap ) ) 
 977				cgs.redflag = ctfFlagStatusRemap[redflagId];
 978			if ( blueflagId >= 0 && blueflagId < ARRAY_LEN( ctfFlagStatusRemap ) )  
 979				cgs.blueflag = ctfFlagStatusRemap[blueflagId];
 980		}
 981	}
 982	else if ( num == CS_SHADERSTATE ) {
 983		CG_ShaderStateChanged();
 984	}
 985	else if ( num >= CS_LIGHT_STYLES && num < CS_LIGHT_STYLES + (MAX_LIGHT_STYLES * 3))
 986	{
 987		CG_SetLightstyle(num - CS_LIGHT_STYLES);
 988	}
 989		
 990}
 991
 992//frees all ghoul2 stuff and npc stuff from a centity -rww
 993void CG_KillCEntityG2(int entNum)
 994{
 995	int j;
 996	clientInfo_t *ci = NULL;
 997	centity_t *cent = &cg_entities[entNum];
 998
 999	if (entNum < MAX_CLIENTS)
1000	{
1001		ci = &cgs.clientinfo[entNum];
1002	}
1003	else
1004	{
1005		ci = cent->npcClient;
1006	}
1007
1008	if (ci)
1009	{
1010		if (ci == cent->npcClient)
1011		{ //never going to be != cent->ghoul2, unless cent->ghoul2 has already been removed (and then this ptr is not valid)
1012			ci->ghoul2Model = NULL;
1013		}
1014		else if (ci->ghoul2Model == cent->ghoul2)
1015		{
1016			ci->ghoul2Model = NULL;
1017		}
1018		else if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model))
1019		{
1020			trap_G2API_CleanGhoul2Models(&ci->ghoul2Model);
1021			ci->ghoul2Model = NULL;
1022		}
1023
1024		//Clean up any weapon instances for custom saber stuff
1025		j = 0;
1026		while (j < MAX_SABERS)
1027		{
1028			if (ci->ghoul2Weapons[j] && trap_G2_HaveWeGhoul2Models(ci->ghoul2Weapons[j]))
1029			{
1030				trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[j]);
1031				ci->ghoul2Weapons[j] = NULL;
1032			}
1033
1034			j++;
1035		}
1036	}
1037
1038	if (cent->ghoul2 && trap_G2_HaveWeGhoul2Models(cent->ghoul2))
1039	{
1040		trap_G2API_CleanGhoul2Models(&cent->ghoul2);
1041		cent->ghoul2 = NULL;
1042	}
1043
1044	if (cent->grip_arm && trap_G2_HaveWeGhoul2Models(cent->grip_arm))
1045	{
1046		trap_G2API_CleanGhoul2Models(&cent->grip_arm);
1047		cent->grip_arm = NULL;
1048	}
1049
1050	if (cent->frame_hold && trap_G2_HaveWeGhoul2Models(cent->frame_hold))
1051	{
1052		trap_G2API_CleanGhoul2Models(&cent->frame_hold);
1053		cent->frame_hold = NULL;
1054	}
1055
1056	if (cent->npcClient)
1057	{
1058		CG_DestroyNPCClient(&cent->npcClient);
1059	}
1060
1061	cent->isRagging = qfalse; //just in case.
1062	cent->ikStatus = qfalse;
1063
1064	cent->localAnimIndex = 0;
1065}
1066
1067void CG_KillCEntityInstances(void)
1068{
1069	int i = 0;
1070	centity_t *cent;
1071
1072	while (i < MAX_GENTITIES)
1073	{
1074		cent = &cg_entities[i];
1075
1076		if (i >= MAX_CLIENTS && cent->currentState.number == i)
1077		{ //do not clear G2 instances on client ents, they are constant
1078			CG_KillCEntityG2(i);
1079		}
1080
1081		cent->bolt1 = 0;
1082		cent->bolt2 = 0;
1083		cent->bolt3 = 0;
1084		cent->bolt4 = 0;
1085
1086		cent->bodyHeight = 0;//SABER_LENGTH_MAX;
1087		//cent->saberExtendTime = 0;
1088
1089		cent->boltInfo = 0;
1090
1091		cent->frame_minus1_refreshed = 0;
1092		cent->frame_minus2_refreshed = 0;
1093		cent->dustTrailTime = 0;
1094		cent->ghoul2weapon = NULL;
1095		//cent->torsoBolt = 0;
1096		cent->trailTime = 0;
1097		cent->frame_hold_time = 0;
1098		cent->frame_hold_refreshed = 0;
1099		cent->trickAlpha = 0;
1100		cent->trickAlphaTime = 0;
1101		VectorClear(cent->turAngles);
1102		cent->weapon = 0;
1103		cent->teamPowerEffectTime = 0;
1104		cent->teamPowerType = 0;
1105		cent->numLoopingSounds = 0;
1106
1107		cent->localAnimIndex = 0;
1108
1109		i++;
1110	}
1111}
1112
1113/*
1114===============
1115CG_MapRestart
1116
1117The server has issued a map_restart, so the next snapshot
1118is completely new and should not be interpolated to.
1119
1120A tournement restart will clear everything, but doesn't
1121require a reload of all the media
1122===============
1123*/
1124static void CG_MapRestart( void ) {
1125	if ( cg_showMiss.integer ) {
1126		CG_Printf( "CG_MapRestart\n" );
1127	}
1128
1129	trap_R_ClearDecals ( );
1130	//FIXME: trap_FX_Reset?
1131
1132	CG_InitLocalEntities();
1133	CG_InitMarkPolys();
1134	CG_ClearParticles ();
1135	CG_KillCEntityInstances();
1136
1137	// make sure the "3 frags left" warnings play again
1138	cg.fraglimitWarnings = 0;
1139
1140	cg.timelimitWarnings = 0;
1141
1142	cg.intermissionStarted = qfalse;
1143
1144	cgs.voteTime = 0;
1145
1146	cg.mapRestart = qtrue;
1147
1148	CG_StartMusic(qtrue);
1149
1150	trap_S_ClearLoopingSounds();
1151
1152	// we really should clear more parts of cg here and stop sounds
1153
1154	// play the "fight" sound if this is a restart without warmup
1155	if ( cg.warmup == 0 && cgs.gametype != GT_SIEGE && cgs.gametype != GT_POWERDUEL/* && cgs.gametype == GT_DUEL */) {
1156		if (!(mov_soundDisable.integer & SDISABLE_ANNOUNCER)) {
1157			trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER );
1158		}
1159		CG_CenterPrint( CG_GetStringEdString("MP_SVGAME", "BEGIN_DUEL"), 120, GIANTCHAR_WIDTH*2 );
1160	}
1161	/*
1162	if (cg_singlePlayerActive.integer) {
1163		trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time));
1164		if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) {
1165			trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string));
1166		}
1167	}
1168	*/
1169//	trap_Cvar_Set("cg_thirdPerson", "0");
1170}
1171
1172/*
1173=================
1174CG_RemoveChatEscapeChar
1175=================
1176*/
1177static void CG_RemoveChatEscapeChar( char *text ) {
1178	int i, l;
1179
1180	l = 0;
1181	for ( i = 0; text[i]; i++ ) {
1182		if (text[i] == '\x19')
1183			continue;
1184		text[l++] = text[i];
1185	}
1186	text[l] = '\0';
1187}
1188
1189#define MAX_STRINGED_SV_STRING 1024	// this is an quake-engine limit, not a StringEd limit
1190
1191void CG_CheckSVStringEdRef(char *buf, const char *str)
1192{ //I don't really like doing this. But it utilizes the system that was already in place.
1193	int i = 0;
1194	int b = 0;
1195	int strLen = 0;
1196	qboolean gotStrip = qfalse;
1197
1198	if (!str || !str[0])
1199	{
1200		if (str)
1201		{
1202			strcpy(buf, str);
1203		}
1204		return;
1205	}
1206
1207	strcpy(buf, str);
1208
1209	strLen = strlen(str);
1210
1211	if (strLen >= MAX_STRINGED_SV_STRING)
1212	{
1213		return;
1214	}
1215
1216	while (i < strLen && str[i])
1217	{
1218		gotStrip = qfalse;
1219
1220		if (str[i] == '@' && (i+1) < strLen)
1221		{
1222			if (str[i+1] == '@' && (i+2) < strLen)
1223			{
1224				if (str[i+2] == '@' && (i+3) < strLen)
1225				{ //@@@ should mean to insert a StringEd reference here, so insert it into buf at the current place
1226					char stringRef[MAX_STRINGED_SV_STRING];
1227					int r = 0;
1228
1229					while (i < strLen && str[i] == '@')
1230					{
1231						i++;
1232					}
1233
1234					while (i < strLen && str[i] && str[i] != ' ' && str[i] != ':' && str[i] != '.' && str[i] != '\n')
1235					{
1236						stringRef[r] = str[i];
1237						r++;
1238						i++;
1239					}
1240					stringRef[r] = 0;
1241
1242					buf[b] = 0;
1243					Q_strcat(buf, MAX_STRINGED_SV_STRING, CG_GetStringEdString("MP_SVGAME", stringRef));
1244					b = strlen(buf);
1245				}
1246			}
1247		}
1248
1249		if (!gotStrip)
1250		{
1251			buf[b] = str[i];
1252			b++;
1253		}
1254		i++;
1255	}
1256
1257	buf[b] = 0;
1258}
1259
1260static void CG_BodyQueueCopy(centity_t *cent, int clientNum, int knownWeapon)
1261{
1262	centity_t		*source;
1263	animation_t		*anim;
1264	float			animSpeed;
1265	int				flags=BONE_ANIM_OVERRIDE_FREEZE;
1266	clientInfo_t	*ci;
1267
1268	if (cent->ghoul2)
1269	{
1270		trap_G2API_CleanGhoul2Models(&cent->ghoul2);
1271	}
1272
1273	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
1274	{
1275		return;
1276	}
1277
1278	source = &cg_entities[ clientNum ];
1279	ci = &cgs.clientinfo[ clientNum ];
1280
1281	if (!source)
1282	{
1283		return;
1284	}
1285
1286	if (!source->ghoul2)
1287	{
1288		return;
1289	}
1290
1291	cent->isRagging = qfalse; //reset in case it's still set from another body that was in this cent slot.
1292	cent->ownerRagging = source->isRagging; //if the owner was in ragdoll state, then we want to go into it too right away.
1293
1294#if 0
1295	VectorCopy(source->lerpOriginOffset, cent->lerpOriginOffset);
1296#endif
1297
1298	cent->bodyFadeTime = 0;
1299	cent->bodyHeight = 0;
1300
1301	cent->dustTrailTime = source->dustTrailTime;
1302
1303	trap_G2API_DuplicateGhoul2Instance(source->ghoul2, &cent->ghoul2);
1304
1305	if (source->isRagging)
1306	{ //just reset it now.
1307		source->isRagging = qfalse;
1308		trap_G2API_SetRagDoll(source->ghoul2, NULL); //calling with null parms resets to no ragdoll.
1309	}
1310
1311	//either force the weapon from when we died or remove it if it was a dropped weapon
1312	if (knownWeapon > WP_BRYAR_PISTOL && trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1))
1313	{
1314		trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1);
1315	}
1316	else if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1))
1317	{
1318		trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, knownWeapon), 0, cent->ghoul2, 1);
1319	}
1320
1321	if (!cent->ownerRagging)
1322	{
1323		int aNum;
1324		int eFrame;
1325		qboolean fallBack = qfalse;
1326
1327		//anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.torsoAnim ];
1328		if (!BG_InDeathAnim(source->currentState.torsoAnim))
1329		{ //then just snap the corpse into a default
1330			anim = &bgAllAnims[source->localAnimIndex].anims[ BOTH_DEAD1 ];
1331			fallBack = qtrue;
1332		}
1333		else
1334		{
1335			anim = &bgAllAnims[source->localAnimIndex].anims[ source->currentState.torsoAnim ];
1336		}
1337		animSpeed = 50.0f / anim->frameLerp;
1338
1339		if (!fallBack)
1340		{
1341			//this will just set us to the last frame of the animation, in theory
1342			aNum = cgs.clientinfo[source->currentState.number].frame+1;
1343
1344			while (aNum >= anim->firstFrame+anim->numFrames)
1345			{
1346				aNum--;
1347			}
1348
1349			if (aNum < anim->firstFrame-1)
1350			{ //wrong animation...?
1351				aNum = (anim->firstFrame+anim->numFrames)-1;
1352			}
1353		}
1354		else
1355		{
1356			aNum = anim->firstFrame;
1357		}
1358
1359		eFrame = anim->firstFrame + anim->numFrames;
1360
1361		//if (!cgs.clientinfo[source->currentState.number].frame || (cent->currentState.torsoAnim) != (source->currentState.torsoAnim) )
1362		//{
1363		//	aNum = (anim->firstFrame+anim->numFrames)-1;
1364		//}
1365
1366		trap_G2API_SetBoneAnim(cent->ghoul2, 0, "upper_lumbar", aNum, eFrame, flags, animSpeed, cg.time, -1, 150);
1367		trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", aNum, eFrame, flags, animSpeed, cg.time, -1, 150);
1368		trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", aNum, eFrame, flags, animSpeed, cg.time, -1, 150);
1369	}
1370
1371	//After we create the bodyqueue, regenerate any limbs on the real instance
1372	if (source->torsoBolt)
1373	{
1374		CG_ReattachLimb(source);
1375	}
1376}
1377
1378/*
1379=================
1380CG_ServerCommand
1381
1382The string has been tokenized and can be retrieved with
1383Cmd_Argc() / Cmd_Argv()
1384=================
1385*/
1386void CG_SiegeBriefingDisplay(int team, int dontshow);
1387void CG_ParseSiegeExtendedData(void);
1388extern void CG_ChatBox_AddString(char *chatStr); //cg_draw.c
1389static void CG_ServerCommand( void ) {
1390	const char	*cmd;
1391	char		text[MAX_SAY_TEXT];
1392	qboolean	IRCG = qfalse;
1393
1394	cmd = CG_Argv(0);
1395
1396	if ( !cmd[0] ) {
1397		// server claimed the command
1398		return;
1399	}
1400
1401#if 0
1402	// never seems to get used -Ste
1403	if ( !strcmp( cmd, "spd" ) ) 
1404	{
1405		const char *ID;
1406		int holdInt,count,i;
1407		char string[1204];
1408
1409		count = trap_Argc();
1410
1411		ID =  CG_Argv(1);
1412		holdInt = atoi(ID);
1413
1414		memset( &string, 0, sizeof( string ) );
1415
1416		Com_sprintf( string,sizeof(string)," \"%s\"", (const char *) CG_Argv(2));
1417
1418		for (i=3;i<count;i++)
1419		{
1420			Com_sprintf( string,sizeof(string)," %s \"%s\"", string, (const char *) CG_Argv(i));
1421		}
1422
1423		trap_SP_Print(holdInt, (byte *)string);
1424		return;
1425	}
1426#endif
1427
1428	if (!strcmp(cmd, "sxd"))
1429	{ //siege extended data, contains extra info certain classes may want to know about other clients
1430        CG_ParseSiegeExtendedData();
1431		return;
1432	}
1433
1434	if (!strcmp(cmd, "sb"))
1435	{ //siege briefing display
1436		CG_SiegeBriefingDisplay(atoi(CG_Argv(1)), 0);
1437		return;
1438	}
1439
1440	if ( !strcmp( cmd, "scl" ) )
1441	{
1442		//if (!( trap_Key_GetCatcher() & KEYCATCH_UI ))
1443		//Well, I want it to come up even if the briefing display is up.
1444		{
1445			trap_OpenUIMenu(UIMENU_CLASSSEL); //UIMENU_CLASSSEL
1446		}
1447		return;
1448	}
1449
1450	if ( !strcmp( cmd, "spc" ) )
1451	{
1452		if (cg.demoPlayback)
1453			return;
1454		trap_Cvar_Set("ui_myteam", "3");
1455		trap_OpenUIMenu(UIMENU_PLAYERCONFIG); //UIMENU_CLASSSEL
1456		return;
1457	}
1458
1459	if ( !strcmp( cmd, "nfr" ) )
1460	{ //"nfr" == "new force rank" (want a short string)
1461		int doMenu = 0;
1462		int setTeam = 0;
1463		int newRank = 0;
1464
1465		if (trap_Argc() < 3)
1466		{
1467#ifdef _DEBUG
1468			Com_Printf("WARNING: Invalid newForceRank string\n");
1469#endif
1470			return;
1471		}
1472
1473		newRank = atoi(CG_Argv(1));
1474		doMenu = atoi(CG_Argv(2));
1475		setTeam = atoi(CG_Argv(3));
1476
1477		trap_Cvar_Set("ui_rankChange", va("%i", newRank));
1478
1479		trap_Cvar_Set("ui_myteam", va("%i", setTeam));
1480
1481		if (!( trap_Key_GetCatcher() & KEYCATCH_UI ) && doMenu && !cg.demoPlayback)
1482		{
1483			trap_OpenUIMenu(UIMENU_PLAYERCONFIG);
1484		}
1485
1486		return;
1487	}
1488
1489	if ( !strcmp( cmd, "kg2" ) )
1490	{ //Kill a ghoul2 instance in this slot.
1491	  //If it has been occupied since this message was sent somehow, the worst that can (should) happen
1492	  //is the instance will have to reinit with its current info.
1493		int indexNum = 0;
1494		int argNum = trap_Argc();
1495		int i = 1;
1496		
1497		if (argNum < 1)
1498		{
1499			return;
1500		}
1501
1502		while (i < argNum)
1503		{
1504			indexNum = atoi(CG_Argv(i));
1505
1506			if (cg_entities[indexNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[indexNum].ghoul2))
1507			{
1508				if (indexNum < MAX_CLIENTS)
1509				{ //You try to do very bad thing!
1510#ifdef _DEBUG
1511					Com_Printf("WARNING: Tried to kill a client ghoul2 instance with a kg2 command!\n");
1512#endif
1513					return;
1514				}
1515
1516				CG_KillCEntityG2(indexNum);
1517			}
1518
1519			i++;
1520		}
1521		
1522		return;
1523	}
1524
1525	if (!strcmp(cmd, "kls"))
1526	{ //kill looping sounds
1527		int indexNum = 0;
1528		int argNum = trap_Argc();
1529		centity_t *clent = NULL;
1530		centity_t *trackerent = NULL;
1531		
1532		if (argNum < 1)
1533		{
1534			assert(0);
1535			return;
1536		}
1537
1538		indexNum = atoi(CG_Argv(1));
1539
1540		if (indexNum != -1)
1541		{
1542			clent = &cg_entities[indexNum];
1543		}
1544
1545		if (argNum >= 2)
1546		{
1547			indexNum = atoi(CG_Argv(2));
1548
1549			if (indexNum != -1)
1550			{
1551				trackerent = &cg_entities[indexNum];
1552			}
1553		}
1554
1555		if (clent)
1556		{
1557			CG_S_StopLoopingSound(clent->currentState.number, -1);
1558		}
1559		if (trackerent)
1560		{
1561			CG_S_StopLoopingSound(trackerent->currentState.number, -1);
1562		}
1563
1564		return;
1565	}
1566
1567	if (!strcmp(cmd, "ircg"))
1568	{ //this means param 2 is the body index and we want to copy to bodyqueue on it
1569		IRCG = qtrue;
1570	}
1571
1572	if (!strcmp(cmd, "rcg") || IRCG)
1573	{ //rcg - Restore Client Ghoul (make sure limbs are reattached and ragdoll state is reset - this must be done reliably)
1574		int indexNum = 0;
1575		int argNum = trap_Argc();
1576		centity_t *clent;
1577		
1578		if (argNum < 1)
1579		{
1580			assert(0);
1581			return;
1582		}
1583
1584		indexNum = atoi(CG_Argv(1));
1585		if (indexNum < 0 || indexNum >= MAX_CLIENTS)
1586		{
1587			assert(0);
1588			return;
1589		}
1590
1591		clent = &cg_entities[indexNum];
1592
1593		//assert(clent->ghoul2);
1594		if (!clent->ghoul2)
1595		{ //this can happen while connecting as a client
1596			return;
1597		}
1598
1599#ifdef _DEBUG
1600		if (!trap_G2_HaveWeGhoul2Models(clent->ghoul2))
1601		{
1602			assert(!"Tried to reset state on a bad instance. Crash is inevitable.");
1603		}
1604#endif
1605
1606		if (IRCG)
1607		{
1608			int bodyIndex = 0;
1609			int weaponIndex = 0;
1610			int side = 0;
1611			centity_t *body;
1612
1613			assert(argNum >= 3);
1614			bodyIndex = atoi(CG_Argv(2));
1615			weaponIndex = atoi(CG_Argv(3));
1616			side = atoi(CG_Argv(4));
1617
1618			body = &cg_entities[bodyIndex];
1619
1620			if (side)
1621			{
1622				body->teamPowerType = qtrue; //light side
1623			}
1624			else
1625			{
1626				body->teamPowerType = qfalse; //dark side
1627			}
1628
1629			CG_BodyQueueCopy(body, clent->currentState.number, weaponIndex);
1630		}
1631
1632		//reattach any missing limbs
1633		if (clent->torsoBolt)
1634		{
1635			CG_ReattachLimb(clent);
1636		}
1637
1638		//make sure ragdoll state is reset
1639		if (clent->isRagging)
1640		{
1641			clent->isRagging = qfalse;
1642			trap_G2API_SetRagDoll(clent->ghoul2, NULL); //calling with null parms resets to no ragdoll.
1643		}
1644		
1645		//clear all the decals as well
1646		trap_G2API_ClearSkinGore(clent->ghoul2);
1647
1648		clent->weapon = 0;
1649		clent->ghoul2weapon = NULL; //force a weapon reinit
1650
1651		return;
1652	}
1653
1654	if ( !strcmp( cmd, "cp" ) ) {
1655		char strEd[MAX_STRINGED_SV_STRING];
1656		CG_CheckSVStringEdRef(strEd, CG_Argv(1));
1657		CG_CenterPrint( strEd, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
1658		return;
1659	}
1660
1661	if ( !strcmp( cmd, "cps" ) ) {
1662		char strEd[MAX_STRINGED_SV_STRING];
1663		char *x = (char *)CG_Argv(1);
1664		if (x[0] == '@')
1665		{
1666			x++;
1667		}
1668		trap_SP_GetStringTextString(x, strEd, MAX_STRINGED_SV_STRING);
1669		CG_CenterPrint( strEd, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
1670		return;
1671	}
1672
1673	if ( !strcmp( cmd, "cs" ) ) {
1674		CG_ConfigStringModified();
1675		return;
1676	}
1677
1678	if ( !strcmp( cmd, "print" ) ) {
1679		char strEd[MAX_STRINGED_SV_STRING];
1680		CG_CheckSVStringEdRef(strEd, CG_Argv(1));
1681		CG_Printf( "%s", strEd );
1682		return;
1683	}
1684
1685	if ( !strcmp( cmd, "chat" ) ) {
1686		if ( !cg_teamChatsOnly.integer ) {
1687			if (!(mov_soundDisable.integer & SDISABLE_CHAT) && mov_chatBeep.integer) {
1688				trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
1689			}
1690			Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
1691			CG_RemoveChatEscapeChar( text );
1692			CG_ChatBox_AddString(text);
1693			CG_Printf( "*%s\n", text );
1694		}
1695		return;
1696	}
1697
1698	if ( !strcmp( cmd, "tchat" ) ) {
1699		if (!(mov_soundDisable.integer & SDISABLE_CHAT) && mov_chatBeep.integer) {
1700			trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
1701		}
1702		Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
1703		CG_RemoveChatEscapeChar( text );
1704		CG_ChatBox_AddString(text);
1705		CG_Printf( "*%s\n", text );
1706
1707		return;
1708	}
1709
1710	//chat with location, possibly localized.
1711	if ( !strcmp( cmd, "lchat" ) ) {
1712		if ( !cg_teamChatsOnly.integer ) {
1713			char name[MAX_STRING_CHARS];
1714			char loc[MAX_STRING_CHARS];
1715			char color[8];
1716			char message[MAX_STRING_CHARS];
1717
1718			if (trap_Argc() < 4)
1719			{
1720				return;
1721			}
1722
1723			Q_strncpyz( name, CG_Argv( 1 ), sizeof( name ) );
1724			Q_strncpyz( loc, CG_Argv( 2 ), sizeof( loc ) );
1725			Q_strncpyz( color, CG_Argv( 3 ), sizeof( color ) );
1726			Q_strncpyz( message, CG_Argv( 4 ), sizeof( message ) );
1727
1728			if (loc[0] == '@')
1729			{ //get localized text
1730				trap_SP_GetStringTextString(loc+1, loc, sizeof( loc ) );
1731			}
1732			if (!(mov_soundDisable.integer & SDISABLE_CHAT) && mov_chatBeep.integer)
1733				trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
1734			//Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
1735			Com_sprintf(text, sizeof( text ), "%s^7<%s> ^%s%s", name, loc, color, message);
1736			CG_RemoveChatEscapeChar( text );
1737			CG_ChatBox_AddString(text);
1738			CG_Printf( "*%s\n", text );
1739		}
1740		return;
1741	}
1742	if ( !strcmp( cmd, "ltchat" ) ) {
1743		char name[MAX_STRING_CHARS];
1744		char loc[MAX_STRING_CHARS];
1745		char color[8];
1746		char message[MAX_STRING_CHARS];
1747
1748		if (trap_Argc() < 4)
1749		{
1750			return;
1751		}
1752
1753		Q_strncpyz( name, CG_Argv( 1 ), sizeof( name ) );
1754		Q_strncpyz( loc, CG_Argv( 2 ), sizeof( loc ) );
1755		Q_strncpyz( color, CG_Argv( 3 ), sizeof( color ) );
1756		Q_strncpyz( message, CG_Argv( 4 ), sizeof( message ) );
1757
1758		if (loc[0] == '@')
1759		{ //get localized text
1760			trap_SP_GetStringTextString(loc+1, loc, sizeof( loc ) );
1761		}
1762		if (!(mov_soundDisable.integer & SDISABLE_CHAT) && mov_chatBeep.integer)
1763			trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
1764		//Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
1765		Com_sprintf(text, sizeof( text ), "%s^7<%s> ^%s%s", name, loc, color, message);
1766		CG_RemoveChatEscapeChar( text );
1767		CG_ChatBox_AddString(text);
1768		CG_Printf( "*%s\n", text );
1769
1770		return;
1771	}
1772
1773	if ( !strcmp( cmd, "scores" ) ) {
1774		CG_ParseScores();
1775		return;
1776	}
1777
1778	if ( !strcmp( cmd, "tinfo" ) ) {
1779		CG_ParseTeamInfo();
1780		return;
1781	}
1782
1783	if ( !strcmp( cmd, "map_restart" ) ) {
1784		CG_MapRestart();
1785		return;
1786	}
1787
1788	//Raz: Buffer overflow fix
1789#if 0
1790	if ( Q_stricmp (cmd, "remapShader") == 0 ) {
1791		if (trap_Argc() == 4) {
1792			trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3));
1793		}
1794	}
1795#else
1796	if ( !Q_stricmp( cmd, "remapShader" ) ) {
1797		if ( trap_Argc() == 4 ) {
1798			char shader1[MAX_QPATH];
1799			char shader2[MAX_QPATH];
1800			Q_strncpyz( shader1, CG_Argv( 1 ), sizeof( shader1 ) );
1801			Q_strncpyz( shader2, CG_Argv( 2 ), sizeof( shader2 ) );
1802			trap_R_RemapShader( shader1, shader2, CG_Argv( 3 ) );
1803			return;
1804		}
1805		return;
1806	}
1807#endif
1808
1809	// loaddeferred can be both a servercmd and a consolecmd
1810	if ( !strcmp( cmd, "loaddefered" ) ) {	// FIXME: spelled wrong, but not changing for demo
1811		CG_LoadDeferredPlayers();
1812		return;
1813	}
1814
1815	// clientLevelShot is sent before taking a special screenshot for
1816	// the menu system during development
1817	if ( !strcmp( cmd, "clientLevelShot" ) ) {
1818		cg.levelShot = qtrue;
1819		return;
1820	}
1821
1822	CG_Printf( "Unknown client game command: %s\n", cmd );
1823}
1824
1825
1826/*
1827====================
1828CG_ExecuteNewServerCommands
1829
1830Execute all of the server commands that were received along
1831with this this snapshot.
1832====================
1833*/
1834void CG_ExecuteNewServerCommands( int latestSequence ) {
1835	while ( cgs.serverCommandSequence < latestSequence ) {
1836		if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) {
1837			CG_ServerCommand();
1838		}
1839	}
1840}