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