📄 cg_servercmds.c
字号:
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
//
// 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" // bk001205 - for Q3_ui as well
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);
#ifdef MISSIONPACK // bk001204
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;
}
#endif
/*
=================
CG_ParseScores
=================
*/
static void CG_ParseScores( void ) {
int i, powerups;
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 * 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;
}
#ifdef MISSIONPACK
CG_SetScoreSelection(NULL);
#endif
}
/*
=================
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.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" );
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);
}
/*
==================
CG_ParseWarmup
==================
*/
static void CG_ParseWarmup( void ) {
const char *info;
int warmup;
info = CG_ConfigString( CS_WARMUP );
warmup = atoi( info );
cg.warmupCount = -1;
if ( warmup == 0 && cg.warmup ) {
} else if ( warmup > 0 && cg.warmup <= 0 ) {
#ifdef MISSIONPACK
if (cgs.gametype >= GT_CTF && cgs.gametype <= GT_HARVESTER) {
trap_S_StartLocalSound( cgs.media.countPrepareTeamSound, CHAN_ANNOUNCER );
} else
#endif
{
trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER );
}
}
cg.warmup = warmup;
}
/*
================
CG_SetConfigValues
Called on load to set the initial values from configure strings
================
*/
void CG_SetConfigValues( void ) {
const char *s;
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 ) {
s = CG_ConfigString( CS_FLAGSTATUS );
cgs.redflag = s[0] - '0';
cgs.blueflag = s[1] - '0';
}
#ifdef MISSIONPACK
else if( cgs.gametype == GT_1FCTF ) {
s = CG_ConfigString( CS_FLAGSTATUS );
cgs.flagStatus = s[0] - '0';
}
#endif
cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
}
/*
=====================
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();
} 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_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 ) );
#ifdef MISSIONPACK
trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER );
#endif //MISSIONPACK
} 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 ) );
#ifdef MISSIONPACK
trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER );
#endif
} 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, qfalse );
}
} else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) {
CG_NewClientInfo( num - CS_PLAYERS );
CG_BuildSpectatorString();
} else if ( num == CS_FLAGSTATUS ) {
if( cgs.gametype == GT_CTF ) {
// 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';
}
#ifdef MISSIONPACK
else if( cgs.gametype == GT_1FCTF ) {
cgs.flagStatus = str[0] - '0';
}
#endif
}
else if ( num == CS_SHADERSTATE ) {
CG_ShaderStateChanged();
}
}
/*
=======================
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;
}
/*
===============
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 ();
// 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();
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( "FIGHT!", 120, GIANTCHAR_WIDTH*2 );
}
#ifdef MISSIONPACK
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));
}
}
#endif
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;
sfxHandle_t sound;
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++ ) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -