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 
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 */
CG_ParseServerinfo(void)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 */
CG_ParseWarmup(void)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 */
CG_SetConfigValues(void)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 */
CG_ShaderStateChanged(void)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 */
CG_ConfigStringModified(void)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_SOUNDS ) {
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 */
CG_AddToTeamChat(const char * str)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 */
CG_MapRestart(void)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 */
CG_ParseVoiceChats(const char * filename,voiceChatList_t * voiceChatList,int maxVoiceChats)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 */
CG_LoadVoiceChats(void)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 */
CG_HeadModelVoiceChats(char * filename)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 */
CG_GetVoiceChat(voiceChatList_t * voiceChatList,const char * id,sfxHandle_t * snd,char ** chat)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 */
CG_VoiceChatListForClient(int clientNum)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 */
CG_PlayVoiceChat(bufferedVoiceChat_t * vchat)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 */
CG_PlayBufferedVoiceChats(void)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 */
CG_AddBufferedVoiceChat(bufferedVoiceChat_t * vchat)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 */
CG_VoiceChatLocal(int mode,qboolean voiceOnly,int clientNum,int color,const char * cmd)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 */
CG_VoiceChat(int mode)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 */
CG_RemoveChatEscapeChar(char * text)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 */
CG_ServerCommand(void)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 	{
1063 		if (trap_Argc() == 4)
1064 		{
1065 			char shader1[MAX_QPATH];
1066 			char shader2[MAX_QPATH];
1067 			char shader3[MAX_QPATH];
1068 
1069 			Q_strncpyz(shader1, CG_Argv(1), sizeof(shader1));
1070 			Q_strncpyz(shader2, CG_Argv(2), sizeof(shader2));
1071 			Q_strncpyz(shader3, CG_Argv(3), sizeof(shader3));
1072 
1073 			trap_R_RemapShader(shader1, shader2, shader3);
1074 		}
1075 
1076 		return;
1077 	}
1078 
1079 	// loaddeferred can be both a servercmd and a consolecmd
1080 	if ( !strcmp( cmd, "loaddefered" ) ) {	// FIXME: spelled wrong, but not changing for demo
1081 		CG_LoadDeferredPlayers();
1082 		return;
1083 	}
1084 
1085 	// clientLevelShot is sent before taking a special screenshot for
1086 	// the menu system during development
1087 	if ( !strcmp( cmd, "clientLevelShot" ) ) {
1088 		cg.levelShot = qtrue;
1089 		return;
1090 	}
1091 
1092 	CG_Printf( "Unknown client game command: %s\n", cmd );
1093 }
1094 
1095 
1096 /*
1097 ====================
1098 CG_ExecuteNewServerCommands
1099 
1100 Execute all of the server commands that were received along
1101 with this this snapshot.
1102 ====================
1103 */
CG_ExecuteNewServerCommands(int latestSequence)1104 void CG_ExecuteNewServerCommands( int latestSequence ) {
1105 	while ( cgs.serverCommandSequence < latestSequence ) {
1106 		if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) {
1107 			CG_ServerCommand();
1108 		}
1109 	}
1110 }
1111