summaryrefslogtreecommitdiff
path: root/src/cgame/cg_servercmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cgame/cg_servercmds.c')
-rw-r--r--src/cgame/cg_servercmds.c1235
1 files changed, 779 insertions, 456 deletions
diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c
index 7fb3e06..4de7586 100644
--- a/src/cgame/cg_servercmds.c
+++ b/src/cgame/cg_servercmds.c
@@ -1,13 +1,14 @@
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
-Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
This file is part of Tremulous.
Tremulous 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,
+published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
@@ -16,8 +17,8 @@ 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 Tremulous; if not, write to the Free Software
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
===========================================================================
*/
@@ -25,7 +26,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
// these are processed at snapshot transition time, so there will definately
// be a valid snapshot this frame
-
#include "cg_local.h"
/*
@@ -38,13 +38,13 @@ static void CG_ParseScores( void )
{
int i;
- cg.numScores = atoi( CG_Argv( 1 ) );
+ cg.numScores = ( trap_Argc( ) - 3 ) / 6;
if( cg.numScores > MAX_CLIENTS )
cg.numScores = MAX_CLIENTS;
- cg.teamScores[ 0 ] = atoi( CG_Argv( 2 ) );
- cg.teamScores[ 1 ] = atoi( CG_Argv( 3 ) );
+ cg.teamScores[ 0 ] = atoi( CG_Argv( 1 ) );
+ cg.teamScores[ 1 ] = atoi( CG_Argv( 2 ) );
memset( cg.scores, 0, sizeof( cg.scores ) );
@@ -54,18 +54,17 @@ static void CG_ParseScores( void )
for( i = 0; i < cg.numScores; i++ )
{
//
- cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 4 ) );
- cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 5 ) );
- cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 6 ) );
- cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 7 ) );
- cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 8 ) );
- cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 9 ) );
+ cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 3 ) );
+ cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 4 ) );
+ cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 5 ) );
+ cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 6 ) );
+ cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 7 ) );
+ cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 8 ) );
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 = 0;
cg.scores[ i ].team = cgs.clientinfo[ cg.scores[ i ].client ].team;
}
@@ -80,22 +79,33 @@ CG_ParseTeamInfo
static void CG_ParseTeamInfo( void )
{
int i;
+ int count;
int client;
- numSortedTeamPlayers = atoi( CG_Argv( 1 ) );
+ count = trap_Argc( );
- for( i = 0; i < numSortedTeamPlayers; i++ )
+ for( i = 1; i < count; i++ ) // i is also incremented when writing into cgs.clientinfo
{
- client = atoi( CG_Argv( i * 6 + 2 ) );
+ client = atoi( CG_Argv( i ) );
+
+ // wrong team? drop the remaining info
+ if( cgs.clientinfo[ client ].team != cg.snap->ps.stats[ STAT_TEAM ] )
+ return;
- sortedTeamPlayers[ i ] = client;
+ if( client < 0 || client >= MAX_CLIENTS )
+ {
+ CG_Printf( "[skipnotify]CG_ParseTeamInfo: bad client number: %d\n", client );
+ return;
+ }
- 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 ) );
+ cgs.clientinfo[ client ].location = atoi( CG_Argv( ++i ) );
+ cgs.clientinfo[ client ].health = atoi( CG_Argv( ++i ) );
+ cgs.clientinfo[ client ].curWeaponClass = atoi( CG_Argv( ++i ) );
+ if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )
+ cgs.clientinfo[ client ].upgrade = atoi( CG_Argv( ++i ) );
}
+
+ cgs.teaminfoReceievedTime = cg.time;
}
@@ -133,13 +143,7 @@ static void CG_ParseWarmup( void )
info = CG_ConfigString( CS_WARMUP );
warmup = atoi( info );
- cg.warmupCount = -1;
-
- if( warmup == 0 && cg.warmup )
- {
- }
-
- cg.warmup = warmup;
+ cg.warmupTime = warmup;
}
/*
@@ -151,19 +155,28 @@ Called on load to set the initial values from configure strings
*/
void CG_SetConfigValues( void )
{
- sscanf( CG_ConfigString( CS_BUILDPOINTS ),
- "%d %d %d %d %d", &cgs.alienBuildPoints,
- &cgs.alienBuildPointsTotal,
- &cgs.humanBuildPoints,
- &cgs.humanBuildPointsTotal,
- &cgs.humanBuildPointsPowered );
+ const char *alienStages = CG_ConfigString( CS_ALIEN_STAGES );
+ const char *humanStages = CG_ConfigString( CS_HUMAN_STAGES );
- sscanf( CG_ConfigString( CS_STAGES ), "%d %d %d %d %d %d", &cgs.alienStage, &cgs.humanStage,
- &cgs.alienKills, &cgs.humanKills, &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
- sscanf( CG_ConfigString( CS_SPAWNS ), "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
+ if( alienStages[0] )
+ {
+ sscanf( alienStages, "%d %d %d", &cgs.alienStage, &cgs.alienCredits,
+ &cgs.alienNextStageThreshold );
+ }
+ else
+ cgs.alienStage = cgs.alienCredits = cgs.alienNextStageThreshold = 0;
+
+
+ if( humanStages[0] )
+ {
+ sscanf( humanStages, "%d %d %d", &cgs.humanStage, &cgs.humanCredits,
+ &cgs.humanNextStageThreshold );
+ }
+ else
+ cgs.humanStage = cgs.humanCredits = cgs.humanNextStageThreshold = 0;
cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
- cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
+ cg.warmupTime = atoi( CG_ConfigString( CS_WARMUP ) );
}
@@ -224,7 +237,7 @@ CG_AnnounceAlienStageTransistion
*/
static void CG_AnnounceAlienStageTransistion( stage_t from, stage_t to )
{
- if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_ALIENS )
return;
trap_S_StartLocalSound( cgs.media.alienStageTransition, CHAN_ANNOUNCER );
@@ -238,7 +251,7 @@ CG_AnnounceHumanStageTransistion
*/
static void CG_AnnounceHumanStageTransistion( stage_t from, stage_t to )
{
- if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ if( cg.predictedPlayerState.stats[ STAT_TEAM ] != TEAM_HUMANS )
return;
trap_S_StartLocalSound( cgs.media.humanStageTransition, CHAN_ANNOUNCER );
@@ -272,91 +285,72 @@ static void CG_ConfigStringModified( void )
CG_ParseServerinfo( );
else if( num == CS_WARMUP )
CG_ParseWarmup( );
- else if( num == CS_BUILDPOINTS )
- sscanf( str, "%d %d %d %d %d", &cgs.alienBuildPoints,
- &cgs.alienBuildPointsTotal,
- &cgs.humanBuildPoints,
- &cgs.humanBuildPointsTotal,
- &cgs.humanBuildPointsPowered );
- else if( num == CS_STAGES )
+ else if( num == CS_ALIEN_STAGES )
{
stage_t oldAlienStage = cgs.alienStage;
- stage_t oldHumanStage = cgs.humanStage;
-
- sscanf( str, "%d %d %d %d %d %d",
- &cgs.alienStage, &cgs.humanStage,
- &cgs.alienKills, &cgs.humanKills,
- &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
-
- if( cgs.alienStage != oldAlienStage )
- CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage );
- if( cgs.humanStage != oldHumanStage )
- CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage );
- }
- else if( num == CS_SPAWNS )
- sscanf( str, "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
- else if( num == CS_LEVEL_START_TIME )
- cgs.levelStartTime = atoi( str );
- else if( num == CS_VOTE_TIME )
- {
- cgs.voteTime = atoi( str );
- cgs.voteModified = qtrue;
+ if( str[0] )
+ {
+ sscanf( str, "%d %d %d", &cgs.alienStage, &cgs.alienCredits,
+ &cgs.alienNextStageThreshold );
- if( cgs.voteTime )
- trap_Cvar_Set( "ui_voteActive", "1" );
+ if( cgs.alienStage != oldAlienStage )
+ CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage );
+ }
else
- trap_Cvar_Set( "ui_voteActive", "0" );
- }
- 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;
+ {
+ cgs.alienStage = cgs.alienCredits = cgs.alienNextStageThreshold = 0;
+ }
}
- 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 )
+ else if( num == CS_HUMAN_STAGES )
{
- int cs_offset = num - CS_TEAMVOTE_TIME;
-
- cgs.teamVoteTime[ cs_offset ] = atoi( str );
- cgs.teamVoteModified[ cs_offset ] = qtrue;
+ stage_t oldHumanStage = cgs.humanStage;
- if( cs_offset == 0 )
+ if( str[0] )
{
- if( cgs.teamVoteTime[ cs_offset ] )
- trap_Cvar_Set( "ui_humanTeamVoteActive", "1" );
- else
- trap_Cvar_Set( "ui_humanTeamVoteActive", "0" );
+ sscanf( str, "%d %d %d", &cgs.humanStage, &cgs.humanCredits,
+ &cgs.humanNextStageThreshold );
+
+ if( cgs.humanStage != oldHumanStage )
+ CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage );
}
- else if( cs_offset == 1 )
+ else
{
- if( cgs.teamVoteTime[ cs_offset ] )
- trap_Cvar_Set( "ui_alienTeamVoteActive", "1" );
- else
- trap_Cvar_Set( "ui_alienTeamVoteActive", "0" );
+ cgs.humanStage = cgs.humanCredits = cgs.humanNextStageThreshold = 0;
}
}
- else if( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 )
+ else if( num == CS_LEVEL_START_TIME )
+ cgs.levelStartTime = atoi( str );
+ else if( num >= CS_VOTE_TIME && num < CS_VOTE_TIME + NUM_TEAMS )
{
- cgs.teamVoteYes[ num - CS_TEAMVOTE_YES ] = atoi( str );
- cgs.teamVoteModified[ num - CS_TEAMVOTE_YES ] = qtrue;
+ cgs.voteTime[ num - CS_VOTE_TIME ] = atoi( str );
+ cgs.voteModified[ num - CS_VOTE_TIME ] = qtrue;
+
+ if( num - CS_VOTE_TIME == TEAM_NONE )
+ trap_Cvar_Set( "ui_voteActive", cgs.voteTime[ TEAM_NONE ] ? "1" : "0" );
+ else if( num - CS_VOTE_TIME == TEAM_ALIENS )
+ trap_Cvar_Set( "ui_alienTeamVoteActive",
+ cgs.voteTime[ TEAM_ALIENS ] ? "1" : "0" );
+ else if( num - CS_VOTE_TIME == TEAM_HUMANS )
+ trap_Cvar_Set( "ui_humanTeamVoteActive",
+ cgs.voteTime[ TEAM_HUMANS ] ? "1" : "0" );
}
- else if( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 )
+ else if( num >= CS_VOTE_YES && num < CS_VOTE_YES + NUM_TEAMS )
{
- cgs.teamVoteNo[ num - CS_TEAMVOTE_NO ] = atoi( str );
- cgs.teamVoteModified[ num - CS_TEAMVOTE_NO ] = qtrue;
+ cgs.voteYes[ num - CS_VOTE_YES ] = atoi( str );
+ cgs.voteModified[ num - CS_VOTE_YES ] = qtrue;
}
- else if( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 )
+ else if( num >= CS_VOTE_NO && num < CS_VOTE_NO + NUM_TEAMS )
{
- Q_strncpyz( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ], str,
- sizeof( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ] ) );
+ cgs.voteNo[ num - CS_VOTE_NO ] = atoi( str );
+ cgs.voteModified[ num - CS_VOTE_NO ] = qtrue;
}
+ else if( num >= CS_VOTE_STRING && num < CS_VOTE_STRING + NUM_TEAMS )
+ Q_strncpyz( cgs.voteString[ num - CS_VOTE_STRING ], str,
+ sizeof( cgs.voteString[ num - CS_VOTE_STRING ] ) );
+ else if( num >= CS_VOTE_CALLER && num < CS_VOTE_CALLER + NUM_TEAMS )
+ Q_strncpyz( cgs.voteCaller[ num - CS_VOTE_CALLER ], str,
+ sizeof( cgs.voteCaller[ num - CS_VOTE_CALLER ] ) );
else if( num == CS_INTERMISSION )
cg.intermissionStarted = atoi( str );
else if( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS )
@@ -413,7 +407,7 @@ static void CG_MapRestart( void )
cg.intermissionStarted = qfalse;
- cgs.voteTime = 0;
+ cgs.voteTime[ TEAM_NONE ] = 0;
cg.mapRestart = qtrue;
@@ -423,574 +417,903 @@ static void CG_MapRestart( void )
// 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 )
- CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH * 2 );
-
trap_Cvar_Set( "cg_thirdPerson", "0" );
}
/*
-=================
-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_SetUIVars
-
-Set some cvars used by the UI
-===============
-*/
-static void CG_SetUIVars( void )
-{
- int i;
- char carriageCvar[ MAX_TOKEN_CHARS ];
-
- *carriageCvar = 0;
-
- //determine what the player is carrying
- for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
- {
- if( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) &&
- BG_FindPurchasableForWeapon( i ) )
- strcat( carriageCvar, va( "W%d ", i ) );
- }
- for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
- {
- if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) &&
- BG_FindPurchasableForUpgrade( i ) )
- strcat( carriageCvar, va( "U%d ", i ) );
- }
- strcat( carriageCvar, "$" );
-
- trap_Cvar_Set( "ui_carriage", carriageCvar );
-
- trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) );
-}
-
-
-/*
==============
CG_Menu
==============
*/
-void CG_Menu( int menu )
+void CG_Menu( int menu, int arg )
{
- const char *cmd = NULL; // command to send
- const char *longMsg = NULL; // command parameter
- const char *shortMsg = NULL; // non-modal version of message
- CG_SetUIVars( );
+ const char *cmd; // command to send
+ const char *longMsg = NULL; // command parameter
+ const char *shortMsg = NULL; // non-modal version of message
+ const char *dialog;
+ dialogType_t type = 0; // controls which cg_disable var will switch it off
+
+ switch( cg.snap->ps.stats[ STAT_TEAM ] )
+ {
+ case TEAM_ALIENS:
+ dialog = "menu tremulous_alien_dialog\n";
+ break;
+ case TEAM_HUMANS:
+ dialog = "menu tremulous_human_dialog\n";
+ break;
+ default:
+ dialog = "menu tremulous_default_dialog\n";
+ }
+ cmd = dialog;
- // string literals have static storage duration, this is safe,
- // cleaner and much more readable.
switch( menu )
{
case MN_TEAM:
cmd = "menu tremulous_teamselect\n";
+ type = DT_INTERACTIVE;
break;
case MN_A_CLASS:
cmd = "menu tremulous_alienclass\n";
+ type = DT_INTERACTIVE;
break;
case MN_H_SPAWN:
cmd = "menu tremulous_humanitem\n";
+ type = DT_INTERACTIVE;
break;
case MN_A_BUILD:
cmd = "menu tremulous_alienbuild\n";
+ type = DT_INTERACTIVE;
break;
case MN_H_BUILD:
cmd = "menu tremulous_humanbuild\n";
+ type = DT_INTERACTIVE;
break;
case MN_H_ARMOURY:
cmd = "menu tremulous_humanarmoury\n";
+ type = DT_INTERACTIVE;
+ break;
+
+ case MN_H_UNKNOWNITEM:
+ shortMsg = "Unknown item";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_A_TEAMFULL:
longMsg = "The alien team has too many players. Please wait until slots "
"become available or join the human team.";
- shortMsg = "The alien team has too many players\n";
- cmd = "menu tremulous_alien_dialog\n";
+ shortMsg = "The alien team has too many players";
+ type = DT_COMMAND;
break;
case MN_H_TEAMFULL:
longMsg = "The human team has too many players. Please wait until slots "
"become available or join the alien team.";
- shortMsg = "The human team has too many players\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "The human team has too many players";
+ type = DT_COMMAND;
break;
- case MN_A_TEAMCHANGEBUILDTIMER:
- longMsg = "You cannot leave the Alien team until your build timer "
- "has expired.";
- shortMsg = "You cannot change teams until your build timer expires.\n";
- cmd = "menu tremulous_alien_dialog\n";
+ case MN_A_TEAMLOCKED:
+ longMsg = "The alien team is locked. You cannot join the aliens "
+ "at this time.";
+ shortMsg = "The alien team is locked";
+ type = DT_COMMAND;
break;
- case MN_H_TEAMCHANGEBUILDTIMER:
- longMsg = "You cannot leave the Human team until your build timer "
- "has expired.";
- shortMsg = "You cannot change teams until your build timer expires.\n";
- cmd = "menu tremulous_human_dialog\n";
+ case MN_H_TEAMLOCKED:
+ longMsg = "The human team is locked. You cannot join the humans "
+ "at this time.";
+ shortMsg = "The human team is locked";
+ type = DT_COMMAND;
+ break;
+
+ case MN_PLAYERLIMIT:
+ longMsg = "The maximum number of playing clients has been reached. "
+ "Please wait until slots become available.";
+ shortMsg = "No free player slots";
+ type = DT_COMMAND;
break;
//===============================
- case MN_H_NOROOM:
- longMsg = "There is no room to build here. Move until the buildable turns "
- "translucent green indicating a valid build location.";
- shortMsg = "There is no room to build here\n";
- cmd = "menu tremulous_human_dialog\n";
+ // Since cheating commands have no default binds, they will often be done
+ // via console. In light of this, perhaps opening a menu is
+ // counterintuitive
+ case MN_CMD_CHEAT:
+ //longMsg = "This action is considered cheating. It can only be used "
+ // "in cheat mode, which is not enabled on this server.";
+ shortMsg = "Cheats are not enabled on this server";
+ type = DT_COMMAND;
break;
- case MN_H_NOPOWER:
- longMsg = "There is no power remaining. Free up power by destroying "
- "existing buildable objects.";
- shortMsg = "There is no power remaining\n";
- cmd = "menu tremulous_human_dialog\n";
+ case MN_CMD_CHEAT_TEAM:
+ shortMsg = "Cheats are not enabled on this server, so "
+ "you may not use this command while on a team";
+ type = DT_COMMAND;
break;
- case MN_H_NOTPOWERED:
- longMsg = "This buildable is not powered. Build a Reactor and/or Repeater "
- "in order to power it.";
- shortMsg = "This buildable is not powered\n";
- cmd = "menu tremulous_human_dialog\n";
+ case MN_CMD_TEAM:
+ //longMsg = "You must be on a team to perform this action. Join the alien"
+ // "or human team and try again.";
+ shortMsg = "Join a team first";
+ type = DT_COMMAND;
break;
- case MN_H_NORMAL:
+ case MN_CMD_SPEC:
+ //longMsg = "You may not perform this action while on a team. Become a "
+ // "spectator before trying again.";
+ shortMsg = "You can only use this command when spectating";
+ type = DT_COMMAND;
+ break;
+
+ case MN_CMD_ALIEN:
+ //longMsg = "You must be on the alien team to perform this action.";
+ shortMsg = "Must be alien to use this command";
+ type = DT_COMMAND;
+ break;
+
+ case MN_CMD_HUMAN:
+ //longMsg = "You must be on the human team to perform this action.";
+ shortMsg = "Must be human to use this command";
+ type = DT_COMMAND;
+ break;
+
+ case MN_CMD_ALIVE:
+ //longMsg = "You must be alive to perform this action.";
+ shortMsg = "Must be alive to use this command";
+ type = DT_COMMAND;
+ break;
+
+
+ //===============================
+
+ case MN_B_NOROOM:
+ longMsg = "There is no room to build here. Move until the structure turns "
+ "translucent green, indicating a valid build location.";
+ shortMsg = "There is no room to build here";
+ type = DT_BUILD;
+ break;
+
+ case MN_B_NORMAL:
longMsg = "Cannot build on this surface. The surface is too steep or "
- "unsuitable to build on. Please choose another site for this "
- "structure.";
- shortMsg = "Cannot build on this surface\n";
- cmd = "menu tremulous_human_dialog\n";
+ "unsuitable for building. Please choose another site for this "
+ "structure.";
+ shortMsg = "Cannot build on this surface";
+ type = DT_BUILD;
+ break;
+
+ case MN_B_CANNOT:
+ longMsg = NULL;
+ shortMsg = "You cannot build that structure";
+ type = DT_BUILD;
+ break;
+
+ // FIXME: MN_H_ and MN_A_?
+ case MN_B_LASTSPAWN:
+ longMsg = "This action would remove your team's last spawn point, "
+ "which often quickly results in a loss. Try building more "
+ "spawns.";
+ shortMsg = "You may not deconstruct the last spawn";
+ break;
+
+ case MN_B_SUDDENDEATH:
+ longMsg = "Neither team has prevailed after a certain time and the "
+ "game has entered Sudden Death. During Sudden Death "
+ "building is not allowed.";
+ shortMsg = "Cannot build during Sudden Death";
+ type = DT_BUILD;
+ break;
+
+ case MN_B_REVOKED:
+ longMsg = "Your teammates have lost faith in your ability to build "
+ "for the team. You will not be allowed to build until your "
+ "team votes to reinstate your building rights.";
+ shortMsg = "Your building rights have been revoked";
+ type = DT_BUILD;
break;
- case MN_H_REACTOR:
- longMsg = "There can only be one Reactor. Destroy the existing one if you "
+ case MN_B_SURRENDER:
+ longMsg = "Your team has decided to admit defeat and concede the game:"
+ "traitors and cowards are not allowed to build.";
+ // too harsh?
+ shortMsg = "Building is denied to traitorous cowards";
+ break;
+
+ //===============================
+
+ case MN_H_NOBP:
+ if( cgs.markDeconstruct )
+ longMsg = "There is no power remaining. Free up power by marking "
+ "existing buildable objects.";
+ else
+ longMsg = "There is no power remaining. Free up power by deconstructing "
+ "existing buildable objects.";
+ shortMsg = "There is no power remaining";
+ type = DT_BUILD;
+ break;
+
+ case MN_H_NOTPOWERED:
+ longMsg = "This buildable is not powered. Build a Reactor and/or Repeater "
+ "in order to power it.";
+ shortMsg = "This buildable is not powered";
+ type = DT_BUILD;
+ break;
+
+ case MN_H_ONEREACTOR:
+ longMsg = "There can only be one Reactor. Deconstruct the existing one if you "
"wish to move it.";
- shortMsg = "There can only be one Reactor\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "There can only be one Reactor";
+ type = DT_BUILD;
break;
- case MN_H_REPEATER:
+ case MN_H_NOPOWERHERE:
longMsg = "There is no power here. If available, a Repeater may be used to "
"transmit power to this location.";
- shortMsg = "There is no power here\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "There is no power here";
+ type = DT_BUILD;
break;
case MN_H_NODCC:
longMsg = "There is no Defense Computer. A Defense Computer is needed to "
"build this.";
- shortMsg = "There is no Defense Computer\n";
- cmd = "menu tremulous_human_dialog\n";
- break;
-
- case MN_H_TNODEWARN:
- longMsg = "WARNING: This Telenode will not be powered. Build near a power "
- "structure to prevent seeing this message again.";
- shortMsg = "This Telenode will not be powered\n";
- cmd = "menu tremulous_human_dialog\n";
- break;
-
- case MN_H_RPTWARN:
- longMsg = "WARNING: This Repeater will not be powered as there is no parent "
- "Reactor providing power. Build a Reactor.";
- shortMsg = "This Repeater will not be powered\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "There is no Defense Computer";
+ type = DT_BUILD;
break;
- case MN_H_RPTWARN2:
+ case MN_H_RPTPOWERHERE:
longMsg = "This area already has power. A Repeater is not required here.";
- shortMsg = "This area already has power\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "This area already has power";
+ type = DT_BUILD;
break;
case MN_H_NOSLOTS:
longMsg = "You have no room to carry this. Please sell any conflicting "
"upgrades before purchasing this item.";
- shortMsg = "You have no room to carry this\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "You have no room to carry this";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_H_NOFUNDS:
longMsg = "Insufficient funds. You do not have enough credits to perform "
"this action.";
- shortMsg = "Insufficient funds\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "Insufficient funds";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_H_ITEMHELD:
longMsg = "You already hold this item. It is not possible to carry multiple "
"items of the same type.";
- shortMsg = "You already hold this item\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "You already hold this item";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_H_NOARMOURYHERE:
longMsg = "You must be near a powered Armoury in order to purchase "
- "weapons, upgrades or non-energy ammunition.";
- shortMsg = "You must be near a powered Armoury\n";
- cmd = "menu tremulous_human_dialog\n";
+ "weapons, upgrades or ammunition.";
+ shortMsg = "You must be near a powered Armoury";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_H_NOENERGYAMMOHERE:
- longMsg = "You must be near an Armoury, Reactor or Repeater in order "
- "to purchase energy ammunition.";
- shortMsg = "You must be near an Armoury, Reactor or Repeater\n";
- cmd = "menu tremulous_human_dialog\n";
+ longMsg = "You must be near a Reactor or a powered Armoury or Repeater "
+ "in order to purchase energy ammunition.";
+ shortMsg = "You must be near a Reactor or a powered Armoury or Repeater";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_H_NOROOMBSUITON:
longMsg = "There is not enough room here to put on a Battle Suit. "
"Make sure you have enough head room to climb in.";
- shortMsg = "Not enough room here to put on a Battle Suit\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "Not enough room here to put on a Battle Suit";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_H_NOROOMBSUITOFF:
longMsg = "There is not enough room here to take off your Battle Suit. "
"Make sure you have enough head room to climb out.";
- shortMsg = "Not enough room here to take off your Battle Suit\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "Not enough room here to take off your Battle Suit";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_H_ARMOURYBUILDTIMER:
longMsg = "You are not allowed to buy or sell weapons until your "
"build timer has expired.";
- shortMsg = "You can not buy or sell weapos until your build timer "
- "expires\n";
- cmd = "menu tremulous_human_dialog\n";
+ shortMsg = "You can not buy or sell weapons until your build timer "
+ "expires";
+ type = DT_ARMOURYEVOLVE;
break;
+ case MN_H_DEADTOCLASS:
+ shortMsg = "You must be dead to use the class command";
+ type = DT_COMMAND;
+ break;
- //===============================
-
- case MN_A_NOROOM:
- longMsg = "There is no room to build here. Move until the structure turns "
- "translucent green indicating a valid build location.";
- shortMsg = "There is no room to build here\n";
- cmd = "menu tremulous_alien_dialog\n";
+ case MN_H_UNKNOWNSPAWNITEM:
+ shortMsg = "Unknown starting item";
+ type = DT_COMMAND;
break;
+ //===============================
+
case MN_A_NOCREEP:
longMsg = "There is no creep here. You must build near existing Eggs or "
"the Overmind. Alien structures will not support themselves.";
- shortMsg = "There is no creep here\n";
- cmd = "menu tremulous_alien_dialog\n";
+ shortMsg = "There is no creep here";
+ type = DT_BUILD;
break;
case MN_A_NOOVMND:
longMsg = "There is no Overmind. An Overmind must be built to control "
- "the structure you tried to place";
- shortMsg = "There is no Overmind\n";
- cmd = "menu tremulous_alien_dialog\n";
- break;
-
- case MN_A_OVERMIND:
- longMsg = "There can only be one Overmind. Destroy the existing one if you "
- "wish to move it.";
- shortMsg = "There can only be one Overmind\n";
- cmd = "menu tremulous_alien_dialog\n";
+ "the structure you tried to place.";
+ shortMsg = "There is no Overmind";
+ type = DT_BUILD;
break;
- case MN_A_HOVEL:
- longMsg = "There can only be one Hovel. Destroy the existing one if you "
+ case MN_A_ONEOVERMIND:
+ longMsg = "There can only be one Overmind. Deconstruct the existing one if you "
"wish to move it.";
- shortMsg = "There can only be one Hovel\n";
- cmd = "menu tremulous_alien_dialog\n";
+ shortMsg = "There can only be one Overmind";
+ type = DT_BUILD;
break;
- case MN_A_NOASSERT:
- longMsg = "The Overmind cannot control any more structures. Destroy existing "
+ case MN_A_NOBP:
+ longMsg = "The Overmind cannot control any more structures. Deconstruct existing "
"structures to build more.";
- shortMsg = "The Overmind cannot control any more structures\n";
- cmd = "menu tremulous_alien_dialog\n";
- break;
-
- case MN_A_SPWNWARN:
- longMsg = "WARNING: This spawn will not be controlled by an Overmind. "
- "Build an Overmind to prevent seeing this message again.";
- shortMsg = "This spawn will not be controlled by an Overmind\n";
- cmd = "menu tremulous_alien_dialog\n";
- break;
-
- case MN_A_NORMAL:
- longMsg = "Cannot build on this surface. This surface is too steep or "
- "unsuitable to build on. Please choose another site for this "
- "structure.";
- shortMsg = "Cannot build on this surface\n";
- cmd = "menu tremulous_alien_dialog\n";
+ shortMsg = "The Overmind cannot control any more structures";
+ type = DT_BUILD;
break;
case MN_A_NOEROOM:
longMsg = "There is no room to evolve here. Move away from walls or other "
- "nearby objects and try again.";
- cmd = "menu tremulous_alien_dialog\n";
- shortMsg = "There is no room to evolve here\n";
+ "nearby objects and try again.";
+ shortMsg = "There is no room to evolve here";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_A_TOOCLOSE:
longMsg = "This location is too close to the enemy to evolve. Move away "
- "until you are no longer aware of the enemy's presence and try "
- "again.";
- shortMsg = "This location is too close to the enemy to evolve\n";
- cmd = "menu tremulous_alien_dialog\n";
+ "from the enemy's presence and try again.";
+ shortMsg = "This location is too close to the enemy to evolve";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_A_NOOVMND_EVOLVE:
longMsg = "There is no Overmind. An Overmind must be built to allow "
"you to upgrade.";
- shortMsg = "There is no Overmind\n";
- cmd = "menu tremulous_alien_dialog\n";
+ shortMsg = "There is no Overmind";
+ type = DT_ARMOURYEVOLVE;
break;
case MN_A_EVOLVEBUILDTIMER:
- longMsg = "You cannot Evolve until your build timer has expired.";
- shortMsg = "You cannot Evolve until your build timer expires\n";
- cmd = "menu tremulous_alien_dialog\n";
+ longMsg = "You cannot evolve until your build timer has expired.";
+ shortMsg = "You cannot evolve until your build timer expires";
+ type = DT_ARMOURYEVOLVE;
break;
- case MN_A_HOVEL_OCCUPIED:
- longMsg = "This Hovel is already occupied by another builder.";
- shortMsg = "This Hovel is already occupied by another builder\n";
- cmd = "menu tremulous_alien_dialog\n";
+ case MN_A_INFEST:
+ trap_Cvar_Set( "ui_currentClass",
+ va( "%d %d", cg.snap->ps.stats[ STAT_CLASS ],
+ cg.snap->ps.persistant[ PERS_CREDIT ] ) );
+
+ cmd = "menu tremulous_alienupgrade\n";
+ type = DT_INTERACTIVE;
break;
- case MN_A_HOVEL_BLOCKED:
- longMsg = "The exit to this Hovel is currently blocked. Please wait until it "
- "becomes clear then try again.";
- shortMsg = "The exit to this Hovel is currently blocked\n";
- cmd = "menu tremulous_alien_dialog\n";
+ case MN_A_CANTEVOLVE:
+ shortMsg = va( "You cannot evolve into a %s",
+ BG_ClassConfig( arg )->humanName );
+ type = DT_ARMOURYEVOLVE;
break;
- case MN_A_HOVEL_EXIT:
- longMsg = "The exit to this Hovel would always be blocked. Please choose "
- "a more suitable location.";
- shortMsg = "The exit to this Hovel would always be blocked\n";
- cmd = "menu tremulous_alien_dialog\n";
+ case MN_A_EVOLVEWALLWALK:
+ shortMsg = "You cannot evolve while wallwalking";
+ type = DT_ARMOURYEVOLVE;
break;
- case MN_A_INFEST:
- trap_Cvar_Set( "ui_currentClass", va( "%d %d", cg.snap->ps.stats[ STAT_PCLASS ],
- cg.snap->ps.persistant[ PERS_CREDIT ] ) );
- cmd = "menu tremulous_alienupgrade\n";
+ case MN_A_UNKNOWNCLASS:
+ shortMsg = "Unknown class";
+ type = DT_ARMOURYEVOLVE;
+ break;
+
+ case MN_A_CLASSNOTSPAWN:
+ shortMsg = va( "You cannot spawn as a %s",
+ BG_ClassConfig( arg )->humanName );
+ type = DT_ARMOURYEVOLVE;
+ break;
+
+ case MN_A_CLASSNOTALLOWED:
+ shortMsg = va( "The %s is not allowed",
+ BG_ClassConfig( arg )->humanName );
+ type = DT_ARMOURYEVOLVE;
+ break;
+
+ case MN_A_CLASSNOTATSTAGE:
+ shortMsg = va( "The %s is not allowed at Stage %d",
+ BG_ClassConfig( arg )->humanName,
+ cgs.alienStage + 1 );
+ type = DT_ARMOURYEVOLVE;
break;
default:
Com_Printf( "cgame: debug: no such menu %d\n", menu );
}
+
+ if( type == DT_ARMOURYEVOLVE && cg_disableUpgradeDialogs.integer )
+ return;
- if( !cg_disableWarningDialogs.integer || !shortMsg )
- {
- // Player either wants dialog window or there's no short message
- if( cmd )
- {
- if( longMsg )
- trap_Cvar_Set( "ui_dialog", longMsg );
+ if( type == DT_BUILD && cg_disableBuildDialogs.integer )
+ return;
- trap_SendConsoleCommand( cmd );
- }
- }
- else
+ if( type == DT_COMMAND && cg_disableCommandDialogs.integer )
+ return;
+
+ if( cmd != dialog )
+ {
+ trap_SendConsoleCommand( cmd );
+ }
+ else if( longMsg && cg_disableWarningDialogs.integer == 0 )
{
- // There is short message and player wants it
- CG_Printf( shortMsg );
- }
+ trap_Cvar_Set( "ui_dialog", longMsg );
+ trap_SendConsoleCommand( cmd );
+ }
+ else if( shortMsg && cg_disableWarningDialogs.integer < 2 )
+ {
+ CG_Printf( "%s\n", shortMsg );
+ }
}
/*
=================
-CG_ServerCommand
-
-The string has been tokenized and can be retrieved with
-Cmd_Argc() / Cmd_Argv()
+CG_Say
=================
*/
-static void CG_ServerCommand( void )
+static void CG_Say( int clientNum, saymode_t mode, const char *text )
{
- const char *cmd;
- char text[ MAX_SAY_TEXT ];
+ char *name;
+ char prefix[ 11 ] = "";
+ char *ignore = "";
+ const char *location = "";
+ char *color;
+ char *maybeColon;
+
+ if( clientNum >= 0 && clientNum < MAX_CLIENTS )
+ {
+ clientInfo_t *ci = &cgs.clientinfo[ clientNum ];
+ char *tcolor = S_COLOR_WHITE;
- cmd = CG_Argv( 0 );
+ name = ci->name;
- if( !cmd[ 0 ] )
- {
- // server claimed the command
- return;
- }
+ if( ci->team == TEAM_ALIENS )
+ tcolor = S_COLOR_RED;
+ else if( ci->team == TEAM_HUMANS )
+ tcolor = S_COLOR_CYAN;
- if( !strcmp( cmd, "cp" ) )
- {
- CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
- return;
- }
+ if( cg_chatTeamPrefix.integer )
+ Com_sprintf( prefix, sizeof( prefix ), "[%s%c" S_COLOR_WHITE "] ",
+ tcolor, toupper( *( BG_TeamName( ci->team ) ) ) );
- if( !strcmp( cmd, "cs" ) )
- {
- CG_ConfigStringModified( );
- return;
+ if( ( mode == SAY_TEAM || mode == SAY_AREA ) &&
+ cg.snap->ps.pm_type != PM_INTERMISSION )
+ {
+ int locationNum;
+
+ if( clientNum == cg.snap->ps.clientNum )
+ {
+ centity_t *locent;
+
+ locent = CG_GetPlayerLocation( );
+ if( locent )
+ locationNum = locent->currentState.generic1;
+ else
+ locationNum = -1;
+ }
+ else
+ locationNum = ci->location;
+
+ if( locationNum >= 0 && locationNum < MAX_LOCATIONS )
+ {
+ const char *s = CG_ConfigString( CS_LOCATIONS + locationNum );
+
+ if( *s )
+ location = va( " (%s" S_COLOR_WHITE ")", s );
+ }
+ }
}
+ else
+ name = "console";
- if( !strcmp( cmd, "print" ) )
+ // IRC-like /me parsing
+ if( mode != SAY_RAW && Q_stricmpn( text, "/me ", 4 ) == 0 )
{
- CG_Printf( "%s", CG_Argv( 1 ) );
- return;
+ text += 4;
+ Q_strcat( prefix, sizeof( prefix ), "* " );
+ maybeColon = "";
}
+ else
+ maybeColon = ":";
- if( !strcmp( cmd, "chat" ) )
+ switch( mode )
{
- if( !cg_teamChatsOnly.integer )
- {
- Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
- if( Q_stricmpn( text, "[skipnotify]", 12 ) )
- trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
- CG_RemoveChatEscapeChar( text );
+ case SAY_ALL:
+ // might already be ignored but in that case no harm is done
+ if( cg_teamChatsOnly.integer )
+ ignore = "[skipnotify]";
+
+#ifdef MODULE_INTERFACE_11
+ CG_Printf( "%s%s%s" S_COLOR_WHITE "%s " S_COLOR_GREEN "%s\n",
+ ignore, prefix, name, maybeColon, text );
+#else
+ CG_Printf( "%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_GREEN "%s\n",
+ ignore, prefix, name, maybeColon, INDENT_MARKER, text );
+#endif
+ break;
+ case SAY_TEAM:
+#ifdef MODULE_INTERFACE_11
+ CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s " S_COLOR_CYAN "%s\n",
+ ignore, prefix, name, location, maybeColon, text );
+#else
+ CG_Printf( "%s%s(%s" S_COLOR_WHITE ")%s%s %c" S_COLOR_CYAN "%s\n",
+ ignore, prefix, name, location, maybeColon, INDENT_MARKER, text );
+#endif
+ break;
+ case SAY_ADMINS:
+ case SAY_ADMINS_PUBLIC:
+#ifdef MODULE_INTERFACE_11
+ CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s " S_COLOR_MAGENTA "%s\n",
+ ignore, prefix,
+ ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]",
+ name, maybeColon, text );
+#else
+ CG_Printf( "%s%s%s%s" S_COLOR_WHITE "%s %c" S_COLOR_MAGENTA "%s\n",
+ ignore, prefix,
+ ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]",
+ name, maybeColon, INDENT_MARKER, text );
+#endif
+ break;
+ case SAY_AREA:
+#ifdef MODULE_INTERFACE_11
+ CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s " S_COLOR_BLUE "%s\n",
+ ignore, prefix, name, location, maybeColon, text );
+#else
+ CG_Printf( "%s%s<%s" S_COLOR_WHITE ">%s%s %c" S_COLOR_BLUE "%s\n",
+ ignore, prefix, name, location, maybeColon, INDENT_MARKER, text );
+#endif
+ break;
+ case SAY_PRIVMSG:
+ case SAY_TPRIVMSG:
+ color = ( mode == SAY_TPRIVMSG ) ? S_COLOR_CYAN : S_COLOR_GREEN;
+#ifdef MODULE_INTERFACE_11
+ CG_Printf( "%s%s[%s" S_COLOR_WHITE " -> %s" S_COLOR_WHITE "]%s %s%s\n",
+ ignore, prefix, name, cgs.clientinfo[ cg.clientNum ].name,
+ maybeColon, color, text );
+#else
+ CG_Printf( "%s%s[%s" S_COLOR_WHITE " -> %s" S_COLOR_WHITE "]%s %c%s%s\n",
+ ignore, prefix, name, cgs.clientinfo[ cg.clientNum ].name,
+ maybeColon, INDENT_MARKER, color, text );
+#endif
+ if( !ignore[0] )
+ {
+ CG_CenterPrint( va( "%sPrivate message from: " S_COLOR_WHITE "%s",
+ color, name ), 200, GIANTCHAR_WIDTH * 4 );
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS )
+ clientNum = cg.clientNum;
+ CG_Printf( ">> to reply, say: /m %d [your message] <<\n", clientNum );
+ }
+ break;
+ case SAY_RAW:
CG_Printf( "%s\n", text );
- }
-
- return;
+ break;
}
- if( !strcmp( cmd, "tchat" ) )
+ switch( mode )
{
- Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
- if( Q_stricmpn( text, "[skipnotify]", 12 ) )
- {
- if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ case SAY_TEAM:
+ case SAY_AREA:
+ case SAY_TPRIVMSG:
+ if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )
+ {
trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND );
- else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ break;
+ }
+ else if( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )
+ {
trap_S_StartLocalSound( cgs.media.humanTalkSound, CHAN_LOCAL_SOUND );
- else
- trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
- }
- CG_RemoveChatEscapeChar( text );
- CG_Printf( "%s\n", text );
- return;
+ break;
+ }
+ default:
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
}
+}
- if( !strcmp( cmd, "scores" ) )
- {
- CG_ParseScores( );
- return;
- }
+/*
+=================
+CG_VoiceTrack
- if( !strcmp( cmd, "tinfo" ) )
+return the voice indexed voice track or print errors quietly to console
+in case someone is on an unpure server and wants to know which voice pak
+is missing or incomplete
+=================
+*/
+static voiceTrack_t *CG_VoiceTrack( char *voice, int cmd, int track )
+{
+ voice_t *v;
+ voiceCmd_t *c;
+ voiceTrack_t *t;
+
+ v = BG_VoiceByName( cgs.voices, voice );
+ if( !v )
{
- CG_ParseTeamInfo( );
- return;
+ CG_Printf( "[skipnotify]WARNING: could not find voice \"%s\"\n", voice );
+ return NULL;
}
-
- if( !strcmp( cmd, "map_restart" ) )
+ c = BG_VoiceCmdByNum( v->cmds, cmd );
+ if( !c )
{
- CG_MapRestart( );
- return;
+ CG_Printf( "[skipnotify]WARNING: could not find command %d "
+ "in voice \"%s\"\n", cmd, voice );
+ return NULL;
}
-
- if( Q_stricmp( cmd, "remapShader" ) == 0 )
+ t = BG_VoiceTrackByNum( c->tracks, track );
+ if( !t )
{
- if( trap_Argc( ) == 4 )
- trap_R_RemapShader( CG_Argv( 1 ), CG_Argv( 2 ), CG_Argv( 3 ) );
+ CG_Printf( "[skipnotify]WARNING: could not find track %d for command %d in "
+ "voice \"%s\"\n", track, cmd, voice );
+ return NULL;
}
+ return t;
+}
- // clientLevelShot is sent before taking a special screenshot for
- // the menu system during development
- if( !strcmp( cmd, "clientLevelShot" ) )
- {
- cg.levelShot = qtrue;
+/*
+=================
+CG_ParseVoice
+
+voice clientNum vChan cmdNum trackNum [sayText]
+=================
+*/
+static void CG_ParseVoice( void )
+{
+ int clientNum;
+ int vChan;
+ char sayText[ MAX_SAY_TEXT] = {""};
+ voiceTrack_t *track;
+ clientInfo_t *ci;
+
+ if( trap_Argc() < 5 || trap_Argc() > 6 )
return;
- }
- //the server has triggered a menu
- if( !strcmp( cmd, "servermenu" ) )
- {
- if( trap_Argc( ) == 2 && !cg.demoPlayback )
- CG_Menu( atoi( CG_Argv( 1 ) ) );
+ if( trap_Argc() == 6 )
+ Q_strncpyz( sayText, CG_Argv( 5 ), sizeof( sayText ) );
+ clientNum = atoi( CG_Argv( 1 ) );
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS )
return;
- }
- //the server thinks this client should close all menus
- if( !strcmp( cmd, "serverclosemenus" ) )
- {
- trap_SendConsoleCommand( "closemenus\n" );
+ vChan = atoi( CG_Argv( 2 ) );
+ if( vChan < 0 || vChan >= VOICE_CHAN_NUM_CHANS )
return;
- }
- //poison cloud effect needs to be reliable
- if( !strcmp( cmd, "poisoncloud" ) )
- {
- cg.poisonedTime = cg.time;
+ if( cg_teamChatsOnly.integer && vChan != VOICE_CHAN_TEAM )
+ return;
- if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) )
- {
- cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS );
- CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity );
- CG_AttachToCent( &cg.poisonCloudPS->attachment );
- }
+ ci = &cgs.clientinfo[ clientNum ];
+ // this joker is still talking
+ if( ci->voiceTime > cg.time )
return;
- }
- if( !strcmp( cmd, "weaponswitch" ) )
+ track = CG_VoiceTrack( ci->voice, atoi( CG_Argv( 3 ) ), atoi( CG_Argv( 4 ) ) );
+
+ // keep track of how long the player will be speaking
+ // assume it takes 3s to say "*unintelligible gibberish*"
+ if( track )
+ ci->voiceTime = cg.time + track->duration;
+ else
+ ci->voiceTime = cg.time + 3000;
+
+ if( !sayText[ 0 ] )
{
- CG_Printf( "client weaponswitch\n" );
- if( trap_Argc( ) == 2 )
+ if( track )
+ Q_strncpyz( sayText, track->text, sizeof( sayText ) );
+ else
+ Q_strncpyz( sayText, "*unintelligible gibberish*", sizeof( sayText ) );
+ }
+
+ if( !cg_noVoiceText.integer )
+ {
+ switch( vChan )
{
- cg.weaponSelect = atoi( CG_Argv( 1 ) );
- cg.weaponSelectTime = cg.time;
+ case VOICE_CHAN_ALL:
+ CG_Say( clientNum, SAY_ALL, sayText );
+ break;
+ case VOICE_CHAN_TEAM:
+ CG_Say( clientNum, SAY_TEAM, sayText );
+ break;
+ default:
+ break;
}
+ }
+ // playing voice audio tracks disabled
+ if( cg_noVoiceChats.integer )
return;
- }
- // server requests a ptrc
- if( !strcmp( cmd, "ptrcrequest" ) )
+ // no audio track to play
+ if( !track )
+ return;
+
+ switch( vChan )
{
- int code = CG_ReadPTRCode( );
+ case VOICE_CHAN_ALL:
+ trap_S_StartLocalSound( track->track, CHAN_VOICE );
+ break;
+ case VOICE_CHAN_TEAM:
+ trap_S_StartLocalSound( track->track, CHAN_VOICE );
+ break;
+ case VOICE_CHAN_LOCAL:
+ trap_S_StartSound( NULL, clientNum, CHAN_VOICE, track->track );
+ break;
+ default:
+ break;
+ }
+}
- trap_SendClientCommand( va( "ptrcverify %d", code ) );
- return;
- }
+/*
+=================
+CG_CenterPrint_f
+=================
+*/
+void CG_CenterPrint_f( void )
+{
+ CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
+}
+
+/*
+=================
+CG_Print_f
+=================
+*/
+static void CG_Print_f( void )
+{
+ CG_Printf( "%s", CG_Argv( 1 ) );
+}
+
+/*
+=================
+CG_Chat_f
+=================
+*/
+static void CG_Chat_f( void )
+{
+ char id[ 3 ];
+ char mode[ 3 ];
+
+ trap_Argv( 1, id, sizeof( id ) );
+ trap_Argv( 2, mode, sizeof( mode ) );
+
+ CG_Say( atoi( id ), atoi( mode ), CG_Argv( 3 ) );
+}
- // server issues a ptrc
- if( !strcmp( cmd, "ptrcissue" ) )
+/*
+=================
+CG_ServerMenu_f
+=================
+*/
+static void CG_ServerMenu_f( void )
+{
+ if( !cg.demoPlayback )
{
if( trap_Argc( ) == 2 )
- {
- int code = atoi( CG_Argv( 1 ) );
+ CG_Menu( atoi( CG_Argv( 1 ) ), 0 );
+ else if( trap_Argc( ) == 3 )
+ CG_Menu( atoi( CG_Argv( 1 ) ), atoi( CG_Argv( 2 ) ) );
+ }
+}
- CG_WritePTRCode( code );
- }
+/*
+=================
+CG_ServerCloseMenus_f
+=================
+*/
+static void CG_ServerCloseMenus_f( void )
+{
+ trap_SendConsoleCommand( "closemenus\n" );
+}
- return;
+/*
+=================
+CG_PoisonCloud_f
+=================
+*/
+static void CG_PoisonCloud_f( void )
+{
+ cg.poisonedTime = cg.time;
+
+ if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) )
+ {
+ cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS );
+ CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity );
+ CG_AttachToCent( &cg.poisonCloudPS->attachment );
}
+}
+
+static char registeredCmds[ 8192 ]; // cmd1\0cmd2\0cmdn\0\0
+static size_t gcmdsOffset;
+static void CG_GameCmds_f( void )
+{
+ int i;
+ int c = trap_Argc( );
+ const char *cmd;
+ size_t len;
- // reply to ptrcverify
- if( !strcmp( cmd, "ptrcconfirm" ) )
+ for( i = 1; i < c; i++ )
{
- trap_SendConsoleCommand( "menu ptrc_popmenu\n" );
+ cmd = CG_Argv( i );
+ len = strlen( cmd ) + 1;
+ if( len + gcmdsOffset >= sizeof( registeredCmds ) - 1 )
+ {
+ CG_Printf( "AddCommand: too many commands (%d >= %d)\n",
+ (int)( len + gcmdsOffset ), (int)( sizeof( registeredCmds ) - 1 ) );
+ return;
+ }
+ trap_AddCommand( cmd );
+ strcpy( registeredCmds + gcmdsOffset, cmd );
+ gcmdsOffset += len;
+ }
+}
+void CG_UnregisterCommands( void )
+{
+ size_t len, offset = 0;
+ while( registeredCmds[ offset ] )
+ {
+ len = strlen( registeredCmds + offset );
+ trap_RemoveCommand( registeredCmds + offset );
+ offset += len + 1;
+ }
+ memset( registeredCmds, 0, 2 );
+ gcmdsOffset = 0;
+}
+
+static consoleCommand_t svcommands[ ] =
+{
+ { "chat", CG_Chat_f },
+ { "cmds", CG_GameCmds_f },
+ { "cp", CG_CenterPrint_f },
+ { "cs", CG_ConfigStringModified },
+ { "map_restart", CG_MapRestart },
+ { "poisoncloud", CG_PoisonCloud_f },
+ { "print", CG_Print_f },
+ { "scores", CG_ParseScores },
+ { "serverclosemenus", CG_ServerCloseMenus_f },
+ { "servermenu", CG_ServerMenu_f },
+ { "tinfo", CG_ParseTeamInfo },
+ { "voice", CG_ParseVoice }
+};
+
+/*
+=================
+CG_ServerCommand
+
+The string has been tokenized and can be retrieved with
+Cmd_Argc() / Cmd_Argv()
+=================
+*/
+static void CG_ServerCommand( void )
+{
+ const char *cmd;
+ consoleCommand_t *command;
+
+ cmd = CG_Argv( 0 );
+ command = bsearch( cmd, svcommands, ARRAY_LEN( svcommands ),
+ sizeof( svcommands[ 0 ] ), cmdcmp );
+
+ if( command )
+ {
+ command->function( );
return;
}