From 425decdf7e9284d15aa726e3ae96b9942fb0e3ea Mon Sep 17 00:00:00 2001 From: IronClawTrem Date: Sun, 16 Feb 2020 03:40:06 +0000 Subject: create tremded branch --- src/cgame/cg_servercmds.c | 1235 ++++++++++++++++++++++++++++----------------- 1 file changed, 779 insertions(+), 456 deletions(-) (limited to 'src/cgame/cg_servercmds.c') 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 + =========================================================================== */ @@ -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; } -- cgit