Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

cg_servercmds.c (29352B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 //
     23 // cg_servercmds.c -- reliably sequenced text commands sent by the server
     24 // these are processed at snapshot transition time, so there will definately
     25 // be a valid snapshot this frame
     26 
     27 #include "cg_local.h"
     28 #include "../../ui/menudef.h" // bk001205 - for Q3_ui as well
     29 
     30 typedef struct {
     31 	const char *order;
     32 	int taskNum;
     33 } orderTask_t;
     34 
     35 static const orderTask_t validOrders[] = {
     36 	{ VOICECHAT_GETFLAG,						TEAMTASK_OFFENSE },
     37 	{ VOICECHAT_OFFENSE,						TEAMTASK_OFFENSE },
     38 	{ VOICECHAT_DEFEND,							TEAMTASK_DEFENSE },
     39 	{ VOICECHAT_DEFENDFLAG,					TEAMTASK_DEFENSE },
     40 	{ VOICECHAT_PATROL,							TEAMTASK_PATROL },
     41 	{ VOICECHAT_CAMP,								TEAMTASK_CAMP },
     42 	{ VOICECHAT_FOLLOWME,						TEAMTASK_FOLLOW },
     43 	{ VOICECHAT_RETURNFLAG,					TEAMTASK_RETRIEVE },
     44 	{ VOICECHAT_FOLLOWFLAGCARRIER,	TEAMTASK_ESCORT }
     45 };
     46 
     47 static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t);
     48 
     49 #ifdef MISSIONPACK // bk001204
     50 static int CG_ValidOrder(const char *p) {
     51 	int i;
     52 	for (i = 0; i < numValidOrders; i++) {
     53 		if (Q_stricmp(p, validOrders[i].order) == 0) {
     54 			return validOrders[i].taskNum;
     55 		}
     56 	}
     57 	return -1;
     58 }
     59 #endif
     60 
     61 /*
     62 =================
     63 CG_ParseScores
     64 
     65 =================
     66 */
     67 static void CG_ParseScores( void ) {
     68 	int		i, powerups;
     69 
     70 	cg.numScores = atoi( CG_Argv( 1 ) );
     71 	if ( cg.numScores > MAX_CLIENTS ) {
     72 		cg.numScores = MAX_CLIENTS;
     73 	}
     74 
     75 	cg.teamScores[0] = atoi( CG_Argv( 2 ) );
     76 	cg.teamScores[1] = atoi( CG_Argv( 3 ) );
     77 
     78 	memset( cg.scores, 0, sizeof( cg.scores ) );
     79 	for ( i = 0 ; i < cg.numScores ; i++ ) {
     80 		//
     81 		cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) );
     82 		cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) );
     83 		cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) );
     84 		cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) );
     85 		cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) );
     86 		powerups = atoi( CG_Argv( i * 14 + 9 ) );
     87 		cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10));
     88 		cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11));
     89 		cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12));
     90 		cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13));
     91 		cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14));
     92 		cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15));
     93 		cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16));
     94 		cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17));
     95 
     96 		if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) {
     97 			cg.scores[i].client = 0;
     98 		}
     99 		cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score;
    100 		cgs.clientinfo[ cg.scores[i].client ].powerups = powerups;
    101 
    102 		cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team;
    103 	}
    104 #ifdef MISSIONPACK
    105 	CG_SetScoreSelection(NULL);
    106 #endif
    107 
    108 }
    109 
    110 /*
    111 =================
    112 CG_ParseTeamInfo
    113 
    114 =================
    115 */
    116 static void CG_ParseTeamInfo( void ) {
    117 	int		i;
    118 	int		client;
    119 
    120 	numSortedTeamPlayers = atoi( CG_Argv( 1 ) );
    121 
    122 	for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) {
    123 		client = atoi( CG_Argv( i * 6 + 2 ) );
    124 
    125 		sortedTeamPlayers[i] = client;
    126 
    127 		cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) );
    128 		cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) );
    129 		cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) );
    130 		cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) );
    131 		cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) );
    132 	}
    133 }
    134 
    135 
    136 /*
    137 ================
    138 CG_ParseServerinfo
    139 
    140 This is called explicitly when the gamestate is first received,
    141 and whenever the server updates any serverinfo flagged cvars
    142 ================
    143 */
    144 void CG_ParseServerinfo( void ) {
    145 	const char	*info;
    146 	char	*mapname;
    147 
    148 	info = CG_ConfigString( CS_SERVERINFO );
    149 	cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) );
    150 	trap_Cvar_Set("g_gametype", va("%i", cgs.gametype));
    151 	cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) );
    152 	cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) );
    153 	cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) );
    154 	cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) );
    155 	cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) );
    156 	cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
    157 	mapname = Info_ValueForKey( info, "mapname" );
    158 	Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname );
    159 	Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) );
    160 	trap_Cvar_Set("g_redTeam", cgs.redTeam);
    161 	Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) );
    162 	trap_Cvar_Set("g_blueTeam", cgs.blueTeam);
    163 }
    164 
    165 /*
    166 ==================
    167 CG_ParseWarmup
    168 ==================
    169 */
    170 static void CG_ParseWarmup( void ) {
    171 	const char	*info;
    172 	int			warmup;
    173 
    174 	info = CG_ConfigString( CS_WARMUP );
    175 
    176 	warmup = atoi( info );
    177 	cg.warmupCount = -1;
    178 
    179 	if ( warmup == 0 && cg.warmup ) {
    180 
    181 	} else if ( warmup > 0 && cg.warmup <= 0 ) {
    182 #ifdef MISSIONPACK
    183 		if (cgs.gametype >= GT_CTF && cgs.gametype <= GT_HARVESTER) {
    184 			trap_S_StartLocalSound( cgs.media.countPrepareTeamSound, CHAN_ANNOUNCER );
    185 		} else
    186 #endif
    187 		{
    188 			trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER );
    189 		}
    190 	}
    191 
    192 	cg.warmup = warmup;
    193 }
    194 
    195 /*
    196 ================
    197 CG_SetConfigValues
    198 
    199 Called on load to set the initial values from configure strings
    200 ================
    201 */
    202 void CG_SetConfigValues( void ) {
    203 	const char *s;
    204 
    205 	cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) );
    206 	cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) );
    207 	cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
    208 	if( cgs.gametype == GT_CTF ) {
    209 		s = CG_ConfigString( CS_FLAGSTATUS );
    210 		cgs.redflag = s[0] - '0';
    211 		cgs.blueflag = s[1] - '0';
    212 	}
    213 #ifdef MISSIONPACK
    214 	else if( cgs.gametype == GT_1FCTF ) {
    215 		s = CG_ConfigString( CS_FLAGSTATUS );
    216 		cgs.flagStatus = s[0] - '0';
    217 	}
    218 #endif
    219 	cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
    220 }
    221 
    222 /*
    223 =====================
    224 CG_ShaderStateChanged
    225 =====================
    226 */
    227 void CG_ShaderStateChanged(void) {
    228 	char originalShader[MAX_QPATH];
    229 	char newShader[MAX_QPATH];
    230 	char timeOffset[16];
    231 	const char *o;
    232 	char *n,*t;
    233 
    234 	o = CG_ConfigString( CS_SHADERSTATE );
    235 	while (o && *o) {
    236 		n = strstr(o, "=");
    237 		if (n && *n) {
    238 			strncpy(originalShader, o, n-o);
    239 			originalShader[n-o] = 0;
    240 			n++;
    241 			t = strstr(n, ":");
    242 			if (t && *t) {
    243 				strncpy(newShader, n, t-n);
    244 				newShader[t-n] = 0;
    245 			} else {
    246 				break;
    247 			}
    248 			t++;
    249 			o = strstr(t, "@");
    250 			if (o) {
    251 				strncpy(timeOffset, t, o-t);
    252 				timeOffset[o-t] = 0;
    253 				o++;
    254 				trap_R_RemapShader( originalShader, newShader, timeOffset );
    255 			}
    256 		} else {
    257 			break;
    258 		}
    259 	}
    260 }
    261 
    262 /*
    263 ================
    264 CG_ConfigStringModified
    265 
    266 ================
    267 */
    268 static void CG_ConfigStringModified( void ) {
    269 	const char	*str;
    270 	int		num;
    271 
    272 	num = atoi( CG_Argv( 1 ) );
    273 
    274 	// get the gamestate from the client system, which will have the
    275 	// new configstring already integrated
    276 	trap_GetGameState( &cgs.gameState );
    277 
    278 	// look up the individual string that was modified
    279 	str = CG_ConfigString( num );
    280 
    281 	// do something with it if necessary
    282 	if ( num == CS_MUSIC ) {
    283 		CG_StartMusic();
    284 	} else if ( num == CS_SERVERINFO ) {
    285 		CG_ParseServerinfo();
    286 	} else if ( num == CS_WARMUP ) {
    287 		CG_ParseWarmup();
    288 	} else if ( num == CS_SCORES1 ) {
    289 		cgs.scores1 = atoi( str );
    290 	} else if ( num == CS_SCORES2 ) {
    291 		cgs.scores2 = atoi( str );
    292 	} else if ( num == CS_LEVEL_START_TIME ) {
    293 		cgs.levelStartTime = atoi( str );
    294 	} else if ( num == CS_VOTE_TIME ) {
    295 		cgs.voteTime = atoi( str );
    296 		cgs.voteModified = qtrue;
    297 	} else if ( num == CS_VOTE_YES ) {
    298 		cgs.voteYes = atoi( str );
    299 		cgs.voteModified = qtrue;
    300 	} else if ( num == CS_VOTE_NO ) {
    301 		cgs.voteNo = atoi( str );
    302 		cgs.voteModified = qtrue;
    303 	} else if ( num == CS_VOTE_STRING ) {
    304 		Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) );
    305 #ifdef MISSIONPACK
    306 		trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER );
    307 #endif //MISSIONPACK
    308 	} else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) {
    309 		cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str );
    310 		cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue;
    311 	} else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) {
    312 		cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str );
    313 		cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue;
    314 	} else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) {
    315 		cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str );
    316 		cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue;
    317 	} else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) {
    318 		Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) );
    319 #ifdef MISSIONPACK
    320 		trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER );
    321 #endif
    322 	} else if ( num == CS_INTERMISSION ) {
    323 		cg.intermissionStarted = atoi( str );
    324 	} else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) {
    325 		cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str );
    326 	} else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS ) {
    327 		if ( str[0] != '*' ) {	// player specific sounds don't register here
    328 			cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str, qfalse );
    329 		}
    330 	} else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) {
    331 		CG_NewClientInfo( num - CS_PLAYERS );
    332 		CG_BuildSpectatorString();
    333 	} else if ( num == CS_FLAGSTATUS ) {
    334 		if( cgs.gametype == GT_CTF ) {
    335 			// format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped
    336 			cgs.redflag = str[0] - '0';
    337 			cgs.blueflag = str[1] - '0';
    338 		}
    339 #ifdef MISSIONPACK
    340 		else if( cgs.gametype == GT_1FCTF ) {
    341 			cgs.flagStatus = str[0] - '0';
    342 		}
    343 #endif
    344 	}
    345 	else if ( num == CS_SHADERSTATE ) {
    346 		CG_ShaderStateChanged();
    347 	}
    348 		
    349 }
    350 
    351 
    352 /*
    353 =======================
    354 CG_AddToTeamChat
    355 
    356 =======================
    357 */
    358 static void CG_AddToTeamChat( const char *str ) {
    359 	int len;
    360 	char *p, *ls;
    361 	int lastcolor;
    362 	int chatHeight;
    363 
    364 	if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) {
    365 		chatHeight = cg_teamChatHeight.integer;
    366 	} else {
    367 		chatHeight = TEAMCHAT_HEIGHT;
    368 	}
    369 
    370 	if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) {
    371 		// team chat disabled, dump into normal chat
    372 		cgs.teamChatPos = cgs.teamLastChatPos = 0;
    373 		return;
    374 	}
    375 
    376 	len = 0;
    377 
    378 	p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight];
    379 	*p = 0;
    380 
    381 	lastcolor = '7';
    382 
    383 	ls = NULL;
    384 	while (*str) {
    385 		if (len > TEAMCHAT_WIDTH - 1) {
    386 			if (ls) {
    387 				str -= (p - ls);
    388 				str++;
    389 				p -= (p - ls);
    390 			}
    391 			*p = 0;
    392 
    393 			cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time;
    394 
    395 			cgs.teamChatPos++;
    396 			p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight];
    397 			*p = 0;
    398 			*p++ = Q_COLOR_ESCAPE;
    399 			*p++ = lastcolor;
    400 			len = 0;
    401 			ls = NULL;
    402 		}
    403 
    404 		if ( Q_IsColorString( str ) ) {
    405 			*p++ = *str++;
    406 			lastcolor = *str;
    407 			*p++ = *str++;
    408 			continue;
    409 		}
    410 		if (*str == ' ') {
    411 			ls = p;
    412 		}
    413 		*p++ = *str++;
    414 		len++;
    415 	}
    416 	*p = 0;
    417 
    418 	cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time;
    419 	cgs.teamChatPos++;
    420 
    421 	if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight)
    422 		cgs.teamLastChatPos = cgs.teamChatPos - chatHeight;
    423 }
    424 
    425 /*
    426 ===============
    427 CG_MapRestart
    428 
    429 The server has issued a map_restart, so the next snapshot
    430 is completely new and should not be interpolated to.
    431 
    432 A tournement restart will clear everything, but doesn't
    433 require a reload of all the media
    434 ===============
    435 */
    436 static void CG_MapRestart( void ) {
    437 	if ( cg_showmiss.integer ) {
    438 		CG_Printf( "CG_MapRestart\n" );
    439 	}
    440 
    441 	CG_InitLocalEntities();
    442 	CG_InitMarkPolys();
    443 	CG_ClearParticles ();
    444 
    445 	// make sure the "3 frags left" warnings play again
    446 	cg.fraglimitWarnings = 0;
    447 
    448 	cg.timelimitWarnings = 0;
    449 
    450 	cg.intermissionStarted = qfalse;
    451 
    452 	cgs.voteTime = 0;
    453 
    454 	cg.mapRestart = qtrue;
    455 
    456 	CG_StartMusic();
    457 
    458 	trap_S_ClearLoopingSounds(qtrue);
    459 
    460 	// we really should clear more parts of cg here and stop sounds
    461 
    462 	// play the "fight" sound if this is a restart without warmup
    463 	if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) {
    464 		trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER );
    465 		CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH*2 );
    466 	}
    467 #ifdef MISSIONPACK
    468 	if (cg_singlePlayerActive.integer) {
    469 		trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time));
    470 		if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) {
    471 			trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string));
    472 		}
    473 	}
    474 #endif
    475 	trap_Cvar_Set("cg_thirdPerson", "0");
    476 }
    477 
    478 #define MAX_VOICEFILESIZE	16384
    479 #define MAX_VOICEFILES		8
    480 #define MAX_VOICECHATS		64
    481 #define MAX_VOICESOUNDS		64
    482 #define MAX_CHATSIZE		64
    483 #define MAX_HEADMODELS		64
    484 
    485 typedef struct voiceChat_s
    486 {
    487 	char id[64];
    488 	int numSounds;
    489 	sfxHandle_t sounds[MAX_VOICESOUNDS];
    490 	char chats[MAX_VOICESOUNDS][MAX_CHATSIZE];
    491 } voiceChat_t;
    492 
    493 typedef struct voiceChatList_s
    494 {
    495 	char name[64];
    496 	int gender;
    497 	int numVoiceChats;
    498 	voiceChat_t voiceChats[MAX_VOICECHATS];
    499 } voiceChatList_t;
    500 
    501 typedef struct headModelVoiceChat_s
    502 {
    503 	char headmodel[64];
    504 	int voiceChatNum;
    505 } headModelVoiceChat_t;
    506 
    507 voiceChatList_t voiceChatLists[MAX_VOICEFILES];
    508 headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS];
    509 
    510 /*
    511 =================
    512 CG_ParseVoiceChats
    513 =================
    514 */
    515 int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) {
    516 	int	len, i;
    517 	fileHandle_t f;
    518 	char buf[MAX_VOICEFILESIZE];
    519 	char **p, *ptr;
    520 	char *token;
    521 	voiceChat_t *voiceChats;
    522 	qboolean compress;
    523 	sfxHandle_t sound;
    524 
    525 	compress = qtrue;
    526 	if (cg_buildScript.integer) {
    527 		compress = qfalse;
    528 	}
    529 
    530 	len = trap_FS_FOpenFile( filename, &f, FS_READ );
    531 	if ( !f ) {
    532 		trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) );
    533 		return qfalse;
    534 	}
    535 	if ( len >= MAX_VOICEFILESIZE ) {
    536 		trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) );
    537 		trap_FS_FCloseFile( f );
    538 		return qfalse;
    539 	}
    540 
    541 	trap_FS_Read( buf, len, f );
    542 	buf[len] = 0;
    543 	trap_FS_FCloseFile( f );
    544 
    545 	ptr = buf;
    546 	p = &ptr;
    547 
    548 	Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename);
    549 	voiceChats = voiceChatList->voiceChats;
    550 	for ( i = 0; i < maxVoiceChats; i++ ) {
    551 		voiceChats[i].id[0] = 0;
    552 	}
    553 	token = COM_ParseExt(p, qtrue);
    554 	if (!token || token[0] == 0) {
    555 		return qtrue;
    556 	}
    557 	if (!Q_stricmp(token, "female")) {
    558 		voiceChatList->gender = GENDER_FEMALE;
    559 	}
    560 	else if (!Q_stricmp(token, "male")) {
    561 		voiceChatList->gender = GENDER_MALE;
    562 	}
    563 	else if (!Q_stricmp(token, "neuter")) {
    564 		voiceChatList->gender = GENDER_NEUTER;
    565 	}
    566 	else {
    567 		trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) );
    568 		return qfalse;
    569 	}
    570 
    571 	voiceChatList->numVoiceChats = 0;
    572 	while ( 1 ) {
    573 		token = COM_ParseExt(p, qtrue);
    574 		if (!token || token[0] == 0) {
    575 			return qtrue;
    576 		}
    577 		Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token);
    578 		token = COM_ParseExt(p, qtrue);
    579 		if (Q_stricmp(token, "{")) {
    580 			trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) );
    581 			return qfalse;
    582 		}
    583 		voiceChats[voiceChatList->numVoiceChats].numSounds = 0;
    584 		while(1) {
    585 			token = COM_ParseExt(p, qtrue);
    586 			if (!token || token[0] == 0) {
    587 				return qtrue;
    588 			}
    589 			if (!Q_stricmp(token, "}"))
    590 				break;
    591 			sound = trap_S_RegisterSound( token, compress );
    592 			voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound;
    593 			token = COM_ParseExt(p, qtrue);
    594 			if (!token || token[0] == 0) {
    595 				return qtrue;
    596 			}
    597 			Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[
    598 							voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token);
    599 			if (sound)
    600 				voiceChats[voiceChatList->numVoiceChats].numSounds++;
    601 			if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS)
    602 				break;
    603 		}
    604 		voiceChatList->numVoiceChats++;
    605 		if (voiceChatList->numVoiceChats >= maxVoiceChats)
    606 			return qtrue;
    607 	}
    608 	return qtrue;
    609 }
    610 
    611 /*
    612 =================
    613 CG_LoadVoiceChats
    614 =================
    615 */
    616 void CG_LoadVoiceChats( void ) {
    617 	int size;
    618 
    619 	size = trap_MemoryRemaining();
    620 	CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS );
    621 	CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS );
    622 	CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS );
    623 	CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS );
    624 	CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS );
    625 	CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS );
    626 	CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS );
    627 	CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS );
    628 	CG_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining());
    629 }
    630 
    631 /*
    632 =================
    633 CG_HeadModelVoiceChats
    634 =================
    635 */
    636 int CG_HeadModelVoiceChats( char *filename ) {
    637 	int	len, i;
    638 	fileHandle_t f;
    639 	char buf[MAX_VOICEFILESIZE];
    640 	char **p, *ptr;
    641 	char *token;
    642 
    643 	len = trap_FS_FOpenFile( filename, &f, FS_READ );
    644 	if ( !f ) {
    645 		//trap_Print( va( "voice chat file not found: %s\n", filename ) );
    646 		return -1;
    647 	}
    648 	if ( len >= MAX_VOICEFILESIZE ) {
    649 		trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) );
    650 		trap_FS_FCloseFile( f );
    651 		return -1;
    652 	}
    653 
    654 	trap_FS_Read( buf, len, f );
    655 	buf[len] = 0;
    656 	trap_FS_FCloseFile( f );
    657 
    658 	ptr = buf;
    659 	p = &ptr;
    660 
    661 	token = COM_ParseExt(p, qtrue);
    662 	if (!token || token[0] == 0) {
    663 		return -1;
    664 	}
    665 
    666 	for ( i = 0; i < MAX_VOICEFILES; i++ ) {
    667 		if ( !Q_stricmp(token, voiceChatLists[i].name) ) {
    668 			return i;
    669 		}
    670 	}
    671 
    672 	//FIXME: maybe try to load the .voice file which name is stored in token?
    673 
    674 	return -1;
    675 }
    676 
    677 
    678 /*
    679 =================
    680 CG_GetVoiceChat
    681 =================
    682 */
    683 int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) {
    684 	int i, rnd;
    685 
    686 	for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) {
    687 		if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) {
    688 			rnd = random() * voiceChatList->voiceChats[i].numSounds;
    689 			*snd = voiceChatList->voiceChats[i].sounds[rnd];
    690 			*chat = voiceChatList->voiceChats[i].chats[rnd];
    691 			return qtrue;
    692 		}
    693 	}
    694 	return qfalse;
    695 }
    696 
    697 /*
    698 =================
    699 CG_VoiceChatListForClient
    700 =================
    701 */
    702 voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) {
    703 	clientInfo_t *ci;
    704 	int voiceChatNum, i, j, k, gender;
    705 	char filename[MAX_QPATH], headModelName[MAX_QPATH];
    706 
    707 	if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
    708 		clientNum = 0;
    709 	}
    710 	ci = &cgs.clientinfo[ clientNum ];
    711 
    712 	for ( k = 0; k < 2; k++ ) {
    713 		if ( k == 0 ) {
    714 			if (ci->headModelName[0] == '*') {
    715 				Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName );
    716 			}
    717 			else {
    718 				Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName );
    719 			}
    720 		}
    721 		else {
    722 			if (ci->headModelName[0] == '*') {
    723 				Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 );
    724 			}
    725 			else {
    726 				Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName );
    727 			}
    728 		}
    729 		// find the voice file for the head model the client uses
    730 		for ( i = 0; i < MAX_HEADMODELS; i++ ) {
    731 			if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName)) {
    732 				break;
    733 			}
    734 		}
    735 		if (i < MAX_HEADMODELS) {
    736 			return &voiceChatLists[headModelVoiceChat[i].voiceChatNum];
    737 		}
    738 		// find a <headmodelname>.vc file
    739 		for ( i = 0; i < MAX_HEADMODELS; i++ ) {
    740 			if (!strlen(headModelVoiceChat[i].headmodel)) {
    741 				Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName);
    742 				voiceChatNum = CG_HeadModelVoiceChats(filename);
    743 				if (voiceChatNum == -1)
    744 					break;
    745 				Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ),
    746 							"%s", headModelName);
    747 				headModelVoiceChat[i].voiceChatNum = voiceChatNum;
    748 				return &voiceChatLists[headModelVoiceChat[i].voiceChatNum];
    749 			}
    750 		}
    751 	}
    752 	gender = ci->gender;
    753 	for (k = 0; k < 2; k++) {
    754 		// just pick the first with the right gender
    755 		for ( i = 0; i < MAX_VOICEFILES; i++ ) {
    756 			if (strlen(voiceChatLists[i].name)) {
    757 				if (voiceChatLists[i].gender == gender) {
    758 					// store this head model with voice chat for future reference
    759 					for ( j = 0; j < MAX_HEADMODELS; j++ ) {
    760 						if (!strlen(headModelVoiceChat[j].headmodel)) {
    761 							Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ),
    762 									"%s", headModelName);
    763 							headModelVoiceChat[j].voiceChatNum = i;
    764 							break;
    765 						}
    766 					}
    767 					return &voiceChatLists[i];
    768 				}
    769 			}
    770 		}
    771 		// fall back to male gender because we don't have neuter in the mission pack
    772 		if (gender == GENDER_MALE)
    773 			break;
    774 		gender = GENDER_MALE;
    775 	}
    776 	// store this head model with voice chat for future reference
    777 	for ( j = 0; j < MAX_HEADMODELS; j++ ) {
    778 		if (!strlen(headModelVoiceChat[j].headmodel)) {
    779 			Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ),
    780 					"%s", headModelName);
    781 			headModelVoiceChat[j].voiceChatNum = 0;
    782 			break;
    783 		}
    784 	}
    785 	// just return the first voice chat list
    786 	return &voiceChatLists[0];
    787 }
    788 
    789 #define MAX_VOICECHATBUFFER		32
    790 
    791 typedef struct bufferedVoiceChat_s
    792 {
    793 	int clientNum;
    794 	sfxHandle_t snd;
    795 	int voiceOnly;
    796 	char cmd[MAX_SAY_TEXT];
    797 	char message[MAX_SAY_TEXT];
    798 } bufferedVoiceChat_t;
    799 
    800 bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER];
    801 
    802 /*
    803 =================
    804 CG_PlayVoiceChat
    805 =================
    806 */
    807 void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) {
    808 #ifdef MISSIONPACK
    809 	// if we are going into the intermission, don't start any voices
    810 	if ( cg.intermissionStarted ) {
    811 		return;
    812 	}
    813 
    814 	if ( !cg_noVoiceChats.integer ) {
    815 		trap_S_StartLocalSound( vchat->snd, CHAN_VOICE);
    816 		if (vchat->clientNum != cg.snap->ps.clientNum) {
    817 			int orderTask = CG_ValidOrder(vchat->cmd);
    818 			if (orderTask > 0) {
    819 				cgs.acceptOrderTime = cg.time + 5000;
    820 				Q_strncpyz(cgs.acceptVoice, vchat->cmd, sizeof(cgs.acceptVoice));
    821 				cgs.acceptTask = orderTask;
    822 				cgs.acceptLeader = vchat->clientNum;
    823 			}
    824 			// see if this was an order
    825 			CG_ShowResponseHead();
    826 		}
    827 	}
    828 	if (!vchat->voiceOnly && !cg_noVoiceText.integer) {
    829 		CG_AddToTeamChat( vchat->message );
    830 		CG_Printf( "%s\n", vchat->message );
    831 	}
    832 	voiceChatBuffer[cg.voiceChatBufferOut].snd = 0;
    833 #endif
    834 }
    835 
    836 /*
    837 =====================
    838 CG_PlayBufferedVoieChats
    839 =====================
    840 */
    841 void CG_PlayBufferedVoiceChats( void ) {
    842 #ifdef MISSIONPACK
    843 	if ( cg.voiceChatTime < cg.time ) {
    844 		if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) {
    845 			//
    846 			CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]);
    847 			//
    848 			cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER;
    849 			cg.voiceChatTime = cg.time + 1000;
    850 		}
    851 	}
    852 #endif
    853 }
    854 
    855 /*
    856 =====================
    857 CG_AddBufferedVoiceChat
    858 =====================
    859 */
    860 void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) {
    861 #ifdef MISSIONPACK
    862 	// if we are going into the intermission, don't start any voices
    863 	if ( cg.intermissionStarted ) {
    864 		return;
    865 	}
    866 
    867 	memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t));
    868 	cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER;
    869 	if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) {
    870 		CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] );
    871 		cg.voiceChatBufferOut++;
    872 	}
    873 #endif
    874 }
    875 
    876 /*
    877 =================
    878 CG_VoiceChatLocal
    879 =================
    880 */
    881 void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ) {
    882 #ifdef MISSIONPACK
    883 	char *chat;
    884 	voiceChatList_t *voiceChatList;
    885 	clientInfo_t *ci;
    886 	sfxHandle_t snd;
    887 	bufferedVoiceChat_t vchat;
    888 
    889 	// if we are going into the intermission, don't start any voices
    890 	if ( cg.intermissionStarted ) {
    891 		return;
    892 	}
    893 
    894 	if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
    895 		clientNum = 0;
    896 	}
    897 	ci = &cgs.clientinfo[ clientNum ];
    898 
    899 	cgs.currentVoiceClient = clientNum;
    900 
    901 	voiceChatList = CG_VoiceChatListForClient( clientNum );
    902 
    903 	if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) {
    904 		//
    905 		if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) {
    906 			vchat.clientNum = clientNum;
    907 			vchat.snd = snd;
    908 			vchat.voiceOnly = voiceOnly;
    909 			Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd));
    910 			if ( mode == SAY_TELL ) {
    911 				Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat);
    912 			}
    913 			else if ( mode == SAY_TEAM ) {
    914 				Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat);
    915 			}
    916 			else {
    917 				Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat);
    918 			}
    919 			CG_AddBufferedVoiceChat(&vchat);
    920 		}
    921 	}
    922 #endif
    923 }
    924 
    925 /*
    926 =================
    927 CG_VoiceChat
    928 =================
    929 */
    930 void CG_VoiceChat( int mode ) {
    931 #ifdef MISSIONPACK
    932 	const char *cmd;
    933 	int clientNum, color;
    934 	qboolean voiceOnly;
    935 
    936 	voiceOnly = atoi(CG_Argv(1));
    937 	clientNum = atoi(CG_Argv(2));
    938 	color = atoi(CG_Argv(3));
    939 	cmd = CG_Argv(4);
    940 
    941 	if (cg_noTaunt.integer != 0) {
    942 		if (!strcmp(cmd, VOICECHAT_KILLINSULT)  || !strcmp(cmd, VOICECHAT_TAUNT) || \
    943 			!strcmp(cmd, VOICECHAT_DEATHINSULT) || !strcmp(cmd, VOICECHAT_KILLGAUNTLET) || \
    944 			!strcmp(cmd, VOICECHAT_PRAISE)) {
    945 			return;
    946 		}
    947 	}
    948 
    949 	CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd );
    950 #endif
    951 }
    952 
    953 /*
    954 =================
    955 CG_RemoveChatEscapeChar
    956 =================
    957 */
    958 static void CG_RemoveChatEscapeChar( char *text ) {
    959 	int i, l;
    960 
    961 	l = 0;
    962 	for ( i = 0; text[i]; i++ ) {
    963 		if (text[i] == '\x19')
    964 			continue;
    965 		text[l++] = text[i];
    966 	}
    967 	text[l] = '\0';
    968 }
    969 
    970 /*
    971 =================
    972 CG_ServerCommand
    973 
    974 The string has been tokenized and can be retrieved with
    975 Cmd_Argc() / Cmd_Argv()
    976 =================
    977 */
    978 static void CG_ServerCommand( void ) {
    979 	const char	*cmd;
    980 	char		text[MAX_SAY_TEXT];
    981 
    982 	cmd = CG_Argv(0);
    983 
    984 	if ( !cmd[0] ) {
    985 		// server claimed the command
    986 		return;
    987 	}
    988 
    989 	if ( !strcmp( cmd, "cp" ) ) {
    990 		CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
    991 		return;
    992 	}
    993 
    994 	if ( !strcmp( cmd, "cs" ) ) {
    995 		CG_ConfigStringModified();
    996 		return;
    997 	}
    998 
    999 	if ( !strcmp( cmd, "print" ) ) {
   1000 		CG_Printf( "%s", CG_Argv(1) );
   1001 #ifdef MISSIONPACK
   1002 		cmd = CG_Argv(1);			// yes, this is obviously a hack, but so is the way we hear about
   1003 									// votes passing or failing
   1004 		if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 )) {
   1005 			trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER );
   1006 		} else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) {
   1007 			trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER );
   1008 		}
   1009 #endif
   1010 		return;
   1011 	}
   1012 
   1013 	if ( !strcmp( cmd, "chat" ) ) {
   1014 		if ( !cg_teamChatsOnly.integer ) {
   1015 			trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
   1016 			Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
   1017 			CG_RemoveChatEscapeChar( text );
   1018 			CG_Printf( "%s\n", text );
   1019 		}
   1020 		return;
   1021 	}
   1022 
   1023 	if ( !strcmp( cmd, "tchat" ) ) {
   1024 		trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
   1025 		Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
   1026 		CG_RemoveChatEscapeChar( text );
   1027 		CG_AddToTeamChat( text );
   1028 		CG_Printf( "%s\n", text );
   1029 		return;
   1030 	}
   1031 	if ( !strcmp( cmd, "vchat" ) ) {
   1032 		CG_VoiceChat( SAY_ALL );
   1033 		return;
   1034 	}
   1035 
   1036 	if ( !strcmp( cmd, "vtchat" ) ) {
   1037 		CG_VoiceChat( SAY_TEAM );
   1038 		return;
   1039 	}
   1040 
   1041 	if ( !strcmp( cmd, "vtell" ) ) {
   1042 		CG_VoiceChat( SAY_TELL );
   1043 		return;
   1044 	}
   1045 
   1046 	if ( !strcmp( cmd, "scores" ) ) {
   1047 		CG_ParseScores();
   1048 		return;
   1049 	}
   1050 
   1051 	if ( !strcmp( cmd, "tinfo" ) ) {
   1052 		CG_ParseTeamInfo();
   1053 		return;
   1054 	}
   1055 
   1056 	if ( !strcmp( cmd, "map_restart" ) ) {
   1057 		CG_MapRestart();
   1058 		return;
   1059 	}
   1060 
   1061   if ( Q_stricmp (cmd, "remapShader") == 0 ) {
   1062 		if (trap_Argc() == 4) {
   1063 			trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3));
   1064 		}
   1065 	}
   1066 
   1067 	// loaddeferred can be both a servercmd and a consolecmd
   1068 	if ( !strcmp( cmd, "loaddefered" ) ) {	// FIXME: spelled wrong, but not changing for demo
   1069 		CG_LoadDeferredPlayers();
   1070 		return;
   1071 	}
   1072 
   1073 	// clientLevelShot is sent before taking a special screenshot for
   1074 	// the menu system during development
   1075 	if ( !strcmp( cmd, "clientLevelShot" ) ) {
   1076 		cg.levelShot = qtrue;
   1077 		return;
   1078 	}
   1079 
   1080 	CG_Printf( "Unknown client game command: %s\n", cmd );
   1081 }
   1082 
   1083 
   1084 /*
   1085 ====================
   1086 CG_ExecuteNewServerCommands
   1087 
   1088 Execute all of the server commands that were received along
   1089 with this this snapshot.
   1090 ====================
   1091 */
   1092 void CG_ExecuteNewServerCommands( int latestSequence ) {
   1093 	while ( cgs.serverCommandSequence < latestSequence ) {
   1094 		if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) {
   1095 			CG_ServerCommand();
   1096 		}
   1097 	}
   1098 }