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