1063 lines
24 KiB
C
1063 lines
24 KiB
C
// Copyright (C) 2001-2002 Raven Software
|
|
//
|
|
// 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
|
|
|
|
/*
|
|
=================
|
|
CG_ParseScores
|
|
=================
|
|
*/
|
|
static void CG_ParseScores( void )
|
|
{
|
|
int i;
|
|
|
|
cg.scoreBoardSpectators[0] = '\0';
|
|
|
|
cg.numScores = atoi( CG_Argv( 1 ) );
|
|
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 < cg.numScores ; i++ )
|
|
{
|
|
cg.scores[i].client = atoi( CG_Argv( i * 9 + 4 ) );
|
|
cg.scores[i].score = atoi( CG_Argv( i * 9 + 5 ) );
|
|
cg.scores[i].kills = atoi( CG_Argv( i * 9 + 6 ) );
|
|
cg.scores[i].deaths = atoi( CG_Argv( i * 9 + 7 ) );
|
|
cg.scores[i].ping = atoi( CG_Argv( i * 9 + 8 ) );
|
|
cg.scores[i].time = atoi( CG_Argv( i * 9 + 9 ) );
|
|
cgs.clientinfo[ cg.scores[i].client ].ghost = atoi( CG_Argv( i * 9 + 10 ) );
|
|
cgs.clientinfo[ cg.scores[i].client ].gametypeitems = atoi( CG_Argv( i * 9 + 11 ) );
|
|
cg.scores[i].teamkillDamage = atoi( CG_Argv( i * 9 + 12 ) );
|
|
|
|
if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS )
|
|
{
|
|
cg.scores[i].client = 0;
|
|
}
|
|
|
|
if ( cg.scores[i].ping < 0 )
|
|
{
|
|
cg.scores[i].time = 0;
|
|
}
|
|
|
|
cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score;
|
|
cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team;
|
|
|
|
if ( cg.scores[i].team == TEAM_SPECTATOR )
|
|
{
|
|
if ( cg.scoreBoardSpectators[0] )
|
|
{
|
|
strcat ( cg.scoreBoardSpectators, ", " );
|
|
}
|
|
|
|
strcat ( cg.scoreBoardSpectators, va("%s (%d)", cgs.clientinfo[cg.scores[i].client].name, cg.scores[i].ping ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
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 = BG_FindGametype ( Info_ValueForKey( info, "g_gametype" ) );
|
|
cgs.gametypeData = &bg_gametypeData[cgs.gametype];
|
|
cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) );
|
|
cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) );
|
|
cgs.scorelimit = atoi( Info_ValueForKey( info, "scorelimit" ) );
|
|
cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) );
|
|
cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
|
|
cgs.friendlyFire = atoi( Info_ValueForKey( info, "g_friendlyFire" ) ) ? qtrue : qfalse;
|
|
cgs.punkbuster = atoi( Info_ValueForKey( info, "sv_punkbster" ) ) ? qtrue : qfalse;
|
|
mapname = Info_ValueForKey( info, "mapname" );
|
|
Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname );
|
|
|
|
trap_Cvar_Set ( "ui_about_gametype", va("%i", cgs.gametype ) );
|
|
trap_Cvar_Set ( "ui_about_gametypename", cgs.gametypeData->displayName );
|
|
trap_Cvar_Set ( "ui_about_scorelimit", va("%i", cgs.scorelimit ) );
|
|
trap_Cvar_Set ( "ui_about_timelimit", va("%i", cgs.timelimit ) );
|
|
trap_Cvar_Set ( "ui_about_friendlyfire", va("%i", cgs.friendlyFire) );
|
|
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_mapname", mapname );
|
|
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" ) );
|
|
trap_Cvar_Set ( "ui_info_availableweapons", Info_ValueForKey ( info, "g_availableWeapons" ) );
|
|
trap_Cvar_Set ( "ui_info_teamgame", va("%i", cgs.gametypeData->teams ? 1 : 0 ) );
|
|
|
|
BG_SetAvailableOutfitting ( Info_ValueForKey ( info, "g_availableWeapons" ) );
|
|
|
|
if ( cgs.gametypeData->teams )
|
|
{
|
|
trap_Cvar_Set ( "ui_info_redteam", CG_ConfigString ( CS_GAMETYPE_REDTEAM ) );
|
|
trap_Cvar_Set ( "ui_info_blueteam", CG_ConfigString ( CS_GAMETYPE_BLUETEAM ) );
|
|
}
|
|
else
|
|
{
|
|
trap_Cvar_Set ( "ui_info_redteam", "" );
|
|
trap_Cvar_Set ( "ui_info_blueteam", "" );
|
|
}
|
|
|
|
info = CG_ConfigString( CS_TERRAINS + 1 );
|
|
if ( !info || !*info )
|
|
{
|
|
cg.mInRMG = qfalse;
|
|
}
|
|
else
|
|
{
|
|
cg.mInRMG = qtrue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
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;
|
|
}
|
|
|
|
static void CG_ParseGametypeTimer ( void )
|
|
{
|
|
cgs.gametypeTimerTime = atoi( CG_ConfigString( CS_GAMETYPE_TIMER ) );
|
|
}
|
|
|
|
static void CG_ParseGametypeMessage ( void )
|
|
{
|
|
char temp[1024];
|
|
char* comma;
|
|
|
|
strcpy ( temp, CG_ConfigString( CS_GAMETYPE_MESSAGE ) );
|
|
comma = strchr ( temp, ',' );
|
|
if ( !comma )
|
|
{
|
|
return;
|
|
}
|
|
|
|
*(comma++) = '\0';
|
|
|
|
cgs.gametypeMessageTime = atoi ( temp );
|
|
|
|
// Silent gametype message
|
|
if ( *comma == '@' )
|
|
{
|
|
strcpy ( cgs.gametypeMessage, comma + 1 );
|
|
}
|
|
else
|
|
{
|
|
strcpy ( cgs.gametypeMessage, comma );
|
|
Com_Printf ( "@%s\n", cgs.gametypeMessage );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_ParseVoteTime
|
|
================
|
|
*/
|
|
static void CG_ParseVoteTime ( void )
|
|
{
|
|
char temp[1024];
|
|
char* comma;
|
|
const char *str;
|
|
|
|
str = CG_ConfigString( CS_VOTE_TIME );
|
|
|
|
strcpy ( temp, str );
|
|
comma = strchr ( temp, ',' );
|
|
if ( !comma )
|
|
{
|
|
cgs.voteTime = cgs.voteDuration = 0;
|
|
return;
|
|
}
|
|
*comma = 0;
|
|
|
|
cgs.voteTime = atoi(str);
|
|
cgs.voteDuration = atoi(comma+1);
|
|
cgs.voteModified = qtrue;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_SetConfigValues
|
|
|
|
Called on load to set the initial values from configure strings
|
|
================
|
|
*/
|
|
void CG_SetConfigValues( void )
|
|
{
|
|
int i;
|
|
|
|
cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
|
|
cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
|
|
cgs.pickupsDisabled = atoi( CG_ConfigString( CS_PICKUPSDISABLED ) );
|
|
cgs.gameID = atoi( CG_ConfigString( CS_GAME_ID ) );
|
|
|
|
trap_Cvar_Set ( "ui_info_pickupsdisabled", va("%i", cgs.pickupsDisabled ) );
|
|
|
|
CG_ParseGametypeTimer ( );
|
|
CG_ParseGametypeMessage ( );
|
|
CG_ParseVoteTime ( );
|
|
|
|
for ( i = 0; i < MAX_HUDICONS; i ++ )
|
|
{
|
|
cgs.hudIcons[i] = atoi ( CG_ConfigString ( CS_HUDICONS + i ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
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_GAMETYPE_TIMER )
|
|
{
|
|
CG_ParseGametypeTimer ( );
|
|
}
|
|
else if ( num == CS_GAMETYPE_MESSAGE )
|
|
{
|
|
CG_ParseGametypeMessage ( );
|
|
}
|
|
else if ( num == CS_LEVEL_START_TIME )
|
|
{
|
|
cgs.levelStartTime = atoi( str );
|
|
}
|
|
else if ( num == CS_VOTE_TIME )
|
|
{
|
|
CG_ParseVoteTime();
|
|
}
|
|
else if ( num == CS_VOTE_NEEDED )
|
|
{
|
|
cgs.voteNeeded = 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 ) );
|
|
trap_S_StartLocalSound( cgs.media.talkSound, CHAN_ANNOUNCER );
|
|
}
|
|
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 );
|
|
}
|
|
else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS )
|
|
{
|
|
if ( str[0] != '*' )
|
|
{
|
|
// player specific sounds don't register here
|
|
cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str );
|
|
}
|
|
}
|
|
else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS )
|
|
{
|
|
CG_NewClientInfo( num - CS_PLAYERS );
|
|
}
|
|
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);
|
|
}
|
|
else if ( num >= CS_ICONS && num < CS_ICONS + MAX_ICONS )
|
|
{
|
|
cgs.gameIcons[ num - CS_ICONS ] = trap_R_RegisterShaderNoMip ( str );
|
|
}
|
|
else if ( num >= CS_HUDICONS && num < CS_HUDICONS + MAX_HUDICONS )
|
|
{
|
|
cgs.hudIcons[ num - CS_HUDICONS ] = atoi ( str );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=======================
|
|
CG_AddChatText
|
|
|
|
Adds chat text to the chat chat buffer
|
|
=======================
|
|
*/
|
|
static void CG_AddChatText ( int client, const char *str )
|
|
{
|
|
int len;
|
|
char *p;
|
|
char *ls;
|
|
int lastcolor;
|
|
int chatHeight;
|
|
float w;
|
|
|
|
if ( client >= 0 )
|
|
{
|
|
cgs.clientinfo[client].mLastChatTime = cg.time;
|
|
}
|
|
|
|
// Grab the users chat height settings
|
|
chatHeight = cg_chatHeight.integer;
|
|
if ( chatHeight > CHAT_HEIGHT )
|
|
{
|
|
chatHeight = CHAT_HEIGHT;
|
|
}
|
|
|
|
// Chats disabled?
|
|
if ( chatHeight <= 0 || cg_chatTime.integer <= 0 )
|
|
{
|
|
cgs.chatPos = cgs.chatLastPos = 0;
|
|
return;
|
|
}
|
|
|
|
len = 0;
|
|
|
|
lastcolor = COLOR_WHITE;
|
|
|
|
// Next position to write chat text too in the circular chat buffer
|
|
p = cgs.chatText[cgs.chatPos % chatHeight];
|
|
*p = 0;
|
|
|
|
ls = NULL;
|
|
w = 0;
|
|
|
|
while (*str)
|
|
{
|
|
float cw = trap_R_GetTextWidth ( va("%c",*str), cgs.media.hudFont, 0.43f, 0 );
|
|
|
|
if ( w > 560 )
|
|
{
|
|
w = 0;
|
|
|
|
if (ls)
|
|
{
|
|
str -= (p - ls);
|
|
str++;
|
|
p -= (p - ls);
|
|
}
|
|
|
|
*p = 0;
|
|
|
|
cgs.chatTime[cgs.chatPos % chatHeight] = cg.time;
|
|
|
|
cgs.chatPos++;
|
|
p = cgs.chatText[ cgs.chatPos % 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++;
|
|
w += cw;
|
|
}
|
|
*p = 0;
|
|
|
|
cgs.chatTime[ cgs.chatPos % chatHeight] = cg.time;
|
|
cgs.chatPos++;
|
|
|
|
if (cgs.chatPos - cgs.chatLastPos > chatHeight)
|
|
{
|
|
cgs.chatLastPos = cgs.chatPos - chatHeight;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
CG_MapRestart
|
|
|
|
The server has issued a map_restart, so the next snapshot
|
|
is completely new and should not be interpolated to.
|
|
|
|
A map restart will clear everything, but doesn't
|
|
require a reload of all the media
|
|
===============
|
|
*/
|
|
void CG_MapRestart( qboolean gametypeRestart )
|
|
{
|
|
if ( cg_showmiss.integer )
|
|
{
|
|
Com_Printf( "CG_MapRestart\n" );
|
|
}
|
|
|
|
trap_R_ClearDecals ( );
|
|
trap_FX_Reset ( );
|
|
trap_MAT_Reset();
|
|
|
|
CG_InitLocalEntities();
|
|
|
|
cg.intermissionStarted = qfalse;
|
|
|
|
// dont clear votes on gametype restarts
|
|
if ( !gametypeRestart )
|
|
{
|
|
cgs.voteTime = 0;
|
|
cgs.gametypeMessage[0] = '\0';
|
|
cgs.gametypeMessageTime = 0;
|
|
}
|
|
|
|
cgs.gameover[0] = '\0';
|
|
|
|
cg.mapRestart = qtrue;
|
|
|
|
// Make sure the weapon selection menu isnt up
|
|
cg.weaponMenuUp = qfalse;
|
|
|
|
cg.gametypeStarted = qfalse;
|
|
|
|
// cgs.media.mAutomap = 0; // make sure to re-upload the auto-map
|
|
|
|
CG_StartMusic(qtrue);
|
|
|
|
trap_S_ClearLoopingSounds(qtrue);
|
|
|
|
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];
|
|
const 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(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(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(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(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(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/male1.voice", &voiceChatLists[1], MAX_VOICECHATS );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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;
|
|
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
|
|
switch ( ci->gender )
|
|
{
|
|
case GENDER_FEMALE:
|
|
return &voiceChatLists[0];
|
|
|
|
case GENDER_MALE:
|
|
return &voiceChatLists[1];
|
|
}
|
|
|
|
// just return the male voice chat list since there are more male characters
|
|
return &voiceChatLists[1];
|
|
}
|
|
|
|
#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_voiceRadio.integer )
|
|
{
|
|
trap_S_StartLocalSound( vchat->snd, CHAN_VOICE);
|
|
}
|
|
|
|
if (!vchat->voiceOnly && !cg_noVoiceText.integer)
|
|
{
|
|
CG_AddChatText ( vchat->clientNum, 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( qboolean voiceOnly, int clientNum, const char* chatprefix, const char *cmd )
|
|
{
|
|
char *chat;
|
|
voiceChatList_t *voiceChatList;
|
|
sfxHandle_t snd;
|
|
bufferedVoiceChat_t vchat;
|
|
|
|
// if we are going into the intermission, don't start any voices
|
|
if ( cg.intermissionStarted )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the voice chat info for the speaking client
|
|
voiceChatList = CG_VoiceChatListForClient( clientNum );
|
|
if ( !CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
vchat.clientNum = clientNum;
|
|
vchat.snd = snd;
|
|
vchat.voiceOnly = voiceOnly;
|
|
Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd));
|
|
|
|
Com_sprintf ( vchat.message, sizeof(vchat.message), "%s%s", chatprefix, chat );
|
|
|
|
CG_AddBufferedVoiceChat(&vchat);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_VoiceChat
|
|
=================
|
|
*/
|
|
void CG_VoiceChat( int mode )
|
|
{
|
|
char cmd[MAX_QPATH];
|
|
const char *chatprefix;
|
|
qboolean voiceOnly;
|
|
int clientNum;
|
|
|
|
voiceOnly = atoi(CG_Argv(1));
|
|
clientNum = atoi(CG_Argv(2));
|
|
Com_sprintf ( cmd, MAX_QPATH, CG_Argv(4) );
|
|
chatprefix = CG_Argv(3);
|
|
|
|
CG_VoiceChatLocal ( voiceOnly, clientNum, chatprefix, 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';
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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, "cp" ) )
|
|
{
|
|
const char* s;
|
|
|
|
s = CG_Argv(1);
|
|
if ( *s == '@' )
|
|
{
|
|
s++;
|
|
}
|
|
else
|
|
{
|
|
Com_Printf ( "@%s", s );
|
|
}
|
|
|
|
CG_CenterPrint( s, 0.43f );
|
|
return;
|
|
}
|
|
|
|
if ( !strcmp( cmd, "cs" ) )
|
|
{
|
|
CG_ConfigStringModified();
|
|
return;
|
|
}
|
|
|
|
if ( !strcmp( cmd, "print" ) )
|
|
{
|
|
Com_Printf( "%s", CG_Argv(1) );
|
|
|
|
/*
|
|
cmd = CG_Argv(1); // yes, this is obviously a hack, but so is the way we hear about
|
|
// votes passing or failing
|
|
if ( !Q_stricmpn( cmd, "vote failed", 11 ) )
|
|
{
|
|
trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER );
|
|
}
|
|
else if ( !Q_stricmpn( cmd, "vote passed", 11 ) )
|
|
{
|
|
trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER );
|
|
}
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if ( !strcmp( cmd, "chat" ) )
|
|
{
|
|
if ( !cg_teamChatsOnly.integer )
|
|
{
|
|
trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
|
|
Q_strncpyz( text, CG_Argv(2), MAX_SAY_TEXT );
|
|
CG_RemoveChatEscapeChar( text );
|
|
CG_AddChatText ( atoi(CG_Argv(1)), text );
|
|
Com_Printf( "@%s\n", text );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( !strcmp( cmd, "tchat" ) )
|
|
{
|
|
trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
|
|
Q_strncpyz( text, CG_Argv(2), MAX_SAY_TEXT );
|
|
CG_RemoveChatEscapeChar( text );
|
|
CG_AddChatText ( atoi(CG_Argv(1)), text );
|
|
Com_Printf( "@%s\n", text );
|
|
return;
|
|
}
|
|
|
|
if ( !strcmp( cmd, "vglobal" ) )
|
|
{
|
|
char *chat;
|
|
voiceChatList_t *voiceChatList;
|
|
sfxHandle_t snd;
|
|
int clientNum;
|
|
|
|
if ( !cg_voiceGlobal.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
clientNum = atoi(CG_Argv(1));
|
|
|
|
// Get the voice chat info for the speaking client
|
|
voiceChatList = CG_VoiceChatListForClient( clientNum );
|
|
if ( !CG_GetVoiceChat( voiceChatList, CG_Argv(2), &snd, &chat ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
trap_S_StartSound ( NULL, clientNum, CHAN_AUTO, snd, 240, 1150 );
|
|
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();
|
|
CG_UpdateTeamCountCvars ( );
|
|
return;
|
|
}
|
|
|
|
if ( !strcmp( cmd, "map_restart" ) )
|
|
{
|
|
CG_MapRestart( qfalse );
|
|
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, "loaddeferred" ) )
|
|
{
|
|
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;
|
|
}
|
|
|
|
Com_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();
|
|
}
|
|
}
|
|
}
|