// Copyright (C) 1999-2000 Id Software, Inc. // // cg_servercmds.c -- reliably sequenced text commands sent by the server // these are processed at snapshot transition time, so there will definately // be a valid snapshot this frame #include "cg_local.h" #include "../../ui/menudef.h" #if !defined(CL_LIGHT_H_INC) #include "cg_lights.h" #endif typedef struct { const char *order; int taskNum; } orderTask_t; static const orderTask_t validOrders[] = { { VOICECHAT_GETFLAG, TEAMTASK_OFFENSE }, { VOICECHAT_OFFENSE, TEAMTASK_OFFENSE }, { VOICECHAT_DEFEND, TEAMTASK_DEFENSE }, { VOICECHAT_DEFENDFLAG, TEAMTASK_DEFENSE }, { VOICECHAT_PATROL, TEAMTASK_PATROL }, { VOICECHAT_CAMP, TEAMTASK_CAMP }, { VOICECHAT_FOLLOWME, TEAMTASK_FOLLOW }, { VOICECHAT_RETURNFLAG, TEAMTASK_RETRIEVE }, { VOICECHAT_FOLLOWFLAGCARRIER, TEAMTASK_ESCORT } }; static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t); static int CG_ValidOrder(const char *p) { int i; for (i = 0; i < numValidOrders; i++) { if (Q_stricmp(p, validOrders[i].order) == 0) { return validOrders[i].taskNum; } } return -1; } /* ================= CG_ParseScores ================= */ static void CG_ParseScores( void ) { int i, powerups, readScores; cg.numScores = atoi( CG_Argv( 1 ) ); readScores = cg.numScores; if (readScores > MAX_CLIENT_SCORE_SEND) { readScores = MAX_CLIENT_SCORE_SEND; } if ( cg.numScores > MAX_CLIENTS ) { cg.numScores = MAX_CLIENTS; } cg.numScores = readScores; cg.teamScores[0] = atoi( CG_Argv( 2 ) ); cg.teamScores[1] = atoi( CG_Argv( 3 ) ); memset( cg.scores, 0, sizeof( cg.scores ) ); for ( i = 0 ; i < readScores ; i++ ) { // cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) ); cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) ); cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) ); cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) ); cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) ); powerups = atoi( CG_Argv( i * 14 + 9 ) ); cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17)); if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { cg.scores[i].client = 0; } cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; } CG_SetScoreSelection(NULL); } /* ================= CG_ParseTeamInfo ================= */ static void CG_ParseTeamInfo( void ) { int i; int client; numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { client = atoi( CG_Argv( i * 6 + 2 ) ); sortedTeamPlayers[i] = client; cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); } } /* ================ CG_ParseServerinfo This is called explicitly when the gamestate is first received, and whenever the server updates any serverinfo flagged cvars ================ */ void CG_ParseServerinfo( void ) { const char *info; char *mapname; info = CG_ConfigString( CS_SERVERINFO ); cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); cgs.needpass = atoi( Info_ValueForKey( info, "needpass" ) ); cgs.jediVmerc = atoi( Info_ValueForKey( info, "g_jediVmerc" ) ); cgs.wDisable = atoi( Info_ValueForKey( info, "wdisable" ) ); cgs.fDisable = atoi( Info_ValueForKey( info, "fdisable" ) ); cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); cgs.duel_fraglimit = atoi( Info_ValueForKey( info, "duel_fraglimit" ) ); cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); mapname = Info_ValueForKey( info, "mapname" ); //rww - You must do this one here, Info_ValueForKey always uses the same memory pointer. trap_Cvar_Set ( "ui_about_mapname", mapname ); Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); trap_Cvar_Set("g_redTeam", cgs.redTeam); Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) ); trap_Cvar_Set("g_blueTeam", cgs.blueTeam); trap_Cvar_Set ( "ui_about_gametype", va("%i", cgs.gametype ) ); trap_Cvar_Set ( "ui_about_fraglimit", va("%i", cgs.fraglimit ) ); trap_Cvar_Set ( "ui_about_duellimit", va("%i", cgs.duel_fraglimit ) ); trap_Cvar_Set ( "ui_about_capturelimit", va("%i", cgs.capturelimit ) ); trap_Cvar_Set ( "ui_about_timelimit", va("%i", cgs.timelimit ) ); trap_Cvar_Set ( "ui_about_maxclients", va("%i", cgs.maxclients ) ); trap_Cvar_Set ( "ui_about_dmflags", va("%i", cgs.dmflags ) ); trap_Cvar_Set ( "ui_about_hostname", Info_ValueForKey( info, "sv_hostname" ) ); trap_Cvar_Set ( "ui_about_needpass", Info_ValueForKey( info, "g_needpass" ) ); trap_Cvar_Set ( "ui_about_botminplayers", Info_ValueForKey ( info, "bot_minplayers" ) ); } /* ================== CG_ParseWarmup ================== */ static void CG_ParseWarmup( void ) { const char *info; int warmup; info = CG_ConfigString( CS_WARMUP ); warmup = atoi( info ); cg.warmupCount = -1; cg.warmup = warmup; } /* ================ CG_SetConfigValues Called on load to set the initial values from configure strings ================ */ void CG_SetConfigValues( void ) { const char *s; const char *str; cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { s = CG_ConfigString( CS_FLAGSTATUS ); cgs.redflag = s[0] - '0'; cgs.blueflag = s[1] - '0'; } cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); // Track who the jedi master is cgs.jediMaster = atoi ( CG_ConfigString ( CS_CLIENT_JEDIMASTER ) ); cgs.duelWinner = atoi ( CG_ConfigString ( CS_CLIENT_DUELWINNER ) ); str = CG_ConfigString(CS_CLIENT_DUELISTS); if (str && str[0]) { char buf[64]; int c = 0; int i = 0; while (str[i] && str[i] != '|') { buf[c] = str[i]; c++; i++; } buf[c] = 0; cgs.duelist1 = atoi ( buf ); c = 0; i++; while (str[i]) { buf[c] = str[i]; c++; i++; } buf[c] = 0; cgs.duelist2 = atoi ( buf ); } } /* ===================== CG_ShaderStateChanged ===================== */ void CG_ShaderStateChanged(void) { char originalShader[MAX_QPATH]; char newShader[MAX_QPATH]; char timeOffset[16]; const char *o; char *n,*t; o = CG_ConfigString( CS_SHADERSTATE ); while (o && *o) { n = strstr(o, "="); if (n && *n) { strncpy(originalShader, o, n-o); originalShader[n-o] = 0; n++; t = strstr(n, ":"); if (t && *t) { strncpy(newShader, n, t-n); newShader[t-n] = 0; } else { break; } t++; o = strstr(t, "@"); if (o) { strncpy(timeOffset, t, o-t); timeOffset[o-t] = 0; o++; trap_R_RemapShader( originalShader, newShader, timeOffset ); } } else { break; } } } /* ================ CG_ConfigStringModified ================ */ static void CG_ConfigStringModified( void ) { const char *str; int num; num = atoi( CG_Argv( 1 ) ); // get the gamestate from the client system, which will have the // new configstring already integrated trap_GetGameState( &cgs.gameState ); // look up the individual string that was modified str = CG_ConfigString( num ); // do something with it if necessary if ( num == CS_MUSIC ) { CG_StartMusic( qtrue ); } else if ( num == CS_SERVERINFO ) { CG_ParseServerinfo(); } else if ( num == CS_WARMUP ) { CG_ParseWarmup(); } else if ( num == CS_SCORES1 ) { cgs.scores1 = atoi( str ); } else if ( num == CS_SCORES2 ) { cgs.scores2 = atoi( str ); } else if ( num == CS_CLIENT_JEDIMASTER ) { cgs.jediMaster = atoi ( str ); } else if ( num == CS_CLIENT_DUELWINNER ) { cgs.duelWinner = atoi ( str ); } else if ( num == CS_CLIENT_DUELISTS ) { char buf[64]; int c = 0; int i = 0; while (str[i] && str[i] != '|') { buf[c] = str[i]; c++; i++; } buf[c] = 0; cgs.duelist1 = atoi ( buf ); c = 0; i++; while (str[i]) { buf[c] = str[i]; c++; i++; } buf[c] = 0; cgs.duelist2 = atoi ( buf ); } else if ( num == CS_LEVEL_START_TIME ) { cgs.levelStartTime = atoi( str ); } else if ( num == CS_VOTE_TIME ) { cgs.voteTime = atoi( str ); cgs.voteModified = qtrue; } else if ( num == CS_VOTE_YES ) { cgs.voteYes = atoi( str ); cgs.voteModified = qtrue; } else if ( num == CS_VOTE_NO ) { cgs.voteNo = atoi( str ); cgs.voteModified = qtrue; } else if ( num == CS_VOTE_STRING ) { Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) { cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str ); cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue; } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) { cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str ); cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue; } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) { cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str ); cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue; } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) { Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); } else if ( num == CS_INTERMISSION ) { cg.intermissionStarted = atoi( str ); } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str ); // GHOUL2 Insert start } else if ( num >= CS_CHARSKINS && num < CS_CHARSKINS+MAX_CHARSKINS ) { cgs.skins[ num-CS_CHARSKINS ] = trap_R_RegisterSkin( str ); // Ghoul2 Insert end } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS ) { if ( str[0] != '*' ) { // player specific sounds don't register here cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str ); } } else if ( num >= CS_EFFECTS && num < CS_SOUNDS+MAX_SOUNDS ) { if ( str[0] != '*' ) { // player specific sounds don't register here cgs.gameEffects[ num-CS_EFFECTS] = trap_FX_RegisterEffect( str ); } } else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) { CG_NewClientInfo( num - CS_PLAYERS, qtrue); CG_BuildSpectatorString(); } else if ( num == CS_FLAGSTATUS ) { if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped cgs.redflag = str[0] - '0'; cgs.blueflag = str[1] - '0'; } } else if ( num == CS_SHADERSTATE ) { CG_ShaderStateChanged(); } else if ( num >= CS_LIGHT_STYLES && num < CS_LIGHT_STYLES + (MAX_LIGHT_STYLES * 3)) { CG_SetLightstyle(num - CS_LIGHT_STYLES); } } /* ======================= CG_AddToTeamChat ======================= */ static void CG_AddToTeamChat( const char *str ) { int len; char *p, *ls; int lastcolor; int chatHeight; if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) { chatHeight = cg_teamChatHeight.integer; } else { chatHeight = TEAMCHAT_HEIGHT; } if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) { // team chat disabled, dump into normal chat cgs.teamChatPos = cgs.teamLastChatPos = 0; return; } len = 0; p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; *p = 0; lastcolor = '7'; ls = NULL; while (*str) { if (len > TEAMCHAT_WIDTH - 1) { if (ls) { str -= (p - ls); str++; p -= (p - ls); } *p = 0; cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; cgs.teamChatPos++; p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; *p = 0; *p++ = Q_COLOR_ESCAPE; *p++ = lastcolor; len = 0; ls = NULL; } if ( Q_IsColorString( str ) ) { *p++ = *str++; lastcolor = *str; *p++ = *str++; continue; } if (*str == ' ') { ls = p; } *p++ = *str++; len++; } *p = 0; cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; cgs.teamChatPos++; if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight) cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; } void CG_LoadClientInfo( clientInfo_t *ci ); void CG_KillCEntityInstances() { int i = 0; while (i < MAX_GENTITIES) { if (i >= MAX_CLIENTS) { //do not clear G2 instances on client ents, they are constant if (cg_entities[i].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[i].ghoul2)) { trap_G2API_CleanGhoul2Models(&(cg_entities[i].ghoul2)); } } /* else { //we must do this, because otherwise after a map_restart it seems to return bad angles in matrices produced by GetBoltMatrix if (cgs.clientinfo[i].ghoul2Model || cg_entities[i].ghoul2) { CG_LoadClientInfo(&cgs.clientinfo[i]); if (cg_entities[i].ghoul2 != cgs.clientinfo[i].ghoul2Model) { if (cg_entities[i].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[i].ghoul2)) { trap_G2API_CleanGhoul2Models(&(cg_entities[i].ghoul2)); } trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[i].ghoul2Model, &cg_entities[i].ghoul2); } } } */ cg_entities[i].isATST = 0; cg_entities[i].atstFootClang = 0; cg_entities[i].atstSwinging = 0; cg_entities[i].bolt1 = 0; cg_entities[i].bolt2 = 0; cg_entities[i].bolt3 = 0; cg_entities[i].bolt4 = 0; cg_entities[i].saberLength = SABER_LENGTH_MAX; cg_entities[i].saberExtendTime = 0; cg_entities[i].boltInfo = 0; cg_entities[i].frame_minus1_refreshed = 0; cg_entities[i].frame_minus2_refreshed = 0; cg_entities[i].dustTrailTime = 0; cg_entities[i].ghoul2weapon = NULL; // cg_entities[i].torsoBolt = 0; cg_entities[i].trailTime = 0; cg_entities[i].frame_hold_time = 0; cg_entities[i].frame_hold_refreshed = 0; cg_entities[i].trickAlpha = 0; cg_entities[i].trickAlphaTime = 0; VectorClear(cg_entities[i].turAngles); cg_entities[i].weapon = 0; cg_entities[i].teamPowerEffectTime = 0; cg_entities[i].teamPowerType = 0; i++; } } /* =============== CG_MapRestart The server has issued a map_restart, so the next snapshot is completely new and should not be interpolated to. A tournement restart will clear everything, but doesn't require a reload of all the media =============== */ static void CG_MapRestart( void ) { if ( cg_showmiss.integer ) { CG_Printf( "CG_MapRestart\n" ); } CG_InitLocalEntities(); CG_InitMarkPolys(); CG_ClearParticles (); CG_KillCEntityInstances(); // make sure the "3 frags left" warnings play again cg.fraglimitWarnings = 0; cg.timelimitWarnings = 0; cg.intermissionStarted = qfalse; cgs.voteTime = 0; cg.mapRestart = qtrue; CG_StartMusic(qtrue); trap_S_ClearLoopingSounds(qtrue); // we really should clear more parts of cg here and stop sounds // play the "fight" sound if this is a restart without warmup if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) { trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); CG_CenterPrint( CG_GetStripEdString("SVINGAME", "BEGIN_DUEL"), 120, GIANTCHAR_WIDTH*2 ); } if (cg_singlePlayerActive.integer) { trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time)); if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) { trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string)); } } trap_Cvar_Set("cg_thirdPerson", "0"); } #define MAX_VOICEFILESIZE 16384 #define MAX_VOICEFILES 8 #define MAX_VOICECHATS 64 #define MAX_VOICESOUNDS 64 #define MAX_CHATSIZE 64 #define MAX_HEADMODELS 64 typedef struct voiceChat_s { char id[64]; int numSounds; sfxHandle_t sounds[MAX_VOICESOUNDS]; char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; } voiceChat_t; typedef struct voiceChatList_s { char name[64]; int gender; int numVoiceChats; voiceChat_t voiceChats[MAX_VOICECHATS]; } voiceChatList_t; typedef struct headModelVoiceChat_s { char headmodel[64]; int voiceChatNum; } headModelVoiceChat_t; voiceChatList_t voiceChatLists[MAX_VOICEFILES]; //headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; /* ================= CG_ParseVoiceChats ================= */ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { int len, i; fileHandle_t f; char buf[MAX_VOICEFILESIZE]; char **p, *ptr; char *token; voiceChat_t *voiceChats; qboolean compress; compress = qtrue; if (cg_buildScript.integer) { compress = qfalse; } len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( !f ) { trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); return qfalse; } if ( len >= MAX_VOICEFILESIZE ) { trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); trap_FS_FCloseFile( f ); return qfalse; } trap_FS_Read( buf, len, f ); buf[len] = 0; trap_FS_FCloseFile( f ); ptr = buf; p = &ptr; Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename); voiceChats = voiceChatList->voiceChats; for ( i = 0; i < maxVoiceChats; i++ ) { voiceChats[i].id[0] = 0; } token = COM_ParseExt((const char **)p, qtrue); if (!token || token[0] == 0) { return qtrue; } if (!Q_stricmp(token, "female")) { voiceChatList->gender = GENDER_FEMALE; } else if (!Q_stricmp(token, "male")) { voiceChatList->gender = GENDER_MALE; } else if (!Q_stricmp(token, "neuter")) { voiceChatList->gender = GENDER_NEUTER; } else { trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); return qfalse; } voiceChatList->numVoiceChats = 0; while ( 1 ) { token = COM_ParseExt((const char **)p, qtrue); if (!token || token[0] == 0) { return qtrue; } Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); token = COM_ParseExt((const char **)p, qtrue); if (Q_stricmp(token, "{")) { trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); return qfalse; } voiceChats[voiceChatList->numVoiceChats].numSounds = 0; while(1) { token = COM_ParseExt((const char **)p, qtrue); if (!token || token[0] == 0) { return qtrue; } if (!Q_stricmp(token, "}")) break; voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = trap_S_RegisterSound( token ); token = COM_ParseExt((const char **)p, qtrue); if (!token || token[0] == 0) { return qtrue; } Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token); voiceChats[voiceChatList->numVoiceChats].numSounds++; if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS) break; } voiceChatList->numVoiceChats++; if (voiceChatList->numVoiceChats >= maxVoiceChats) return qtrue; } return qtrue; } /* ================= CG_LoadVoiceChats ================= */ void CG_LoadVoiceChats( void ) { int size; size = trap_MemoryRemaining(); CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS ); CG_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining()); } /* ================= CG_HeadModelVoiceChats ================= */ int CG_HeadModelVoiceChats( char *filename ) { int len, i; fileHandle_t f; char buf[MAX_VOICEFILESIZE]; char **p, *ptr; char *token; len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( !f ) { //trap_Print( va( "voice chat file not found: %s\n", filename ) ); return -1; } if ( len >= MAX_VOICEFILESIZE ) { trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); trap_FS_FCloseFile( f ); return -1; } trap_FS_Read( buf, len, f ); buf[len] = 0; trap_FS_FCloseFile( f ); ptr = buf; p = &ptr; token = COM_ParseExt((const char **)p, qtrue); if (!token || token[0] == 0) { return -1; } for ( i = 0; i < MAX_VOICEFILES; i++ ) { if ( !Q_stricmp(token, voiceChatLists[i].name) ) { return i; } } //FIXME: maybe try to load the .voice file which name is stored in token? return -1; } /* ================= CG_GetVoiceChat ================= */ int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) { int i, rnd; for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) { if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) { rnd = random() * voiceChatList->voiceChats[i].numSounds; *snd = voiceChatList->voiceChats[i].sounds[rnd]; *chat = voiceChatList->voiceChats[i].chats[rnd]; return qtrue; } } return qfalse; } /* ================= CG_VoiceChatListForClient ================= */ voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { clientInfo_t *ci; if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = 0; } ci = &cgs.clientinfo[ clientNum ]; /* int voiceChatNum, i, j, k, gender; char filename[MAX_QPATH], headModelName[MAX_QPATH]; for ( k = 0; k < 2; k++ ) { if ( k == 0 ) { if (ci->headModelName[0] == '*') { Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName ); } else { Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName ); } } else { if (ci->headModelName[0] == '*') { Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 ); } else { Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName ); } } // find the voice file for the head model the client uses for ( i = 0; i < MAX_HEADMODELS; i++ ) { if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName)) { break; } } if (i < MAX_HEADMODELS) { return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; } // find a .vc file for ( i = 0; i < MAX_HEADMODELS; i++ ) { if (!strlen(headModelVoiceChat[i].headmodel)) { Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName); voiceChatNum = CG_HeadModelVoiceChats(filename); if (voiceChatNum == -1) break; Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ), "%s", headModelName); headModelVoiceChat[i].voiceChatNum = voiceChatNum; return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; } } } gender = ci->gender; for (k = 0; k < 2; k++) { // just pick the first with the right gender for ( i = 0; i < MAX_VOICEFILES; i++ ) { if (strlen(voiceChatLists[i].name)) { if (voiceChatLists[i].gender == gender) { // store this head model with voice chat for future reference for ( j = 0; j < MAX_HEADMODELS; j++ ) { if (!strlen(headModelVoiceChat[j].headmodel)) { Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), "%s", headModelName); headModelVoiceChat[j].voiceChatNum = i; break; } } return &voiceChatLists[i]; } } } // fall back to male gender because we don't have neuter in the mission pack if (gender == GENDER_MALE) break; gender = GENDER_MALE; } // store this head model with voice chat for future reference for ( j = 0; j < MAX_HEADMODELS; j++ ) { if (!strlen(headModelVoiceChat[j].headmodel)) { Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), "%s", headModelName); headModelVoiceChat[j].voiceChatNum = 0; break; } } */ // just return the first voice chat list return &voiceChatLists[0]; } #define MAX_VOICECHATBUFFER 32 typedef struct bufferedVoiceChat_s { int clientNum; sfxHandle_t snd; int voiceOnly; char cmd[MAX_SAY_TEXT]; char message[MAX_SAY_TEXT]; } bufferedVoiceChat_t; bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; /* ================= CG_PlayVoiceChat ================= */ void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) { // if we are going into the intermission, don't start any voices if ( cg.intermissionStarted ) { return; } if ( !cg_noVoiceChats.integer ) { trap_S_StartLocalSound( vchat->snd, CHAN_VOICE); if (vchat->clientNum != cg.snap->ps.clientNum) { int orderTask = CG_ValidOrder(vchat->cmd); if (orderTask > 0) { cgs.acceptOrderTime = cg.time + 5000; Q_strncpyz(cgs.acceptVoice, vchat->cmd, sizeof(cgs.acceptVoice)); cgs.acceptTask = orderTask; cgs.acceptLeader = vchat->clientNum; } // see if this was an order CG_ShowResponseHead(); } } if (!vchat->voiceOnly && !cg_noVoiceText.integer) { CG_AddToTeamChat( vchat->message ); CG_Printf( "%s\n", vchat->message ); } voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; } /* ===================== CG_PlayBufferedVoieChats ===================== */ void CG_PlayBufferedVoiceChats( void ) { if ( cg.voiceChatTime < cg.time ) { if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) { // CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]); // cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER; cg.voiceChatTime = cg.time + 1000; } } } /* ===================== CG_AddBufferedVoiceChat ===================== */ void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) { // if we are going into the intermission, don't start any voices if ( cg.intermissionStarted ) { return; } memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) { CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); cg.voiceChatBufferOut++; } } /* ================= CG_VoiceChatLocal ================= */ void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ) { char *chat; voiceChatList_t *voiceChatList; clientInfo_t *ci; sfxHandle_t snd; bufferedVoiceChat_t vchat; // if we are going into the intermission, don't start any voices if ( cg.intermissionStarted ) { return; } if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = 0; } ci = &cgs.clientinfo[ clientNum ]; cgs.currentVoiceClient = clientNum; voiceChatList = CG_VoiceChatListForClient( clientNum ); if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) { // if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { vchat.clientNum = clientNum; vchat.snd = snd; vchat.voiceOnly = voiceOnly; Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); if ( mode == SAY_TELL ) { Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); } else if ( mode == SAY_TEAM ) { Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); } else { Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); } CG_AddBufferedVoiceChat(&vchat); } } } /* ================= CG_VoiceChat ================= */ void CG_VoiceChat( int mode ) { const char *cmd; int clientNum, color; qboolean voiceOnly; voiceOnly = atoi(CG_Argv(1)); clientNum = atoi(CG_Argv(2)); color = atoi(CG_Argv(3)); cmd = CG_Argv(4); if (cg_noTaunt.integer != 0) { if (!strcmp(cmd, VOICECHAT_KILLINSULT) || !strcmp(cmd, VOICECHAT_TAUNT) || \ !strcmp(cmd, VOICECHAT_DEATHINSULT) || !strcmp(cmd, VOICECHAT_KILLGAUNTLET) || \ !strcmp(cmd, VOICECHAT_PRAISE)) { return; } } CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd ); } /* ================= CG_RemoveChatEscapeChar ================= */ static void CG_RemoveChatEscapeChar( char *text ) { int i, l; l = 0; for ( i = 0; text[i]; i++ ) { if (text[i] == '\x19') continue; text[l++] = text[i]; } text[l] = '\0'; } #define MAX_STRIPED_SV_STRING 1024 void CG_CheckSVStripEdRef(char *buf, const char *str) { //I don't really like doing this. But it utilizes the system that was already in place. int i = 0; int b = 0; int strLen = 0; qboolean gotStrip = qfalse; if (!str || !str[0]) { if (str) { strcpy(buf, str); } return; } strcpy(buf, str); strLen = strlen(str); if (strLen >= MAX_STRIPED_SV_STRING) { return; } while (i < strLen && str[i]) { gotStrip = qfalse; if (str[i] == '@' && (i+1) < strLen) { if (str[i+1] == '@' && (i+2) < strLen) { if (str[i+2] == '@' && (i+3) < strLen) { //@@@ should mean to insert a striped reference here, so insert it into buf at the current place char stripRef[MAX_STRIPED_SV_STRING]; int r = 0; while (i < strLen && str[i] == '@') { i++; } while (i < strLen && str[i] && str[i] != ' ' && str[i] != ':' && str[i] != '.' && str[i] != '\n') { stripRef[r] = str[i]; r++; i++; } stripRef[r] = 0; buf[b] = 0; Q_strcat(buf, MAX_STRIPED_SV_STRING, CG_GetStripEdString("SVINGAME", stripRef)); b = strlen(buf); } } } if (!gotStrip) { buf[b] = str[i]; b++; } i++; } buf[b] = 0; } /* ================= CG_ServerCommand The string has been tokenized and can be retrieved with Cmd_Argc() / Cmd_Argv() ================= */ static void CG_ServerCommand( void ) { const char *cmd; char text[MAX_SAY_TEXT]; cmd = CG_Argv(0); if ( !cmd[0] ) { // server claimed the command return; } if ( !strcmp( cmd, "spd" ) ) { const char *ID; int holdInt,count,i; char string[1204]; count = trap_Argc(); ID = CG_Argv(1); holdInt = atoi(ID); memset( &string, 0, sizeof( string ) ); Com_sprintf( string,sizeof(string)," \"%s\"", (const char *) CG_Argv(2)); for (i=3;i