jedioutcast/CODE-mp/cgame/cg_servercmds.c
2013-04-04 13:24:26 -05:00

1360 lines
35 KiB
C

// 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.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.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 <headmodelname>.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<count;i++)
{
Com_sprintf( string,sizeof(string)," %s \"%s\"", string, (const char *) CG_Argv(i));
}
trap_SP_Print(holdInt, (byte *)string);
return;
}
if ( !strcmp( cmd, "nfr" ) )
{ //"nfr" == "new force rank" (want a short string)
int doMenu = 0;
int setTeam = 0;
int newRank = 0;
if (trap_Argc() < 3)
{
#ifdef _DEBUG
Com_Printf("WARNING: Invalid newForceRank string\n");
#endif
return;
}
newRank = atoi(CG_Argv(1));
doMenu = atoi(CG_Argv(2));
setTeam = atoi(CG_Argv(3));
trap_Cvar_Set("ui_rankChange", va("%i", newRank));
trap_Cvar_Set("ui_myteam", va("%i", setTeam));
if (!( trap_Key_GetCatcher() & KEYCATCH_UI ) && doMenu)
{
trap_OpenUIMenu(3);
}
return;
}
if ( !strcmp( cmd, "kg2" ) )
{ //Kill a ghoul2 instance in this slot.
//If it has been occupied since this message was sent somehow, the worst that can (should) happen
//is the instance will have to reinit with its current info.
int indexNum = 0;
int argNum = trap_Argc();
int i = 1;
if (argNum < 1)
{
return;
}
while (i < argNum)
{
indexNum = atoi(CG_Argv(i));
if (cg_entities[indexNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[indexNum].ghoul2))
{
if (indexNum < MAX_CLIENTS)
{ //You try to do very bad thing!
#ifdef _DEBUG
Com_Printf("WARNING: Tried to kill a client ghoul2 instance with a kg2 command!\n");
#endif
return;
}
trap_G2API_CleanGhoul2Models(&(cg_entities[indexNum].ghoul2));
}
i++;
}
return;
}
if ( !strcmp( cmd, "cp" ) ) {
char strEd[MAX_STRIPED_SV_STRING];
CG_CheckSVStripEdRef(strEd, CG_Argv(1));
CG_CenterPrint( strEd, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
return;
}
if ( !strcmp( cmd, "cs" ) ) {
CG_ConfigStringModified();
return;
}
if ( !strcmp( cmd, "print" ) ) {
char strEd[MAX_STRIPED_SV_STRING];
CG_CheckSVStripEdRef(strEd, CG_Argv(1));
CG_Printf( "%s", strEd );
return;
}
if ( !strcmp( cmd, "chat" ) ) {
if ( !cg_teamChatsOnly.integer ) {
trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
CG_RemoveChatEscapeChar( text );
CG_Printf( "%s\n", text );
}
return;
}
if ( !strcmp( cmd, "tchat" ) ) {
trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
CG_RemoveChatEscapeChar( text );
CG_AddToTeamChat( text );
CG_Printf( "%s\n", text );
return;
}
if ( !strcmp( cmd, "vchat" ) ) {
CG_VoiceChat( SAY_ALL );
return;
}
if ( !strcmp( cmd, "vtchat" ) ) {
CG_VoiceChat( SAY_TEAM );
return;
}
if ( !strcmp( cmd, "vtell" ) ) {
CG_VoiceChat( SAY_TELL );
return;
}
if ( !strcmp( cmd, "scores" ) ) {
CG_ParseScores();
return;
}
if ( !strcmp( cmd, "tinfo" ) ) {
CG_ParseTeamInfo();
return;
}
if ( !strcmp( cmd, "map_restart" ) ) {
CG_MapRestart();
return;
}
if ( Q_stricmp (cmd, "remapShader") == 0 ) {
if (trap_Argc() == 4) {
trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3));
}
}
// loaddeferred can be both a servercmd and a consolecmd
if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo
CG_LoadDeferredPlayers();
return;
}
// clientLevelShot is sent before taking a special screenshot for
// the menu system during development
if ( !strcmp( cmd, "clientLevelShot" ) ) {
cg.levelShot = qtrue;
return;
}
CG_Printf( "Unknown client game command: %s\n", cmd );
}
/*
====================
CG_ExecuteNewServerCommands
Execute all of the server commands that were received along
with this this snapshot.
====================
*/
void CG_ExecuteNewServerCommands( int latestSequence ) {
while ( cgs.serverCommandSequence < latestSequence ) {
if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) {
CG_ServerCommand();
}
}
}