cg_servercmds.c (29352B)
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 Foobar; 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" // bk001205 - for Q3_ui as well 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 // bk001204 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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_MODELS ) { 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 if (trap_Argc() == 4) { 1063 trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); 1064 } 1065 } 1066 1067 // loaddeferred can be both a servercmd and a consolecmd 1068 if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo 1069 CG_LoadDeferredPlayers(); 1070 return; 1071 } 1072 1073 // clientLevelShot is sent before taking a special screenshot for 1074 // the menu system during development 1075 if ( !strcmp( cmd, "clientLevelShot" ) ) { 1076 cg.levelShot = qtrue; 1077 return; 1078 } 1079 1080 CG_Printf( "Unknown client game command: %s\n", cmd ); 1081 } 1082 1083 1084 /* 1085 ==================== 1086 CG_ExecuteNewServerCommands 1087 1088 Execute all of the server commands that were received along 1089 with this this snapshot. 1090 ==================== 1091 */ 1092 void CG_ExecuteNewServerCommands( int latestSequence ) { 1093 while ( cgs.serverCommandSequence < latestSequence ) { 1094 if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { 1095 CG_ServerCommand(); 1096 } 1097 } 1098 }