1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”).
8 
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW MP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RTCW MP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 
30 
31 // cg_servercmds.c -- reliably sequenced text commands sent by the server
32 // these are processed at snapshot transition time, so there will definately
33 // be a valid snapshot this frame
34 
35 #include "cg_local.h"
36 #include "../ui/ui_shared.h"
37 
38 void CG_StartShakeCamera( float param );                                // NERVE - SMF
39 
40 /*
41 =================
42 CG_ParseScores
43 
44 =================
45 */
CG_ParseScores(void)46 static void CG_ParseScores( void ) {
47 	int i, powerups;
48 
49 	cg.numScores = atoi( CG_Argv( 1 ) );
50 	if ( cg.numScores > MAX_CLIENTS ) {
51 		cg.numScores = MAX_CLIENTS;
52 	}
53 
54 	cg.teamScores[0] = atoi( CG_Argv( 2 ) );
55 	cg.teamScores[1] = atoi( CG_Argv( 3 ) );
56 
57 	memset( cg.scores, 0, sizeof( cg.scores ) );
58 	for ( i = 0 ; i < cg.numScores ; i++ ) {
59 		//
60 		cg.scores[i].client = atoi( CG_Argv( i * 8 + 4 ) );
61 		cg.scores[i].score = atoi( CG_Argv( i * 8 + 5 ) );
62 		cg.scores[i].ping = atoi( CG_Argv( i * 8 + 6 ) );
63 		cg.scores[i].time = atoi( CG_Argv( i * 8 + 7 ) );
64 		cg.scores[i].scoreFlags = atoi( CG_Argv( i * 8 + 8 ) );
65 		powerups = atoi( CG_Argv( i * 8 + 9 ) );
66 		cg.scores[i].playerClass = atoi( CG_Argv( i * 8 + 10 ) );       // NERVE - SMF
67 		cg.scores[i].respawnsLeft = atoi( CG_Argv( i * 8 + 11 ) );      // NERVE - SMF
68 		// DHM - Nerve :: the following parameters are not sent by server
69 		/*
70 		cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10));
71 		cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11));
72 		cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12));
73 		cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13));
74 		cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14));
75 		cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15));
76 		cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16));
77 		cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17));
78 		*/
79 
80 		if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) {
81 			cg.scores[i].client = 0;
82 		}
83 		cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score;
84 		cgs.clientinfo[ cg.scores[i].client ].powerups = powerups;
85 
86 		cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team;
87 	}
88 #ifdef MISSIONPACK
89 	CG_SetScoreSelection( NULL );
90 #endif
91 
92 }
93 
94 /*
95 =================
96 CG_ParseTeamInfo
97 
98 =================
99 */
CG_ParseTeamInfo(void)100 static void CG_ParseTeamInfo( void ) {
101 	int i;
102 	int client;
103 
104 	// NERVE - SMF
105 	cg.identifyClientNum = atoi( CG_Argv( 1 ) );
106 	cg.identifyClientHealth = atoi( CG_Argv( 2 ) );
107 	// -NERVE - SMF
108 
109 	numSortedTeamPlayers = atoi( CG_Argv( 3 ) );
110 	if( numSortedTeamPlayers < 0 || numSortedTeamPlayers > TEAM_MAXOVERLAY )
111 	{
112 		CG_Error( "CG_ParseTeamInfo: numSortedTeamPlayers out of range (%d)",
113 				numSortedTeamPlayers );
114 		return;
115 	}
116 
117 	for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) {
118 		client = atoi( CG_Argv( i * 5 + 4 ) );
119 		if( client < 0 || client >= MAX_CLIENTS )
120 		{
121 		  CG_Error( "CG_ParseTeamInfo: bad client number: %d", client );
122 		  return;
123 		}
124 
125 		sortedTeamPlayers[i] = client;
126 
127 		cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 5 + 5 ) );
128 		cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 5 + 6 ) );
129 		cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 5 + 7 ) );
130 
131 		cg_entities[ client ].currentState.teamNum = atoi( CG_Argv( i * 5 + 8 ) );
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 	cgs.antilag = atoi( Info_ValueForKey( info, "g_antilag" ) );
151 	if ( !cgs.localServer ) {
152 		trap_Cvar_Set( "g_gametype", va( "%i", cgs.gametype ) );
153 		trap_Cvar_Set( "g_antilag", va( "%i", cgs.antilag ) );
154 	}
155 	cgs.dmflags = 0; //atoi( Info_ValueForKey( info, "dmflags" ) );				// NERVE - SMF - no longer serverinfo
156 	cgs.teamflags = 0; //atoi( Info_ValueForKey( info, "teamflags" ) );
157 	cgs.fraglimit = 0; //atoi( Info_ValueForKey( info, "fraglimit" ) );			// NERVE - SMF - no longer serverinfo
158 	cgs.capturelimit = 0; //atoi( Info_ValueForKey( info, "capturelimit" ) );	// NERVE - SMF - no longer serverinfo
159 	cgs.timelimit = atof( Info_ValueForKey( info, "timelimit" ) );
160 	cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
161 	mapname = Info_ValueForKey( info, "mapname" );
162 	Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname );
163 
164 // JPW NERVE
165 // prolly should parse all CS_SERVERINFO keys automagically, but I don't want to break anything that might be improperly set for wolf SP, so I'm just parsing MP relevant stuff here
166 	trap_Cvar_Set( "g_medicChargeTime",Info_ValueForKey( info,"g_medicChargeTime" ) );
167 	trap_Cvar_Set( "g_engineerChargeTime",Info_ValueForKey( info,"g_engineerChargeTime" ) );
168 	trap_Cvar_Set( "g_soldierChargeTime",Info_ValueForKey( info,"g_soldierChargeTime" ) );
169 	trap_Cvar_Set( "g_LTChargeTime",Info_ValueForKey( info,"g_LTChargeTime" ) );
170 	trap_Cvar_Set( "g_redlimbotime",Info_ValueForKey( info,"g_redlimbotime" ) );
171 	cg_redlimbotime.integer = atoi( Info_ValueForKey( info, "g_redlimbotime" ) );
172 	cg_bluelimbotime.integer = atoi( Info_ValueForKey( info, "g_bluelimbotime" ) );
173 // jpw
174 
175 	cgs.minclients = atoi( Info_ValueForKey( info, "g_minGameClients" ) );       // NERVE - SMF
176 
177 	// TTimo - make this available for ingame_callvote
178 	trap_Cvar_Set( "cg_ui_voteFlags", Info_ValueForKey( info, "g_voteFlags" ) );
179 }
180 
181 /*
182 ==================
183 CG_ParseWolfinfo
184 
185 NERVE - SMF
186 ==================
187 */
CG_ParseWolfinfo(void)188 void CG_ParseWolfinfo( void ) {
189 	const char  *info;
190 
191 	info = CG_ConfigString( CS_WOLFINFO );
192 
193 	cgs.currentRound = atoi( Info_ValueForKey( info, "g_currentRound" ) );
194 	cgs.nextTimeLimit = atof( Info_ValueForKey( info, "g_nextTimeLimit" ) );
195 	cgs.gamestate = atoi( Info_ValueForKey( info, "gamestate" ) );
196 	if ( !cgs.localServer ) {
197 		trap_Cvar_Set( "gamestate", va( "%i", cgs.gamestate ) );
198 	}
199 }
200 
201 /*
202 ==================
203 CG_ParseWarmup
204 ==================
205 */
CG_ParseWarmup(void)206 static void CG_ParseWarmup( void ) {
207 	const char  *info;
208 	int warmup;
209 
210 	info = CG_ConfigString( CS_WARMUP );
211 
212 	warmup = atoi( info );
213 	cg.warmupCount = -1;
214 
215 	if ( warmup == 0 && cg.warmup ) {
216 
217 	} else if ( warmup > 0 && cg.warmup <= 0 ) {
218 		trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER );
219 	}
220 
221 	cg.warmup = warmup;
222 }
223 
224 /*
225 =====================
226 CG_ParseScreenFade
227 =====================
228 */
CG_ParseScreenFade(void)229 static void CG_ParseScreenFade( void ) {
230 	const char  *info;
231 	char *token;
232 
233 	info = CG_ConfigString( CS_SCREENFADE );
234 	token = COM_Parse( (char **)&info );
235 	cgs.fadeAlpha = atof( token );
236 	token = COM_Parse( (char **)&info );
237 	cgs.fadeStartTime = atoi( token );
238 	token = COM_Parse( (char **)&info );
239 	cgs.fadeDuration = atoi( token );
240 
241 	if ( cgs.fadeStartTime + cgs.fadeDuration < cg.time ) {
242 		cgs.fadeAlphaCurrent = cgs.fadeAlpha;
243 	}
244 }
245 
246 
247 /*
248 ==============
249 CG_ParseFog
250 	float near dist
251 	float far dist
252 	float density
253 	float[3] r,g,b
254 	int		time
255 ==============
256 */
CG_ParseFog(void)257 static void CG_ParseFog( void ) {
258 	const char  *info;
259 	char *token;
260 	float ne, fa, r, g, b, density;
261 	int time;
262 
263 	info = CG_ConfigString( CS_FOGVARS );
264 	token = COM_Parse( (char **)&info );    ne = atof( token );
265 	token = COM_Parse( (char **)&info );    fa = atof( token );
266 	token = COM_Parse( (char **)&info );    density = atof( token );
267 	token = COM_Parse( (char **)&info );    r = atof( token );
268 	token = COM_Parse( (char **)&info );    g = atof( token );
269 	token = COM_Parse( (char **)&info );    b = atof( token );
270 	token = COM_Parse( (char **)&info );    time = atoi( token );
271 
272 	if ( fa ) {    // far of '0' from a target_fog means "return to map fog"
273 		trap_R_SetFog( FOG_SERVER, (int)ne, (int)fa, r, g, b, density + .1 );
274 		trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_SERVER, time, 0, 0, 0, 0 );
275 	} else {
276 		trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP, time, 0, 0, 0, 0 );
277 	}
278 }
279 
280 /*
281 ================
282 CG_SetConfigValues
283 
284 Called on load to set the initial values from configure strings
285 ================
286 */
CG_SetConfigValues(void)287 void CG_SetConfigValues( void ) {
288 #ifdef MISSIONPACK
289 	const char *s;
290 #endif
291 
292 	cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) );
293 	cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) );
294 	cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
295 #ifdef MISSIONPACK
296 	if ( cgs.gametype == GT_CTF ) {
297 		s = CG_ConfigString( CS_FLAGSTATUS );
298 		cgs.redflag = s[0] - '0';
299 		cgs.blueflag = s[1] - '0';
300 	} else if ( cgs.gametype == GT_1FCTF )    {
301 		s = CG_ConfigString( CS_FLAGSTATUS );
302 		cgs.flagStatus = s[0] - '0';
303 	}
304 #endif
305 	cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
306 }
307 
308 /*
309 =====================
310 CG_ShaderStateChanged
311 =====================
312 */
CG_ShaderStateChanged(void)313 void CG_ShaderStateChanged( void ) {
314 	char originalShader[MAX_QPATH];
315 	char newShader[MAX_QPATH];
316 	char timeOffset[16];
317 	const char *o;
318 	char *n,*t;
319 
320 	o = CG_ConfigString( CS_SHADERSTATE );
321 	while ( o && *o ) {
322 		n = strstr( o, "=" );
323 		if ( n && *n ) {
324 			strncpy( originalShader, o, n - o );
325 			originalShader[n - o] = 0;
326 			n++;
327 			t = strstr( n, ":" );
328 			if ( t && *t ) {
329 				strncpy( newShader, n, t - n );
330 				newShader[t - n] = 0;
331 			} else {
332 				break;
333 			}
334 			t++;
335 			o = strstr( t, "@" );
336 			if ( o ) {
337 				strncpy( timeOffset, t, o - t );
338 				timeOffset[o - t] = 0;
339 				o++;
340 				trap_R_RemapShader( originalShader, newShader, timeOffset );
341 			}
342 		} else {
343 			break;
344 		}
345 	}
346 }
347 
348 /*
349 ================
350 CG_ConfigStringModified
351 
352 ================
353 */
CG_ConfigStringModified(void)354 static void CG_ConfigStringModified( void ) {
355 	const char  *str;
356 	int num;
357 
358 	num = atoi( CG_Argv( 1 ) );
359 
360 	// get the gamestate from the client system, which will have the
361 	// new configstring already integrated
362 	trap_GetGameState( &cgs.gameState );
363 
364 	// look up the individual string that was modified
365 	str = CG_ConfigString( num );
366 
367 	// do something with it if necessary
368 	if ( num == CS_MUSIC ) {
369 		CG_StartMusic();
370 	} else if ( num == CS_SERVERINFO ) {
371 		CG_ParseServerinfo();
372 	} else if ( num == CS_WARMUP ) {
373 		CG_ParseWarmup();
374 	} else if ( num == CS_WOLFINFO ) {      // NERVE - SMF
375 		CG_ParseWolfinfo();
376 	} else if ( num == CS_SCORES1 ) {
377 		cgs.scores1 = atoi( str );
378 	} else if ( num == CS_SCORES2 ) {
379 		cgs.scores2 = atoi( str );
380 	} else if ( num == CS_LEVEL_START_TIME ) {
381 		cgs.levelStartTime = atoi( str );
382 	} else if ( num == CS_VOTE_TIME ) {
383 		cgs.voteTime = atoi( str );
384 		cgs.voteModified = qtrue;
385 	} else if ( num == CS_VOTE_YES ) {
386 		cgs.voteYes = atoi( str );
387 		cgs.voteModified = qtrue;
388 	} else if ( num == CS_VOTE_NO ) {
389 		cgs.voteNo = atoi( str );
390 		cgs.voteModified = qtrue;
391 	} else if ( num == CS_VOTE_STRING ) {
392 		Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) );
393 #if 0
394 		trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER );
395 	} else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1 ) {
396 		cgs.teamVoteTime[num - CS_TEAMVOTE_TIME] = atoi( str );
397 		cgs.teamVoteModified[num - CS_TEAMVOTE_TIME] = qtrue;
398 	} else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 ) {
399 		cgs.teamVoteYes[num - CS_TEAMVOTE_YES] = atoi( str );
400 		cgs.teamVoteModified[num - CS_TEAMVOTE_YES] = qtrue;
401 	} else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 ) {
402 		cgs.teamVoteNo[num - CS_TEAMVOTE_NO] = atoi( str );
403 		cgs.teamVoteModified[num - CS_TEAMVOTE_NO] = qtrue;
404 	} else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 ) {
405 		Q_strncpyz( cgs.teamVoteString[num - CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString[0] ) );
406 		trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER );
407 #endif
408 	} else if ( num == CS_INTERMISSION ) {
409 		cg.intermissionStarted = atoi( str );
410 	} else if ( num == CS_SCREENFADE ) {
411 		CG_ParseScreenFade();
412 	} else if ( num == CS_FOGVARS ) {
413 		CG_ParseFog();
414 	} else if ( num >= CS_MODELS && num < CS_MODELS + MAX_MODELS ) {
415 		cgs.gameModels[ num - CS_MODELS ] = trap_R_RegisterModel( str );
416 	} else if ( num >= CS_SOUNDS && num < CS_SOUNDS + MAX_SOUNDS ) {
417 		if ( str[0] != '*' ) {   // player specific sounds don't register here
418 
419 			// Ridah, register sound scripts seperately
420 			if ( !strstr( str, ".wav" ) ) {
421 				CG_SoundScriptPrecache( str );
422 			} else {
423 				cgs.gameSounds[ num - CS_SOUNDS] = trap_S_RegisterSound( str );
424 			}
425 
426 		}
427 	} else if ( num >= CS_PLAYERS && num < CS_PLAYERS + MAX_CLIENTS ) {
428 		CG_NewClientInfo( num - CS_PLAYERS );
429 	}
430 	// Rafael particle configstring
431 	else if ( num >= CS_PARTICLES && num < CS_PARTICLES + MAX_PARTICLES_AREAS ) {
432 		CG_NewParticleArea( num );
433 	}
434 //----(SA)	have not reached this code yet so I don't know if I really need this here
435 	else if ( num >= CS_DLIGHTS && num < CS_DLIGHTS + MAX_DLIGHTS ) {
436 //		CG_Printf( ">>>>>>>>>>>got configstring for dlight: %d\ntell Sherman!!!!!!!!!!", num - CS_DLIGHTS );
437 //----(SA)
438 	} else if ( num == CS_SHADERSTATE )   {
439 		CG_ShaderStateChanged();
440 	}
441 }
442 
443 
444 /*
445 =======================
446 CG_AddToTeamChat
447 
448 =======================
449 */
CG_AddToTeamChat(const char * str)450 static void CG_AddToTeamChat( const char *str ) {
451 	int len;
452 	char *p, *ls;
453 	int lastcolor;
454 	int chatHeight;
455 
456 	if ( cg_teamChatHeight.integer < TEAMCHAT_HEIGHT ) {
457 		chatHeight = cg_teamChatHeight.integer;
458 	} else {
459 		chatHeight = TEAMCHAT_HEIGHT;
460 	}
461 
462 	if ( chatHeight <= 0 || cg_teamChatTime.integer <= 0 ) {
463 		// team chat disabled, dump into normal chat
464 		cgs.teamChatPos = cgs.teamLastChatPos = 0;
465 		return;
466 	}
467 
468 	len = 0;
469 
470 	p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight];
471 	*p = 0;
472 
473 	lastcolor = '7';
474 
475 	ls = NULL;
476 	while ( *str ) {
477 		if ( len > TEAMCHAT_WIDTH - 1 ) {
478 			if ( ls ) {
479 				str -= ( p - ls );
480 				str++;
481 				p -= ( p - ls );
482 			}
483 			*p = 0;
484 
485 			cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time;
486 
487 			cgs.teamChatPos++;
488 			p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight];
489 			*p = 0;
490 			*p++ = Q_COLOR_ESCAPE;
491 			*p++ = lastcolor;
492 			len = 0;
493 			ls = NULL;
494 		}
495 
496 		if ( Q_IsColorString( str ) ) {
497 			*p++ = *str++;
498 			lastcolor = *str;
499 			*p++ = *str++;
500 			continue;
501 		}
502 		if ( *str == ' ' ) {
503 			ls = p;
504 		}
505 		*p++ = *str++;
506 		len++;
507 	}
508 	*p = 0;
509 
510 	cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time;
511 	cgs.teamChatPos++;
512 
513 	if ( cgs.teamChatPos - cgs.teamLastChatPos > chatHeight ) {
514 		cgs.teamLastChatPos = cgs.teamChatPos - chatHeight;
515 	}
516 }
517 
518 /*
519 =======================
520 CG_AddToNotify
521 
522 =======================
523 */
CG_AddToNotify(const char * str)524 void CG_AddToNotify( const char *str ) {
525 	int len;
526 	char *p, *ls;
527 	int lastcolor;
528 	int chatHeight;
529 	float notifytime;
530 	char var[MAX_TOKEN_CHARS];
531 
532 	trap_Cvar_VariableStringBuffer( "con_notifytime", var, sizeof( var ) );
533 	notifytime = atof( var ) * 1000;
534 
535 	chatHeight = NOTIFY_HEIGHT;
536 
537 	if ( chatHeight <= 0 || notifytime <= 0 ) {
538 		// team chat disabled, dump into normal chat
539 		cgs.notifyPos = cgs.notifyLastPos = 0;
540 		return;
541 	}
542 
543 	len = 0;
544 
545 	p = cgs.notifyMsgs[cgs.notifyPos % chatHeight];
546 	*p = 0;
547 
548 	lastcolor = '7';
549 
550 	ls = NULL;
551 	while ( *str ) {
552 		if ( len > NOTIFY_WIDTH - 1 || ( *str == '\n' && ( *( str + 1 ) != 0 ) ) ) {
553 			if ( ls ) {
554 				str -= ( p - ls );
555 				str++;
556 				p -= ( p - ls );
557 			}
558 			*p = 0;
559 
560 			cgs.notifyMsgTimes[cgs.notifyPos % chatHeight] = cg.time;
561 
562 			cgs.notifyPos++;
563 			p = cgs.notifyMsgs[cgs.notifyPos % chatHeight];
564 			*p = 0;
565 			*p++ = Q_COLOR_ESCAPE;
566 			*p++ = lastcolor;
567 			len = 0;
568 			ls = NULL;
569 		}
570 
571 		if ( Q_IsColorString( str ) ) {
572 			*p++ = *str++;
573 			lastcolor = *str;
574 			*p++ = *str++;
575 			continue;
576 		}
577 		if ( *str == ' ' ) {
578 			ls = p;
579 		}
580 		while ( *str == '\n' ) {
581 			// TTimo gcc warning: value computed is not used
582 			// was *str++;
583 			str++;
584 		}
585 
586 		if ( *str ) {
587 			*p++ = *str++;
588 			len++;
589 		}
590 	}
591 	*p = 0;
592 
593 	cgs.notifyMsgTimes[cgs.notifyPos % chatHeight] = cg.time;
594 	cgs.notifyPos++;
595 
596 	if ( cgs.notifyPos - cgs.notifyLastPos > chatHeight ) {
597 		cgs.notifyLastPos = cgs.notifyPos - chatHeight;
598 	}
599 }
600 
601 /*
602 ===============
603 CG_SendMoveSpeed
604 ===============
605 */
CG_SendMoveSpeed(animation_t * animList,int numAnims,char * modelName)606 void CG_SendMoveSpeed( animation_t *animList, int numAnims, char *modelName ) {
607 	animation_t *anim;
608 	int i;
609 	char text[10000];
610 
611 	if ( !cgs.localServer ) {
612 		return;
613 	}
614 
615 	text[0] = 0;
616 	Q_strcat( text, sizeof( text ), modelName );
617 
618 	for ( i = 0, anim = animList; i < numAnims; i++, anim++ ) {
619 		if ( anim->moveSpeed <= 0 ) {
620 			continue;
621 		}
622 
623 		// add this to the list
624 		Q_strcat( text, sizeof( text ), va( " %s %i", anim->name, anim->moveSpeed ) );
625 	}
626 
627 	// send the movespeeds to the server
628 	trap_SendMoveSpeedsToGame( 0, text );
629 }
630 
631 /*
632 ===============
633 CG_SendMoveSpeeds
634 
635   send moveSpeeds for all unique models
636 ===============
637 */
CG_SendMoveSpeeds(void)638 void CG_SendMoveSpeeds( void ) {
639 	int i;
640 	animModelInfo_t *modelInfo;
641 
642 	for ( i = 0, modelInfo = cgs.animScriptData.modelInfo; i < MAX_ANIMSCRIPT_MODELS; i++, modelInfo++ ) {
643 		if ( !modelInfo->modelname[0] ) {
644 			continue;
645 		}
646 
647 		// send this model
648 		CG_SendMoveSpeed( modelInfo->animations, modelInfo->numAnimations, modelInfo->modelname );
649 	}
650 }
651 
652 
653 /*
654 ===============
655 CG_MapRestart
656 
657 The server has issued a map_restart, so the next snapshot
658 is completely new and should not be interpolated to.
659 
660 A tournement restart will clear everything, but doesn't
661 require a reload of all the media
662 ===============
663 */
CG_MapRestart(void)664 static void CG_MapRestart( void ) {
665 	int i;
666 	if ( cg_showmiss.integer ) {
667 		CG_Printf( "CG_MapRestart\n" );
668 	}
669 
670 	memset( &cg.lastWeapSelInBank[0], 0, MAX_WEAP_BANKS * sizeof( int ) );  // clear weapon bank selections
671 
672 	cg.centerPrintTime = 0; // reset centerprint counter so previous messages don't re-appear
673 	cg.itemPickupTime = 0;  // reset item pickup counter so previous messages don't re-appear
674 	cg.cursorHintFade = 0;  // reset cursor hint timer
675 
676 	// DHM - Nerve :: Reset complaint system
677 	cgs.complaintClient = -1;
678 	cgs.complaintEndTime = 0;
679 
680 	// (SA) clear zoom (so no warpies)
681 	cg.zoomedBinoc = qfalse;
682 	cg.zoomedBinoc = cg.zoomedScope = qfalse;
683 	cg.zoomTime = 0;
684 	cg.zoomval = 0;
685 
686 	// reset fog to world fog (if present)
687 	trap_R_SetFog( FOG_CMD_SWITCHFOG, FOG_MAP,20,0,0,0,0 );
688 
689 	CG_InitLocalEntities();
690 	CG_InitMarkPolys();
691 
692 	//Rafael particles
693 	CG_ClearParticles();
694 	// done.
695 
696 	for ( i = 1; i < MAX_PARTICLES_AREAS; i++ )
697 	{
698 		{
699 			int rval;
700 
701 			rval = CG_NewParticleArea( CS_PARTICLES + i );
702 			if ( !rval ) {
703 				break;
704 			}
705 		}
706 	}
707 
708 
709 	// Ridah, trails
710 //	CG_ClearTrails();
711 	// done.
712 
713 	// Ridah
714 	CG_ClearFlameChunks();
715 	CG_SoundInit();
716 	// done.
717 
718 	// make sure the "3 frags left" warnings play again
719 	cg.fraglimitWarnings = 0;
720 
721 	cg.timelimitWarnings = 0;
722 
723 	cg.rewardTime = 0;
724 	cg.intermissionStarted = qfalse;
725 	cg.levelShot = qfalse;
726 
727 	cgs.voteTime = 0;
728 
729 	cg.lightstylesInited    = qfalse;
730 
731 	cg.mapRestart = qtrue;
732 
733 	CG_StartMusic();
734 
735 	trap_S_ClearLoopingSounds( qtrue );
736 
737 	cg.latchVictorySound = qfalse;          // NERVE - SMF
738 // JPW NERVE -- reset render flags
739 	cg_fxflags = 0;
740 // jpw
741 
742 
743 	// we really should clear more parts of cg here and stop sounds
744 	cg.v_dmg_time = 0;
745 	cg.v_noFireTime = 0;
746 	cg.v_fireTime = 0;
747 
748 	// inform LOCAL server of animationSpeeds for AI use (ONLY)
749 //	if ( cgs.localServer ) {
750 //		CG_SendMoveSpeeds();
751 //	}
752 
753 	// play the "fight" sound if this is a restart without warmup
754 	if ( cg.warmup == 0 && cgs.gametype == GT_TOURNAMENT ) {
755 		trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER );
756 		CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH * 2 );
757 	}
758 #ifdef MISSIONPACK
759 	if ( cg_singlePlayerActive.integer ) {
760 		trap_Cvar_Set( "ui_matchStartTime", va( "%i", cg.time ) );
761 		if ( cg_recordSPDemo.integer && *cg_recordSPDemoName.string ) {
762 			trap_SendConsoleCommand( va( "set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string ) );
763 		}
764 	}
765 #endif
766 	trap_Cvar_Set( "cg_thirdPerson", "0" );
767 }
768 
769 /*
770 =================
771 CG_RequestMoveSpeed
772 =================
773 */
CG_RequestMoveSpeed(const char * modelname)774 void CG_RequestMoveSpeed( const char *modelname ) {
775 	animModelInfo_t *modelInfo;
776 
777 	modelInfo = BG_ModelInfoForModelname( (char *)modelname );
778 
779 	if ( !modelInfo ) {
780 		// ignore it
781 		return;
782 	}
783 
784 	// send it
785 	CG_SendMoveSpeed( modelInfo->animations, modelInfo->numAnimations, (char *)modelname );
786 }
787 
788 // NERVE - SMF
789 #define MAX_VOICEFILESIZE   16384
790 #define MAX_VOICEFILES      8
791 #define MAX_VOICECHATS      64
792 #define MAX_VOICESOUNDS     64
793 #define MAX_CHATSIZE        64
794 #define MAX_HEADMODELS      64
795 
796 typedef struct voiceChat_s
797 {
798 	char id[64];
799 	int numSounds;
800 	sfxHandle_t sounds[MAX_VOICESOUNDS];
801 	char chats[MAX_VOICESOUNDS][MAX_CHATSIZE];
802 	qhandle_t sprite[MAX_VOICESOUNDS];          // DHM - Nerve
803 } voiceChat_t;
804 
805 typedef struct voiceChatList_s
806 {
807 	char name[64];
808 	int gender;
809 	int numVoiceChats;
810 	voiceChat_t voiceChats[MAX_VOICECHATS];
811 } voiceChatList_t;
812 
813 typedef struct headModelVoiceChat_s
814 {
815 	char headmodel[64];
816 	int voiceChatNum;
817 } headModelVoiceChat_t;
818 
819 voiceChatList_t voiceChatLists[MAX_VOICEFILES];
820 headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS];
821 
822 /*
823 =================
824 CG_ParseVoiceChats
825 =================
826 */
CG_ParseVoiceChats(const char * filename,voiceChatList_t * voiceChatList,int maxVoiceChats)827 int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) {
828 	int len, i;
829 	int current = 0;
830 	fileHandle_t f;
831 	char buf[MAX_VOICEFILESIZE];
832 	char **p, *ptr;
833 	char *token;
834 	voiceChat_t *voiceChats;
835 
836 	len = trap_FS_FOpenFile( filename, &f, FS_READ );
837 	if ( !f ) {
838 		trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) );
839 		return qfalse;
840 	}
841 	if ( len >= MAX_VOICEFILESIZE ) {
842 		trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i\n", filename, len, MAX_VOICEFILESIZE ) );
843 		trap_FS_FCloseFile( f );
844 		return qfalse;
845 	}
846 
847 	trap_FS_Read( buf, len, f );
848 	buf[len] = 0;
849 	trap_FS_FCloseFile( f );
850 
851 	ptr = buf;
852 	p = &ptr;
853 
854 	Com_sprintf( voiceChatList->name, sizeof( voiceChatList->name ), "%s", filename );
855 	voiceChats = voiceChatList->voiceChats;
856 	for ( i = 0; i < maxVoiceChats; i++ ) {
857 		voiceChats[i].id[0] = 0;
858 	}
859 	token = COM_ParseExt( p, qtrue );
860 	if ( !token[0] ) {
861 		return qtrue;
862 	}
863 	if ( !Q_stricmp( token, "female" ) ) {
864 		voiceChatList->gender = GENDER_FEMALE;
865 	} else if ( !Q_stricmp( token, "male" ) )        {
866 		voiceChatList->gender = GENDER_MALE;
867 	} else if ( !Q_stricmp( token, "neuter" ) )        {
868 		voiceChatList->gender = GENDER_NEUTER;
869 	} else {
870 		trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) );
871 		return qfalse;
872 	}
873 
874 	voiceChatList->numVoiceChats = 0;
875 	while ( 1 ) {
876 		token = COM_ParseExt( p, qtrue );
877 		if ( !token[0] ) {
878 			return qtrue;
879 		}
880 		Com_sprintf( voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token );
881 		token = COM_ParseExt( p, qtrue );
882 		if ( Q_stricmp( token, "{" ) ) {
883 			trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) );
884 			return qfalse;
885 		}
886 		voiceChats[voiceChatList->numVoiceChats].numSounds = 0;
887 		current = voiceChats[voiceChatList->numVoiceChats].numSounds;
888 
889 		while ( 1 ) {
890 			token = COM_ParseExt( p, qtrue );
891 			if ( !token[0] ) {
892 				return qtrue;
893 			}
894 			if ( !Q_stricmp( token, "}" ) ) {
895 				break;
896 			}
897 			voiceChats[voiceChatList->numVoiceChats].sounds[current] = trap_S_RegisterSound( token );
898 			token = COM_ParseExt( p, qtrue );
899 			if ( !token[0] ) {
900 				return qtrue;
901 			}
902 			Com_sprintf( voiceChats[voiceChatList->numVoiceChats].chats[current], MAX_CHATSIZE, "%s", token );
903 
904 			// DHM - Nerve :: Specify sprite shader to show above player's head
905 			token = COM_ParseExt( p, qfalse );
906 			if ( !token[0] || !Q_stricmp( token, "}" ) ) {
907 				voiceChats[voiceChatList->numVoiceChats].sprite[current] = trap_R_RegisterShader( "sprites/voiceChat" );
908 				COM_RestoreParseSession( p );
909 			} else {
910 				voiceChats[voiceChatList->numVoiceChats].sprite[current] = trap_R_RegisterShader( token );
911 				if ( voiceChats[voiceChatList->numVoiceChats].sprite[current] == 0 ) {
912 					voiceChats[voiceChatList->numVoiceChats].sprite[current] = trap_R_RegisterShader( "sprites/voiceChat" );
913 				}
914 			}
915 			// dhm - end
916 
917 			voiceChats[voiceChatList->numVoiceChats].numSounds++;
918 			current = voiceChats[voiceChatList->numVoiceChats].numSounds;
919 
920 			if ( voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS ) {
921 				break;
922 			}
923 		}
924 
925 		voiceChatList->numVoiceChats++;
926 		if ( voiceChatList->numVoiceChats >= maxVoiceChats ) {
927 			return qtrue;
928 		}
929 	}
930 	return qtrue;
931 }
932 
933 /*
934 =================
935 CG_LoadVoiceChats
936 =================
937 */
CG_LoadVoiceChats(void)938 void CG_LoadVoiceChats( void ) {
939 	int size;
940 
941 	size = trap_MemoryRemaining();
942 	CG_ParseVoiceChats( "scripts/wm_axis_chat.voice", &voiceChatLists[0], MAX_VOICECHATS );
943 	CG_ParseVoiceChats( "scripts/wm_allies_chat.voice", &voiceChatLists[1], MAX_VOICECHATS );
944 //	CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS );
945 //	CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS );
946 //	CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS );
947 //	CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS );
948 //	CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS );
949 //	CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS );
950 //	CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS );
951 	CG_Printf( "voice chat memory size = %d\n", size - trap_MemoryRemaining() );
952 }
953 
954 /*
955 =================
956 CG_HeadModelVoiceChats
957 =================
958 */
CG_HeadModelVoiceChats(char * filename)959 int CG_HeadModelVoiceChats( char *filename ) {
960 	int len, i;
961 	fileHandle_t f;
962 	char buf[MAX_VOICEFILESIZE];
963 	char **p, *ptr;
964 	char *token;
965 
966 	len = trap_FS_FOpenFile( filename, &f, FS_READ );
967 	if ( !f ) {
968 		trap_Print( va( "voice chat file not found: %s\n", filename ) );
969 		return -1;
970 	}
971 	if ( len >= MAX_VOICEFILESIZE ) {
972 		trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i\n", filename, len, MAX_VOICEFILESIZE ) );
973 		trap_FS_FCloseFile( f );
974 		return -1;
975 	}
976 
977 	trap_FS_Read( buf, len, f );
978 	buf[len] = 0;
979 	trap_FS_FCloseFile( f );
980 
981 	ptr = buf;
982 	p = &ptr;
983 
984 	token = COM_ParseExt( p, qtrue );
985 	if ( !token[0] ) {
986 		return -1;
987 	}
988 
989 	for ( i = 0; i < MAX_VOICEFILES; i++ ) {
990 		if ( !Q_stricmp( token, voiceChatLists[i].name ) ) {
991 			return i;
992 		}
993 	}
994 
995 	//FIXME: maybe try to load the .voice file which name is stored in token?
996 
997 	return -1;
998 }
999 
1000 
1001 /*
1002 =================
1003 CG_GetVoiceChat
1004 =================
1005 */
CG_GetVoiceChat(voiceChatList_t * voiceChatList,const char * id,sfxHandle_t * snd,qhandle_t * sprite,char ** chat)1006 int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, qhandle_t *sprite, char **chat ) {
1007 	int i, rnd;
1008 
1009 	for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) {
1010 		if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) {
1011 			rnd = random() * voiceChatList->voiceChats[i].numSounds;
1012 			*snd = voiceChatList->voiceChats[i].sounds[rnd];
1013 			*sprite = voiceChatList->voiceChats[i].sprite[rnd];
1014 			*chat = voiceChatList->voiceChats[i].chats[rnd];
1015 			return qtrue;
1016 		}
1017 	}
1018 	return qfalse;
1019 }
1020 
1021 /*
1022 =================
1023 CG_VoiceChatListForClient
1024 =================
1025 */
CG_VoiceChatListForClient(int clientNum)1026 voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) {
1027 	clientInfo_t *ci;
1028 	int voiceChatNum, i, j, k, gender;
1029 	char filename[128], *headModelName;
1030 
1031 	// NERVE - SMF
1032 	if ( cgs.gametype >= GT_WOLF ) {
1033 		if ( cgs.clientinfo[ clientNum ].team == TEAM_RED ) {
1034 			return &voiceChatLists[0];
1035 		} else {
1036 			return &voiceChatLists[1];
1037 		}
1038 	}
1039 	// -NERVE - SMF
1040 
1041 	if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
1042 		clientNum = 0;
1043 	}
1044 	ci = &cgs.clientinfo[ clientNum ];
1045 
1046 	headModelName = ci->headModelName;
1047 	if ( headModelName[0] == '*' ) {
1048 		headModelName++;
1049 	}
1050 	// find the voice file for the head model the client uses
1051 	for ( i = 0; i < MAX_HEADMODELS; i++ ) {
1052 		if ( !Q_stricmp( headModelVoiceChat[i].headmodel, headModelName ) ) {
1053 			break;
1054 		}
1055 	}
1056 	if ( i < MAX_HEADMODELS ) {
1057 		return &voiceChatLists[headModelVoiceChat[i].voiceChatNum];
1058 	}
1059 	// find a <headmodelname>.vc file
1060 	for ( i = 0; i < MAX_HEADMODELS; i++ ) {
1061 		if ( !strlen( headModelVoiceChat[i].headmodel ) ) {
1062 			Com_sprintf( filename, sizeof( filename ), "scripts/%s.vc", headModelName );
1063 			voiceChatNum = CG_HeadModelVoiceChats( filename );
1064 			if ( voiceChatNum == -1 ) {
1065 				break;
1066 			}
1067 			Com_sprintf( headModelVoiceChat[i].headmodel, sizeof( headModelVoiceChat[i].headmodel ),
1068 						 "%s", headModelName );
1069 			headModelVoiceChat[i].voiceChatNum = voiceChatNum;
1070 			return &voiceChatLists[headModelVoiceChat[i].voiceChatNum];
1071 		}
1072 	}
1073 	gender = ci->gender;
1074 	for ( k = 0; k < 2; k++ ) {
1075 		// just pick the first with the right gender
1076 		for ( i = 0; i < MAX_VOICEFILES; i++ ) {
1077 			if ( strlen( voiceChatLists[i].name ) ) {
1078 				if ( voiceChatLists[i].gender == gender ) {
1079 					// store this head model with voice chat for future reference
1080 					for ( j = 0; j < MAX_HEADMODELS; j++ ) {
1081 						if ( !strlen( headModelVoiceChat[j].headmodel ) ) {
1082 							Com_sprintf( headModelVoiceChat[j].headmodel, sizeof( headModelVoiceChat[j].headmodel ),
1083 										 "%s", headModelName );
1084 							headModelVoiceChat[j].voiceChatNum = i;
1085 							break;
1086 						}
1087 					}
1088 					return &voiceChatLists[i];
1089 				}
1090 			}
1091 		}
1092 		// fall back to male gender because we don't have neuter in the mission pack
1093 		if ( gender == GENDER_MALE ) {
1094 			break;
1095 		}
1096 		gender = GENDER_MALE;
1097 	}
1098 	// store this head model with voice chat for future reference
1099 	for ( j = 0; j < MAX_HEADMODELS; j++ ) {
1100 		if ( !strlen( headModelVoiceChat[j].headmodel ) ) {
1101 			Com_sprintf( headModelVoiceChat[j].headmodel, sizeof( headModelVoiceChat[j].headmodel ),
1102 						 "%s", headModelName );
1103 			headModelVoiceChat[j].voiceChatNum = 0;
1104 			break;
1105 		}
1106 	}
1107 	// just return the first voice chat list
1108 	return &voiceChatLists[0];
1109 }
1110 
1111 #define MAX_VOICECHATBUFFER     32
1112 
1113 typedef struct bufferedVoiceChat_s
1114 {
1115 	int clientNum;
1116 	sfxHandle_t snd;
1117 	qhandle_t sprite;
1118 	int voiceOnly;
1119 	char cmd[MAX_SAY_TEXT];
1120 	char message[MAX_SAY_TEXT];
1121 	vec3_t origin;          // NERVE - SMF
1122 } bufferedVoiceChat_t;
1123 
1124 bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER];
1125 
1126 /*
1127 =================
1128 CG_PlayVoiceChat
1129 =================
1130 */
CG_PlayVoiceChat(bufferedVoiceChat_t * vchat)1131 void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) {
1132 	// if we are going into the intermission, don't start any voices
1133 /*	// NERVE - SMF - don't do this in wolfMP
1134 	if ( cg.intermissionStarted ) {
1135 		return;
1136 	}
1137 */
1138 
1139 	if ( !cg_noVoiceChats.integer ) {
1140 		trap_S_StartLocalSound( vchat->snd, CHAN_VOICE );
1141 
1142 		// DHM - Nerve :: Show icon above head
1143 		if ( vchat->clientNum == cg.snap->ps.clientNum ) {
1144 			cg.predictedPlayerEntity.voiceChatSprite = vchat->sprite;
1145 			if ( vchat->sprite == cgs.media.voiceChatShader ) {
1146 				cg.predictedPlayerEntity.voiceChatSpriteTime = cg.time + cg_voiceSpriteTime.integer;
1147 			} else {
1148 				cg.predictedPlayerEntity.voiceChatSpriteTime = cg.time + cg_voiceSpriteTime.integer * 2;
1149 			}
1150 		} else {
1151 			cg_entities[ vchat->clientNum ].voiceChatSprite = vchat->sprite;
1152 			VectorCopy( vchat->origin, cg_entities[ vchat->clientNum ].lerpOrigin );            // NERVE - SMF
1153 			if ( vchat->sprite == cgs.media.voiceChatShader ) {
1154 				cg_entities[ vchat->clientNum ].voiceChatSpriteTime = cg.time + cg_voiceSpriteTime.integer;
1155 			} else {
1156 				cg_entities[ vchat->clientNum ].voiceChatSpriteTime = cg.time + cg_voiceSpriteTime.integer * 2;
1157 			}
1158 		}
1159 		// dhm - end
1160 
1161 #ifdef MISSIONPACK
1162 		if ( vchat->clientNum != cg.snap->ps.clientNum ) {
1163 			int orderTask = CG_ValidOrder( vchat->cmd );
1164 			if ( orderTask > 0 ) {
1165 				cgs.acceptOrderTime = cg.time + 5000;
1166 				Q_strncpyz( cgs.acceptVoice, vchat->cmd, sizeof( cgs.acceptVoice ) );
1167 				cgs.acceptTask = orderTask;
1168 				cgs.acceptLeader = vchat->clientNum;
1169 			}
1170 			// see if this was an order
1171 			CG_ShowResponseHead();
1172 		}
1173 #endif
1174 	}
1175 	if ( !vchat->voiceOnly && !cg_noVoiceText.integer ) {
1176 		CG_AddToTeamChat( vchat->message );
1177 		CG_Printf( "%s", va( "[skipnotify]: %s\n", vchat->message ) ); // JPW NERVE
1178 	}
1179 	voiceChatBuffer[cg.voiceChatBufferOut].snd = 0;
1180 }
1181 
1182 /*
1183 =====================
1184 CG_PlayBufferedVoieChats
1185 =====================
1186 */
CG_PlayBufferedVoiceChats(void)1187 void CG_PlayBufferedVoiceChats( void ) {
1188 	if ( cg.voiceChatTime < cg.time ) {
1189 		if ( cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd ) {
1190 			//
1191 			CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] );
1192 			//
1193 			cg.voiceChatBufferOut = ( cg.voiceChatBufferOut + 1 ) % MAX_VOICECHATBUFFER;
1194 			cg.voiceChatTime = cg.time + 1000;
1195 		}
1196 	}
1197 }
1198 
1199 /*
1200 =====================
1201 CG_AddBufferedVoiceChat
1202 =====================
1203 */
CG_AddBufferedVoiceChat(bufferedVoiceChat_t * vchat)1204 void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) {
1205 	// if we are going into the intermission, don't start any voices
1206 /*	// NERVE - SMF - don't do this in wolfMP
1207 	if ( cg.intermissionStarted ) {
1208 		return;
1209 	}
1210 */
1211 
1212 // JPW NERVE new system doesn't buffer but overwrites vchats FIXME put this on a cvar to choose which to use
1213 	memcpy( &voiceChatBuffer[0],vchat,sizeof( bufferedVoiceChat_t ) );
1214 	cg.voiceChatBufferIn = 0;
1215 	CG_PlayVoiceChat( &voiceChatBuffer[0] );
1216 
1217 /* JPW NERVE pulled this
1218 	memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t));
1219 	cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER;
1220 	if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) {
1221 		CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] );
1222 		cg.voiceChatBufferOut++;
1223 	}
1224 */
1225 }
1226 
1227 /*
1228 =================
1229 CG_VoiceChatLocal
1230 =================
1231 */
CG_VoiceChatLocal(int mode,qboolean voiceOnly,int clientNum,int color,const char * cmd,vec3_t origin)1232 void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd, vec3_t origin ) {
1233 	char *chat;
1234 	voiceChatList_t *voiceChatList;
1235 	clientInfo_t *ci;
1236 	sfxHandle_t snd;
1237 	qhandle_t sprite;
1238 	bufferedVoiceChat_t vchat;
1239 	const char *loc;            // NERVE - SMF
1240 
1241 /*	// NERVE - SMF - don't do this in wolfMP
1242 	// if we are going into the intermission, don't start any voices
1243 	if ( cg.intermissionStarted ) {
1244 		return;
1245 	}
1246 */
1247 
1248 	if ( mode == SAY_ALL && cgs.gametype >= GT_TEAM && cg_teamChatsOnly.integer ) {
1249 		return;
1250 	}
1251 
1252 	if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
1253 		clientNum = 0;
1254 	}
1255 	ci = &cgs.clientinfo[ clientNum ];
1256 
1257 	cgs.currentVoiceClient = clientNum;
1258 
1259 	voiceChatList = CG_VoiceChatListForClient( clientNum );
1260 
1261 	if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &sprite, &chat ) ) {
1262 		vchat.clientNum = clientNum;
1263 		vchat.snd = snd;
1264 		vchat.sprite = sprite;
1265 		vchat.voiceOnly = voiceOnly;
1266 		VectorCopy( origin, vchat.origin );     // NERVE - SMF
1267 		Q_strncpyz( vchat.cmd, cmd, sizeof( vchat.cmd ) );
1268 
1269 		// NERVE - SMF - get location
1270 		loc = CG_ConfigString( CS_LOCATIONS + ci->location );
1271 		if ( !loc || !*loc ) {
1272 			loc = " ";
1273 		}
1274 		// -NERVE - SMF
1275 
1276 		if ( mode == SAY_TELL ) {
1277 			Com_sprintf( vchat.message, sizeof( vchat.message ), "[%s]%c%c[%s]: %c%c%s",
1278 						 ci->name, Q_COLOR_ESCAPE, COLOR_YELLOW, CG_TranslateString( loc ), Q_COLOR_ESCAPE, color, CG_TranslateString( chat ) );
1279 		} else if ( mode == SAY_TEAM )   {
1280 			Com_sprintf( vchat.message, sizeof( vchat.message ), "(%s)%c%c(%s): %c%c%s",
1281 						 ci->name, Q_COLOR_ESCAPE, COLOR_YELLOW, CG_TranslateString( loc ), Q_COLOR_ESCAPE, color, CG_TranslateString( chat ) );
1282 		} else {
1283 			Com_sprintf( vchat.message, sizeof( vchat.message ), "%s %c%c(%s): %c%c%s",
1284 						 ci->name, Q_COLOR_ESCAPE, COLOR_YELLOW, CG_TranslateString( loc ), Q_COLOR_ESCAPE, color, CG_TranslateString( chat ) );
1285 		}
1286 		CG_AddBufferedVoiceChat( &vchat );
1287 	}
1288 }
1289 
1290 /*
1291 =================
1292 CG_VoiceChat
1293 =================
1294 */
CG_VoiceChat(int mode)1295 void CG_VoiceChat( int mode ) {
1296 	const char *cmd;
1297 	int clientNum, color;
1298 	qboolean voiceOnly;
1299 	vec3_t origin;          // NERVE - SMF
1300 
1301 	voiceOnly = atoi( CG_Argv( 1 ) );
1302 	clientNum = atoi( CG_Argv( 2 ) );
1303 	color = atoi( CG_Argv( 3 ) );
1304 
1305 	// NERVE - SMF - added origin
1306 	origin[0] = atoi( CG_Argv( 5 ) );
1307 	origin[1] = atoi( CG_Argv( 6 ) );
1308 	origin[2] = atoi( CG_Argv( 7 ) );
1309 
1310 	cmd = CG_Argv( 4 );
1311 
1312 	if ( cg_noTaunt.integer != 0 ) {
1313 		if ( !strcmp( cmd, VOICECHAT_KILLINSULT )  || !strcmp( cmd, VOICECHAT_TAUNT ) || \
1314 			 !strcmp( cmd, VOICECHAT_DEATHINSULT ) || !strcmp( cmd, VOICECHAT_KILLGAUNTLET ) ||	\
1315 			 !strcmp( cmd, VOICECHAT_PRAISE ) ) {
1316 			return;
1317 		}
1318 	}
1319 
1320 	CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd, origin );
1321 }
1322 // -NERVE - SMF
1323 
1324 /*
1325 =================
1326 CG_RemoveChatEscapeChar
1327 =================
1328 */
CG_RemoveChatEscapeChar(char * text)1329 static void CG_RemoveChatEscapeChar( char *text ) {
1330 	int i, l;
1331 
1332 	l = 0;
1333 	for ( i = 0; text[i]; i++ ) {
1334 		if ( text[i] == '\x19' ) {
1335 			continue;
1336 		}
1337 		text[l++] = text[i];
1338 	}
1339 	text[l] = '\0';
1340 }
1341 
1342 /*
1343 =================
1344 CG_LocalizeServerCommand
1345 
1346 NERVE - SMF - localize string sent from server
1347 
1348 - localization is ON by default.
1349 - use [lof] in string to turn OFF
1350 - use [lon] in string to turn back ON
1351 =================
1352 */
CG_LocalizeServerCommand(const char * buf)1353 const char* CG_LocalizeServerCommand( const char *buf ) {
1354 	static char token[MAX_TOKEN_CHARS];
1355 	char temp[MAX_TOKEN_CHARS];
1356 	qboolean togloc = qtrue;
1357 	const char *s;
1358 	int i, prev;
1359 
1360 	memset( token, 0, sizeof( token ) );
1361 	s = buf;
1362 	prev = 0;
1363 
1364 	for ( i = 0; *s; i++, s++ ) {
1365 		// TTimo:
1366 		// line was: if ( *s == '[' && !Q_strncmp( s, "[lon]", 5 ) || !Q_strncmp( s, "[lof]", 5 ) ) {
1367 		// || prevails on &&, gcc warning was 'suggest parentheses around && within ||'
1368 		// modified to the correct behaviour
1369 		if ( *s == '[' && ( !Q_strncmp( s, "[lon]", 5 ) || !Q_strncmp( s, "[lof]", 5 ) ) ) {
1370 
1371 			if ( togloc ) {
1372 				memset( temp, 0, sizeof( temp ) );
1373 				strncpy( temp, buf + prev, i - prev );
1374 				strcat( token, CG_TranslateString( temp ) );
1375 			} else {
1376 				strncat( token, buf + prev, i - prev );
1377 			}
1378 
1379 			if ( s[3] == 'n' ) {
1380 				togloc = qtrue;
1381 			} else {
1382 				togloc = qfalse;
1383 			}
1384 
1385 			i += 5;
1386 			s += 5;
1387 			prev = i;
1388 		}
1389 	}
1390 
1391 	if ( togloc ) {
1392 		memset( temp, 0, sizeof( temp ) );
1393 		strncpy( temp, buf + prev, i - prev );
1394 		strcat( token, CG_TranslateString( temp ) );
1395 	} else {
1396 		strncat( token, buf + prev, i - prev );
1397 	}
1398 
1399 	return token;
1400 }
1401 // -NERVE - SMF
1402 
1403 /*
1404 =================
1405 CG_ServerCommand
1406 
1407 The string has been tokenized and can be retrieved with
1408 Cmd_Argc() / Cmd_Argv()
1409 =================
1410 */
CG_ServerCommand(void)1411 static void CG_ServerCommand( void ) {
1412 	const char  *cmd;
1413 	char text[MAX_SAY_TEXT];
1414 
1415 	cmd = CG_Argv( 0 );
1416 
1417 	if ( !cmd[0] ) {
1418 		// server claimed the command
1419 		return;
1420 	}
1421 
1422 //	if ( !strcmp( cmd, "startCam" ) ) {
1423 //		CG_StartCamera( CG_Argv( 1 ), atoi( CG_Argv( 2 ) ) );
1424 //		return;
1425 //	}
1426 
1427 	if ( !strcmp( cmd, "mvspd" ) ) {
1428 		CG_RequestMoveSpeed( CG_Argv( 1 ) );
1429 		return;
1430 	}
1431 
1432 	if ( !strcmp( cmd, "tinfo" ) ) {
1433 		CG_ParseTeamInfo();
1434 		return;
1435 	}
1436 
1437 	if ( !strcmp( cmd, "scores" ) ) {
1438 		CG_ParseScores();
1439 		return;
1440 	}
1441 
1442 	if ( !strcmp( cmd, "cp" ) ) {
1443 		// NERVE - SMF
1444 		int args = trap_Argc();
1445 		char *s;
1446 
1447 		if ( args >= 3 ) {
1448 			s = CG_TranslateString( CG_Argv( 1 ) );
1449 
1450 			if ( args == 4 ) {
1451 				s = va( "%s%s", CG_Argv( 3 ), s );
1452 			}
1453 
1454 			CG_PriorityCenterPrint( s, SCREEN_HEIGHT - ( SCREEN_HEIGHT * 0.25 ), SMALLCHAR_WIDTH, atoi( CG_Argv( 2 ) ) );
1455 		} else {
1456 			CG_CenterPrint( CG_LocalizeServerCommand( CG_Argv( 1 ) ), SCREEN_HEIGHT - ( SCREEN_HEIGHT * 0.25 ), SMALLCHAR_WIDTH );  //----(SA)	modified
1457 		}
1458 		return;
1459 	}
1460 
1461 	if ( !strcmp( cmd, "cs" ) ) {
1462 		CG_ConfigStringModified();
1463 		return;
1464 	}
1465 
1466 	// NERVE - SMF
1467 	if ( !strcmp( cmd, "shake" ) ) {
1468 		CG_StartShakeCamera( atof( CG_Argv( 1 ) ) );
1469 		return;
1470 	}
1471 	// -NERVE - SMF
1472 
1473 	if ( !strcmp( cmd, "print" ) ) {
1474 		CG_Printf( "[cgnotify]%s", CG_LocalizeServerCommand( CG_Argv( 1 ) ) );
1475 #ifdef MISSIONPACK
1476 		cmd = CG_Argv( 1 );           // yes, this is obviously a hack, but so is the way we hear about
1477 									  // votes passing or failing
1478 		if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 ) ) {
1479 			trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER );
1480 		} else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) {
1481 			trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER );
1482 		}
1483 #endif
1484 		return;
1485 	}
1486 
1487 	if ( !strcmp( cmd, "chat" ) ) {
1488 		const char *s;
1489 
1490 		if ( cgs.gametype >= GT_TEAM && cg_teamChatsOnly.integer ) {
1491 			return;
1492  		}
1493 
1494 		if ( atoi( CG_Argv( 2 ) ) ) {
1495 			s = CG_LocalizeServerCommand( CG_Argv( 1 ) );
1496 		} else {
1497 			s = CG_Argv( 1 );
1498 		}
1499 
1500 		trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
1501 		Q_strncpyz( text, s, MAX_SAY_TEXT );
1502 		CG_RemoveChatEscapeChar( text );
1503 		CG_AddToTeamChat( text ); // JPW NERVE
1504 		CG_Printf( "[skipnotify]%s\n", text ); // JPW NERVE
1505 
1506 		// NERVE - SMF - we also want this to display in limbo chat
1507 		if ( cgs.gametype >= GT_WOLF ) {
1508 			trap_UI_LimboChat( text );
1509 		}
1510 
1511 		return;
1512 	}
1513 
1514 	if ( !strcmp( cmd, "tchat" ) ) {
1515 		const char *s;
1516 
1517 		if ( atoi( CG_Argv( 2 ) ) ) {
1518 			s = CG_LocalizeServerCommand( CG_Argv( 1 ) );
1519 		} else {
1520 			s = CG_Argv( 1 );
1521 		}
1522 
1523 		trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
1524 		Q_strncpyz( text, s, MAX_SAY_TEXT );
1525 		CG_RemoveChatEscapeChar( text );
1526 		CG_AddToTeamChat( text );
1527 		CG_Printf( "[skipnotify]%s\n", text ); // JPW NERVE
1528 
1529 		// NERVE - SMF - we also want this to display in limbo chat
1530 		if ( cgs.gametype >= GT_WOLF ) {
1531 			trap_UI_LimboChat( text );
1532 		}
1533 
1534 		return;
1535 	}
1536 
1537 	if ( !strcmp( cmd, "vchat" ) ) {
1538 		CG_VoiceChat( SAY_ALL );            // NERVE - SMF - enabled support
1539 		return;
1540 	}
1541 
1542 	if ( !strcmp( cmd, "vtchat" ) ) {
1543 		CG_VoiceChat( SAY_TEAM );           // NERVE - SMF - enabled support
1544 		return;
1545 	}
1546 
1547 	if ( !strcmp( cmd, "vtell" ) ) {
1548 		CG_VoiceChat( SAY_TELL );           // NERVE - SMF - enabled support
1549 		return;
1550 	}
1551 
1552 	// NERVE - SMF - limbo chat
1553 	if ( !strcmp( cmd, "lchat" ) ) {
1554 		trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
1555 		Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
1556 		CG_RemoveChatEscapeChar( text );
1557 		trap_UI_LimboChat( text );
1558 		CG_Printf( "[skipnotify]%s\n", text ); // JPW NERVE
1559 		return;
1560 	}
1561 	// -NERVE - SMF
1562 
1563 	// NERVE - SMF
1564 	if ( !Q_stricmp( cmd, "oid" ) ) {
1565 		CG_ObjectivePrint( CG_Argv( 1 ), SMALLCHAR_WIDTH );
1566 		return;
1567 	}
1568 	// -NERVE - SMF
1569 
1570 	// DHM - Nerve :: Allow client to lodge a complaing
1571 	if ( !Q_stricmp( cmd, "complaint" ) ) {
1572 		cgs.complaintEndTime = cg.time + 20000;
1573 		cgs.complaintClient = atoi( CG_Argv( 1 ) );
1574 
1575 		if ( cgs.complaintClient < 0 ) {
1576 			cgs.complaintEndTime = cg.time + 10000;
1577 		}
1578 
1579 		return;
1580 	}
1581 	// dhm
1582 
1583 	if ( !strcmp( cmd, "map_restart" ) ) {
1584 		CG_MapRestart();
1585 		return;
1586 	}
1587 
1588 //	if ( !strcmp( cmd, "startCam" ) ) {
1589 //		CG_StartCamera( CG_Argv( 1 ), atoi( CG_Argv( 2 ) ) );
1590 //		return;
1591 //	}
1592 
1593 	if ( !strcmp( cmd, "mvspd" ) ) {
1594 		CG_RequestMoveSpeed( CG_Argv( 1 ) );
1595 		return;
1596 	}
1597 
1598 	if ( Q_stricmp (cmd, "remapShader") == 0 )
1599 	{
1600 		if (trap_Argc() == 4)
1601 		{
1602 			char shader1[MAX_QPATH];
1603 			char shader2[MAX_QPATH];
1604 			char shader3[MAX_QPATH];
1605 
1606 			Q_strncpyz(shader1, CG_Argv(1), sizeof(shader1));
1607 			Q_strncpyz(shader2, CG_Argv(2), sizeof(shader2));
1608 			Q_strncpyz(shader3, CG_Argv(3), sizeof(shader3));
1609 
1610 			trap_R_RemapShader(shader1, shader2, shader3);
1611 		}
1612 
1613 		return;
1614 	}
1615 
1616 	// loaddeferred can be both a servercmd and a consolecmd
1617 	if ( !strcmp( cmd, "loaddeferred" ) ) {  // spelling fixed (SA)
1618 		CG_LoadDeferredPlayers();
1619 		return;
1620 	}
1621 
1622 	// clientLevelShot is sent before taking a special screenshot for
1623 	// the menu system during development
1624 	if ( !strcmp( cmd, "clientLevelShot" ) ) {
1625 		cg.levelShot = qtrue;
1626 		return;
1627 	}
1628 
1629 	CG_Printf( "Unknown client game command: %s\n", cmd );
1630 }
1631 
1632 
1633 /*
1634 ====================
1635 CG_ExecuteNewServerCommands
1636 
1637 Execute all of the server commands that were received along
1638 with this this snapshot.
1639 ====================
1640 */
CG_ExecuteNewServerCommands(int latestSequence)1641 void CG_ExecuteNewServerCommands( int latestSequence ) {
1642 	while ( cgs.serverCommandSequence < latestSequence ) {
1643 		if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) {
1644 			CG_ServerCommand();
1645 		}
1646 	}
1647 }
1648