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