summaryrefslogtreecommitdiff
path: root/mod/src/game/g_cmds.c
diff options
context:
space:
mode:
authorTim Angus <tim@ngus.net>2005-12-10 03:18:22 +0000
committerTim Angus <tim@ngus.net>2005-12-10 03:18:22 +0000
commitef5d1d446e3c078b81882c2eda6525aee7ccfa1e (patch)
tree16c1e9460317f2ca507f8e1888216816d05f251c /mod/src/game/g_cmds.c
parent3b447421efc76ba76fbdae62f893fc6916af5433 (diff)
* Moved existing src directory out the way whilst I merge ioq3
Diffstat (limited to 'mod/src/game/g_cmds.c')
-rw-r--r--mod/src/game/g_cmds.c2305
1 files changed, 2305 insertions, 0 deletions
diff --git a/mod/src/game/g_cmds.c b/mod/src/game/g_cmds.c
new file mode 100644
index 00000000..819fbb75
--- /dev/null
+++ b/mod/src/game/g_cmds.c
@@ -0,0 +1,2305 @@
+// Copyright (C) 1999-2000 Id Software, Inc.
+//
+
+/*
+ * Portions Copyright (C) 2000-2001 Tim Angus
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the OSML - Open Source Modification License v1.0 as
+ * described in the file COPYING which is distributed with this source
+ * code.
+ *
+ * This program 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.
+ */
+
+#include "g_local.h"
+
+/*
+==================
+G_SanitiseName
+
+Remove case and control characters from a player name
+==================
+*/
+void G_SanitiseName( char *in, char *out )
+{
+ while( *in )
+ {
+ if( *in == 27 )
+ {
+ in += 2; // skip color code
+ continue;
+ }
+
+ if( *in < 32 )
+ {
+ in++;
+ continue;
+ }
+
+ *out++ = tolower( *in++ );
+ }
+
+ *out = 0;
+}
+
+/*
+==================
+G_ClientNumberFromString
+
+Returns a player number for either a number or name string
+Returns -1 if invalid
+==================
+*/
+int G_ClientNumberFromString( gentity_t *to, char *s )
+{
+ gclient_t *cl;
+ int idnum;
+ char s2[ MAX_STRING_CHARS ];
+ char n2[ MAX_STRING_CHARS ];
+
+ // numeric values are just slot numbers
+ if( s[ 0 ] >= '0' && s[ 0 ] <= '9' )
+ {
+ idnum = atoi( s );
+
+ if( idnum < 0 || idnum >= level.maxclients )
+ {
+ G_SendCommandFromServer( to - g_entities, va( "print \"Bad client slot: %i\n\"", idnum ) );
+ return -1;
+ }
+
+ cl = &level.clients[ idnum ];
+
+ if( cl->pers.connected != CON_CONNECTED )
+ {
+ G_SendCommandFromServer( to - g_entities, va( "print \"Client %i is not active\n\"", idnum ) );
+ return -1;
+ }
+
+ return idnum;
+ }
+
+ // check for a name match
+ G_SanitiseName( s, s2 );
+
+ for( idnum = 0, cl = level.clients; idnum < level.maxclients; idnum++, cl++ )
+ {
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ G_SanitiseName( cl->pers.netname, n2 );
+
+ if( !strcmp( n2, s2 ) )
+ return idnum;
+ }
+
+ G_SendCommandFromServer( to - g_entities, va( "print \"User %s is not on the server\n\"", s ) );
+ return -1;
+}
+
+/*
+==================
+ScoreboardMessage
+
+==================
+*/
+void ScoreboardMessage( gentity_t *ent )
+{
+ char entry[ 1024 ];
+ char string[ 1400 ];
+ int stringlength;
+ int i, j;
+ gclient_t *cl;
+ int numSorted;
+ weapon_t weapon = WP_NONE;
+ upgrade_t upgrade = UP_NONE;
+
+ // send the latest information on all clients
+ string[ 0 ] = 0;
+ stringlength = 0;
+
+ numSorted = level.numConnectedClients;
+
+ for( i = 0; i < numSorted; i++ )
+ {
+ int ping;
+
+ cl = &level.clients[ level.sortedClients[ i ] ];
+
+ if( cl->pers.connected == CON_CONNECTING )
+ ping = -1;
+ else
+ ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
+
+ if( cl->ps.stats[ STAT_HEALTH ] > 0 )
+ {
+ weapon = cl->ps.weapon;
+
+ if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) )
+ upgrade = UP_BATTLESUIT;
+ else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) )
+ upgrade = UP_JETPACK;
+ else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) )
+ upgrade = UP_BATTPACK;
+ else if( BG_InventoryContainsUpgrade( UP_HELMET, cl->ps.stats ) )
+ upgrade = UP_HELMET;
+ else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) )
+ upgrade = UP_LIGHTARMOUR;
+ else
+ upgrade = UP_NONE;
+ }
+ else
+ {
+ weapon = WP_NONE;
+ upgrade = UP_NONE;
+ }
+
+ Com_sprintf( entry, sizeof( entry ),
+ " %d %d %d %d %d %d", level.sortedClients[ i ], cl->ps.persistant[ PERS_SCORE ],
+ ping, ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade );
+
+ j = strlen( entry );
+
+ if( stringlength + j > 1024 )
+ break;
+
+ strcpy( string + stringlength, entry );
+ stringlength += j;
+ }
+
+ G_SendCommandFromServer( ent-g_entities, va( "scores %i %i %i%s", i,
+ level.alienKills, level.humanKills, string ) );
+}
+
+
+/*
+==================
+Cmd_Score_f
+
+Request current scoreboard information
+==================
+*/
+void Cmd_Score_f( gentity_t *ent )
+{
+ ScoreboardMessage( ent );
+}
+
+
+
+/*
+==================
+CheatsOk
+==================
+*/
+qboolean CheatsOk( gentity_t *ent )
+{
+ if( !g_cheats.integer )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Cheats are not enabled on this server\n\"" ) );
+ return qfalse;
+ }
+
+ if( ent->health <= 0 )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You must be alive to use this command\n\"" ) );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+/*
+==================
+ConcatArgs
+==================
+*/
+char *ConcatArgs( int start )
+{
+ int i, c, tlen;
+ static char line[ MAX_STRING_CHARS ];
+ int len;
+ char arg[ MAX_STRING_CHARS ];
+
+ len = 0;
+ c = trap_Argc( );
+
+ for( i = start; i < c; i++ )
+ {
+ trap_Argv( i, arg, sizeof( arg ) );
+ tlen = strlen( arg );
+
+ if( len + tlen >= MAX_STRING_CHARS - 1 )
+ break;
+
+ memcpy( line + len, arg, tlen );
+ len += tlen;
+
+ if( i != c - 1 )
+ {
+ line[ len ] = ' ';
+ len++;
+ }
+ }
+
+ line[ len ] = 0;
+
+ return line;
+}
+
+
+/*
+==================
+Cmd_Give_f
+
+Give items to a client
+==================
+*/
+void Cmd_Give_f( gentity_t *ent )
+{
+ char *name;
+ qboolean give_all;
+
+ if( !CheatsOk( ent ) )
+ return;
+
+ name = ConcatArgs( 1 );
+
+ if( Q_stricmp( name, "all" ) == 0 )
+ give_all = qtrue;
+ else
+ give_all = qfalse;
+
+ if( give_all || Q_stricmp( name, "health" ) == 0 )
+ {
+ ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ];
+ if( !give_all )
+ return;
+ }
+
+ if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 )
+ {
+ int credits = atoi( name + 6 );
+
+ if( !credits )
+ G_AddCreditToClient( ent->client, 1, qtrue );
+ else
+ G_AddCreditToClient( ent->client, credits, qtrue );
+
+ if( !give_all )
+ return;
+ }
+}
+
+
+/*
+==================
+Cmd_God_f
+
+Sets client to godmode
+
+argv(0) god
+==================
+*/
+void Cmd_God_f( gentity_t *ent )
+{
+ char *msg;
+
+ if( !CheatsOk( ent ) )
+ return;
+
+ ent->flags ^= FL_GODMODE;
+
+ if( !( ent->flags & FL_GODMODE ) )
+ msg = "godmode OFF\n";
+ else
+ msg = "godmode ON\n";
+
+ G_SendCommandFromServer( ent - g_entities, va( "print \"%s\"", msg ) );
+}
+
+
+/*
+==================
+Cmd_Notarget_f
+
+Sets client to notarget
+
+argv(0) notarget
+==================
+*/
+void Cmd_Notarget_f( gentity_t *ent )
+{
+ char *msg;
+
+ if( !CheatsOk( ent ) )
+ return;
+
+ ent->flags ^= FL_NOTARGET;
+
+ if( !( ent->flags & FL_NOTARGET ) )
+ msg = "notarget OFF\n";
+ else
+ msg = "notarget ON\n";
+
+ G_SendCommandFromServer( ent - g_entities, va( "print \"%s\"", msg ) );
+}
+
+
+/*
+==================
+Cmd_Noclip_f
+
+argv(0) noclip
+==================
+*/
+void Cmd_Noclip_f( gentity_t *ent )
+{
+ char *msg;
+
+ if( !CheatsOk( ent ) )
+ return;
+
+ if( ent->client->noclip )
+ msg = "noclip OFF\n";
+ else
+ msg = "noclip ON\n";
+
+ ent->client->noclip = !ent->client->noclip;
+
+ G_SendCommandFromServer( ent - g_entities, va( "print \"%s\"", msg ) );
+}
+
+
+/*
+==================
+Cmd_LevelShot_f
+
+This is just to help generate the level pictures
+for the menus. It goes to the intermission immediately
+and sends over a command to the client to resize the view,
+hide the scoreboard, and take a special screenshot
+==================
+*/
+void Cmd_LevelShot_f( gentity_t *ent )
+{
+ if( !CheatsOk( ent ) )
+ return;
+
+ BeginIntermission( );
+ G_SendCommandFromServer( ent - g_entities, "clientLevelShot" );
+}
+
+/*
+=================
+Cmd_Kill_f
+=================
+*/
+void Cmd_Kill_f( gentity_t *ent )
+{
+ if( ent->client->sess.sessionTeam == TEAM_SPECTATOR )
+ return;
+
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_NONE )
+ return;
+
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING )
+ return;
+
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Leave the hovel first (use your destroy key)\n\"" );
+ return;
+ }
+
+ if( ent->health <= 0 )
+ return;
+
+ if( g_cheats.integer )
+ {
+ ent->flags &= ~FL_GODMODE;
+ ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0;
+ player_die( ent, ent, ent, 100000, MOD_SUICIDE );
+ }
+ else
+ {
+ if( ent->suicideTime == 0 )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"You will suicide in 20 seconds\n\"" );
+ ent->suicideTime = level.time + 20000;
+ }
+ else if( ent->suicideTime > level.time )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Suicide cancelled\n\"" );
+ ent->suicideTime = 0;
+ }
+ }
+}
+
+/*
+=================
+G_ChangeTeam
+=================
+*/
+void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam )
+{
+ pTeam_t oldTeam = ent->client->pers.teamSelection;
+
+ ent->client->pers.teamSelection = newTeam;
+
+ if( oldTeam != newTeam )
+ {
+ //if the client is in a queue make sure they are removed from it before changing
+ if( oldTeam == PTE_ALIENS )
+ G_RemoveFromSpawnQueue( &level.alienSpawnQueue, ent->client->ps.clientNum );
+ else if( oldTeam == PTE_HUMANS )
+ G_RemoveFromSpawnQueue( &level.humanSpawnQueue, ent->client->ps.clientNum );
+
+ level.bankCredits[ ent->client->ps.clientNum ] = 0;
+ ent->client->ps.persistant[ PERS_CREDIT ] = 0;
+ ent->client->ps.persistant[ PERS_SCORE ] = 0;
+ ent->client->pers.classSelection = PCL_NONE;
+ ClientSpawn( ent, NULL, NULL, NULL );
+ }
+
+ ent->client->pers.joinedATeam = qtrue;
+
+ //update ClientInfo
+ ClientUserinfoChanged( ent->client->ps.clientNum );
+}
+
+/*
+=================
+Cmd_Team_f
+=================
+*/
+void Cmd_Team_f( gentity_t *ent )
+{
+ pTeam_t team;
+ char s[ MAX_TOKEN_CHARS ];
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ if( !strlen( s ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va("print \"team: %i\n\"", ent->client->pers.teamSelection ) );
+ return;
+ }
+
+ if( !Q_stricmp( s, "spectate" ) )
+ team = PTE_NONE;
+ else if( !Q_stricmp( s, "aliens" ) )
+ {
+ if( g_teamForceBalance.integer && level.numAlienClients > level.numHumanClients )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_TEAMFULL );
+ return;
+ }
+
+ team = PTE_ALIENS;
+ }
+ else if( !Q_stricmp( s, "humans" ) )
+ {
+ if( g_teamForceBalance.integer && level.numHumanClients > level.numAlienClients )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_TEAMFULL );
+ return;
+ }
+
+ team = PTE_HUMANS;
+ }
+ else if( !Q_stricmp( s, "auto" ) )
+ {
+ if( level.numHumanClients > level.numAlienClients )
+ team = PTE_ALIENS;
+ else if( level.numHumanClients < level.numAlienClients )
+ team = PTE_HUMANS;
+ else
+ team = PTE_ALIENS + ( rand( ) % 2 );
+ }
+ else
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Unknown team: %s\n\"", s ) );
+ return;
+ }
+
+ G_ChangeTeam( ent, team );
+
+ if( team == PTE_ALIENS )
+ G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " joined the aliens\n\"", ent->client->pers.netname ) );
+ else if( team == PTE_HUMANS )
+ G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " joined the humans\n\"", ent->client->pers.netname ) );
+}
+
+
+/*
+==================
+G_Say
+==================
+*/
+static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message )
+{
+ if( !other )
+ return;
+
+ if( !other->inuse )
+ return;
+
+ if( !other->client )
+ return;
+
+ if( other->client->pers.connected != CON_CONNECTED )
+ return;
+
+ if( mode == SAY_TEAM && !OnSameTeam( ent, other ) )
+ return;
+
+ G_SendCommandFromServer( other-g_entities, va( "%s \"%s%c%c%s\"",
+ mode == SAY_TEAM ? "tchat" : "chat",
+ name, Q_COLOR_ESCAPE, color, message ) );
+}
+
+#define EC "\x19"
+
+void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText )
+{
+ int j;
+ gentity_t *other;
+ int color;
+ char name[ 64 ];
+ // don't let text be too long for malicious reasons
+ char text[ MAX_SAY_TEXT ];
+ char location[ 64 ];
+
+ switch( mode )
+ {
+ default:
+ case SAY_ALL:
+ G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText );
+ Com_sprintf( name, sizeof( name ), "%s%c%c"EC": ", ent->client->pers.netname,
+ Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_GREEN;
+ break;
+
+ case SAY_TEAM:
+ G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText );
+ if( Team_GetLocationMsg( ent, location, sizeof( location ) ) )
+ Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC") (%s)"EC": ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location );
+ else
+ Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC")"EC": ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_CYAN;
+ break;
+
+ case SAY_TELL:
+ if( target &&
+ target->client->ps.stats[ STAT_PTEAM ] == ent->client->ps.stats[ STAT_PTEAM ] &&
+ Team_GetLocationMsg( ent, location, sizeof( location ) ) )
+ Com_sprintf( name, sizeof( name ), EC"[%s%c%c"EC"] (%s)"EC": ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location );
+ else
+ Com_sprintf( name, sizeof( name ), EC"[%s%c%c"EC"]"EC": ",
+ ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE );
+ color = COLOR_MAGENTA;
+ break;
+ }
+
+ Q_strncpyz( text, chatText, sizeof( text ) );
+
+ if( target )
+ {
+ G_SayTo( ent, target, mode, color, name, text );
+ return;
+ }
+
+ // echo the text to the console
+ if( g_dedicated.integer )
+ G_Printf( "%s%s\n", name, text);
+
+ // send it to all the apropriate clients
+ for( j = 0; j < level.maxclients; j++ )
+ {
+ other = &g_entities[ j ];
+ G_SayTo( ent, other, mode, color, name, text );
+ }
+}
+
+
+/*
+==================
+Cmd_Say_f
+==================
+*/
+static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 )
+{
+ char *p;
+
+ if( trap_Argc( ) < 2 && !arg0 )
+ return;
+
+ if( arg0 )
+ p = ConcatArgs( 0 );
+ else
+ p = ConcatArgs( 1 );
+
+ G_Say( ent, NULL, mode, p );
+}
+
+/*
+==================
+Cmd_Tell_f
+==================
+*/
+static void Cmd_Tell_f( gentity_t *ent )
+{
+ int targetNum;
+ gentity_t *target;
+ char *p;
+ char arg[MAX_TOKEN_CHARS];
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ trap_Argv( 1, arg, sizeof( arg ) );
+ targetNum = atoi( arg );
+
+ if( targetNum < 0 || targetNum >= level.maxclients )
+ return;
+
+ target = &g_entities[ targetNum ];
+ if( !target || !target->inuse || !target->client )
+ return;
+
+ p = ConcatArgs( 2 );
+
+ G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p );
+ G_Say( ent, target, SAY_TELL, p );
+ // don't tell to the player self if it was already directed to this player
+ // also don't send the chat back to a bot
+ if( ent != target && !( ent->r.svFlags & SVF_BOT ) )
+ G_Say( ent, ent, SAY_TELL, p );
+}
+
+/*
+==================
+Cmd_Where_f
+==================
+*/
+void Cmd_Where_f( gentity_t *ent )
+{
+ G_SendCommandFromServer( ent-g_entities, va( "print \"%s\n\"", vtos( ent->s.origin ) ) );
+}
+
+/*
+==================
+Cmd_CallVote_f
+==================
+*/
+void Cmd_CallVote_f( gentity_t *ent )
+{
+ int i;
+ char arg1[ MAX_STRING_TOKENS ];
+ char arg2[ MAX_STRING_TOKENS ];
+
+ if( !g_allowVote.integer )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Voting not allowed here\n\"" );
+ return;
+ }
+
+ if( level.voteTime )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"A vote is already in progress\n\"" );
+ return;
+ }
+
+ if( ent->client->pers.voteCount >= MAX_VOTE_COUNT )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"You have called the maximum number of votes\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_NONE )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Not allowed to call a vote as spectator\n\"" );
+ return;
+ }
+
+ // make sure it is a valid command to vote on
+ trap_Argv( 1, arg1, sizeof( arg1 ) );
+ trap_Argv( 2, arg2, sizeof( arg2 ) );
+
+ if( strchr( arg1, ';' ) || strchr( arg2, ';' ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Invalid vote string\n\"" );
+ return;
+ }
+
+ if( !Q_stricmp( arg1, "map_restart" ) ) { }
+ else if( !Q_stricmp( arg1, "nextmap" ) ) { }
+ else if( !Q_stricmp( arg1, "map" ) ) { }
+ else if( !Q_stricmp( arg1, "kick" ) ) { }
+ else if( !Q_stricmp( arg1, "clientkick" ) ) { }
+ else if( !Q_stricmp( arg1, "timelimit" ) ) { }
+ else
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Invalid vote string\n\"" );
+ G_SendCommandFromServer( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map <mapname>, "
+ "kick <player>, clientkick <clientnum>, "
+ "timelimit <time>\n\"" );
+ return;
+ }
+
+ // if there is still a vote to be executed
+ if( level.voteExecuteTime )
+ {
+ level.voteExecuteTime = 0;
+ trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) );
+ }
+
+ if( !Q_stricmp( arg1, "map" ) )
+ {
+ // special case for map changes, we want to reset the nextmap setting
+ // this allows a player to change maps, but not upset the map rotation
+ char s[ MAX_STRING_CHARS ];
+
+ trap_Cvar_VariableStringBuffer( "nextmap", s, sizeof( s ) );
+
+ if( *s )
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s; set nextmap \"%s\"", arg1, arg2, s );
+ else
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 );
+
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
+ }
+ else if( !Q_stricmp( arg1, "nextmap" ) )
+ {
+ char s[ MAX_STRING_CHARS ];
+
+ trap_Cvar_VariableStringBuffer( "nextmap", s, sizeof( s ) );
+
+ if( !*s )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"nextmap not set\n\"" );
+ return;
+ }
+
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "vstr nextmap" );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
+ }
+ else
+ {
+ Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 );
+ Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString );
+ }
+
+ G_SendCommandFromServer( -1, va( "print \"%s called a vote\n\"", ent->client->pers.netname ) );
+
+ // start the voting, the caller autoamtically votes yes
+ level.voteTime = level.time;
+ level.voteYes = 1;
+ level.voteNo = 0;
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ level.clients[i].ps.eFlags &= ~EF_VOTED;
+
+ ent->client->ps.eFlags |= EF_VOTED;
+
+ trap_SetConfigstring( CS_VOTE_TIME, va( "%i", level.voteTime ) );
+ trap_SetConfigstring( CS_VOTE_STRING, level.voteDisplayString );
+ trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) );
+ trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) );
+}
+
+/*
+==================
+Cmd_Vote_f
+==================
+*/
+void Cmd_Vote_f( gentity_t *ent )
+{
+ char msg[ 64 ];
+
+ if( !level.voteTime )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"No vote in progress\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.eFlags & EF_VOTED )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Vote already cast\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_NONE )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Not allowed to vote as spectator\n\"" );
+ return;
+ }
+
+ G_SendCommandFromServer( ent-g_entities, "print \"Vote cast\n\"" );
+
+ ent->client->ps.eFlags |= EF_VOTED;
+
+ trap_Argv( 1, msg, sizeof( msg ) );
+
+ if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' )
+ {
+ level.voteYes++;
+ trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) );
+ }
+ else
+ {
+ level.voteNo++;
+ trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) );
+ }
+
+ // a majority will be determined in G_CheckVote, which will also account
+ // for players entering or leaving
+}
+
+/*
+==================
+Cmd_CallTeamVote_f
+==================
+*/
+void Cmd_CallTeamVote_f( gentity_t *ent )
+{
+ int i, team, cs_offset;
+ char arg1[ MAX_STRING_TOKENS ];
+ char arg2[ MAX_STRING_TOKENS ];
+
+ team = ent->client->ps.stats[ STAT_PTEAM ];
+
+ if( team == PTE_HUMANS )
+ cs_offset = 0;
+ else if( team == PTE_ALIENS )
+ cs_offset = 1;
+ else
+ return;
+
+ if( !g_allowVote.integer )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Voting not allowed here\n\"" );
+ return;
+ }
+
+ if( level.teamVoteTime[ cs_offset ] )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"A team vote is already in progress\n\"" );
+ return;
+ }
+
+ if( ent->client->pers.teamVoteCount >= MAX_VOTE_COUNT )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"You have called the maximum number of team votes\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_NONE )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Not allowed to call a vote as spectator\n\"" );
+ return;
+ }
+
+ // make sure it is a valid command to vote on
+ trap_Argv( 1, arg1, sizeof( arg1 ) );
+ trap_Argv( 2, arg2, sizeof( arg2 ) );
+
+ if( strchr( arg1, ';' ) || strchr( arg2, ';' ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Invalid team vote string\n\"" );
+ return;
+ }
+
+ if( !Q_stricmp( arg1, "teamkick" ) )
+ {
+ char netname[ MAX_NETNAME ], kickee[ MAX_NETNAME ];
+
+ Q_strncpyz( kickee, arg2, sizeof( kickee ) );
+ Q_CleanStr( kickee );
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+
+ if( level.clients[ i ].ps.stats[ STAT_PTEAM ] != team )
+ continue;
+
+ Q_strncpyz( netname, level.clients[ i ].pers.netname, sizeof( netname ) );
+ Q_CleanStr( netname );
+
+ if( !Q_stricmp( netname, kickee ) )
+ break;
+ }
+
+ if( i >= level.maxclients )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"%s is not a valid player on your team\n\"", arg2 ) );
+ return;
+ }
+ }
+ else
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Invalid vote string\n\"" );
+ G_SendCommandFromServer( ent-g_entities, "print \"Team vote commands are: teamkick <player>\n\"" );
+ return;
+ }
+
+ Com_sprintf( level.teamVoteString[ cs_offset ],
+ sizeof( level.teamVoteString[ cs_offset ] ), "kick \"%s\"", arg2 );
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+
+ if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == team )
+ G_SendCommandFromServer( i, va("print \"%s called a team vote\n\"", ent->client->pers.netname ) );
+ }
+
+ // start the voting, the caller autoamtically votes yes
+ level.teamVoteTime[ cs_offset ] = level.time;
+ level.teamVoteYes[ cs_offset ] = 1;
+ level.teamVoteNo[ cs_offset ] = 0;
+
+ for( i = 0 ; i < level.maxclients ; i++ )
+ {
+ if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == team )
+ level.clients[ i ].ps.eFlags &= ~EF_TEAMVOTED;
+ }
+
+ ent->client->ps.eFlags |= EF_TEAMVOTED;
+
+ trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, va( "%i", level.teamVoteTime[ cs_offset ] ) );
+ trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, level.teamVoteString[ cs_offset ] );
+ trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va( "%i", level.teamVoteYes[ cs_offset ] ) );
+ trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va( "%i", level.teamVoteNo[ cs_offset ] ) );
+}
+
+
+/*
+==================
+Cmd_TeamVote_f
+==================
+*/
+void Cmd_TeamVote_f( gentity_t *ent )
+{
+ int team, cs_offset;
+ char msg[ 64 ];
+
+ team = ent->client->ps.stats[ STAT_PTEAM ];
+ if( team == PTE_HUMANS )
+ cs_offset = 0;
+ else if( team == PTE_ALIENS )
+ cs_offset = 1;
+ else
+ return;
+
+ if( !level.teamVoteTime[ cs_offset ] )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"No team vote in progress\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.eFlags & EF_TEAMVOTED )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Team vote already cast\n\"" );
+ return;
+ }
+
+ if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_NONE )
+ {
+ G_SendCommandFromServer( ent-g_entities, "print \"Not allowed to vote as spectator\n\"" );
+ return;
+ }
+
+ G_SendCommandFromServer( ent-g_entities, "print \"Team vote cast\n\"" );
+
+ ent->client->ps.eFlags |= EF_TEAMVOTED;
+
+ trap_Argv( 1, msg, sizeof( msg ) );
+
+ if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' )
+ {
+ level.teamVoteYes[ cs_offset ]++;
+ trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va( "%i", level.teamVoteYes[ cs_offset ] ) );
+ }
+ else
+ {
+ level.teamVoteNo[ cs_offset ]++;
+ trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va( "%i", level.teamVoteNo[ cs_offset ] ) );
+ }
+
+ // a majority will be determined in TeamCheckVote, which will also account
+ // for players entering or leaving
+}
+
+
+/*
+=================
+Cmd_SetViewpos_f
+=================
+*/
+void Cmd_SetViewpos_f( gentity_t *ent )
+{
+ vec3_t origin, angles;
+ char buffer[ MAX_TOKEN_CHARS ];
+ int i;
+
+ if( !g_cheats.integer )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Cheats are not enabled on this server\n\"" ) );
+ return;
+ }
+
+ if( trap_Argc( ) != 5 )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"usage: setviewpos x y z yaw\n\"" ) );
+ return;
+ }
+
+ VectorClear( angles );
+
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ trap_Argv( i + 1, buffer, sizeof( buffer ) );
+ origin[ i ] = atof( buffer );
+ }
+
+ trap_Argv( 4, buffer, sizeof( buffer ) );
+ angles[ YAW ] = atof( buffer );
+
+ TeleportPlayer( ent, origin, angles );
+}
+
+#define EVOLVE_TRACE_HEIGHT 128.0f
+#define AS_OVER_RT3 ((ALIENSENSE_RANGE*0.5f)/M_ROOT3)
+
+/*
+=================
+Cmd_Class_f
+=================
+*/
+void Cmd_Class_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int clientNum;
+ int i;
+ trace_t tr, tr2;
+ vec3_t infestOrigin;
+ int allowedClasses[ PCL_NUM_CLASSES ];
+ int numClasses = 0;
+ pClass_t currentClass = ent->client->ps.stats[ STAT_PCLASS ];
+
+ int numLevels;
+ vec3_t fromMins, fromMaxs, toMins, toMaxs;
+ vec3_t temp;
+
+ int entityList[ MAX_GENTITIES ];
+ vec3_t range = { AS_OVER_RT3, AS_OVER_RT3, AS_OVER_RT3 };
+ vec3_t mins, maxs;
+ int num;
+ gentity_t *other;
+
+ if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ clientNum = ent->client - level.clients;
+ trap_Argv( 1, s, sizeof( s ) );
+
+ if( G_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) )
+ allowedClasses[ numClasses++ ] = PCL_ALIEN_BUILDER0;
+
+ if( G_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) &&
+ BG_FindStagesForClass( PCL_ALIEN_BUILDER0_UPG, g_alienStage.integer ) )
+ allowedClasses[ numClasses++ ] = PCL_ALIEN_BUILDER0_UPG;
+
+ if( G_ClassIsAllowed( PCL_ALIEN_LEVEL0 ) )
+ allowedClasses[ numClasses++ ] = PCL_ALIEN_LEVEL0;
+
+ if( ent->client->pers.teamSelection == PTE_ALIENS &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) )
+ {
+ //if we are not currently spectating, we are attempting evolution
+ if( currentClass != PCL_NONE )
+ {
+ //check there are no humans nearby
+ VectorAdd( ent->client->ps.origin, range, maxs );
+ VectorSubtract( ent->client->ps.origin, range, mins );
+
+ num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+ for( i = 0; i < num; i++ )
+ {
+ other = &g_entities[ entityList[ i ] ];
+
+ if( ( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ||
+ ( other->s.eType == ET_BUILDABLE && other->biteam == BIT_HUMANS ) )
+ {
+ ent->client->pers.classSelection = PCL_NONE;
+ G_TriggerMenu( clientNum, MN_A_TOOCLOSE );
+ return;
+ }
+ }
+
+ if( !level.overmindPresent )
+ {
+ ent->client->pers.classSelection = PCL_NONE;
+ G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE );
+ return;
+ }
+
+ //guard against selling the HBUILD weapons exploit
+ if( ( currentClass == PCL_ALIEN_BUILDER0 ||
+ currentClass == PCL_ALIEN_BUILDER0_UPG ) &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Cannot evolve until build timer expires\n\"" ) );
+ return;
+ }
+
+ //evolve now
+ ent->client->pers.classSelection = BG_FindClassNumForName( s );
+
+ if( ent->client->pers.classSelection == PCL_NONE )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Unknown class\n\"" ) );
+ return;
+ }
+
+ numLevels = BG_ClassCanEvolveFromTo( currentClass,
+ ent->client->pers.classSelection,
+ (short)ent->client->ps.persistant[ PERS_CREDIT ], 0 );
+
+ BG_FindBBoxForClass( currentClass,
+ fromMins, fromMaxs, NULL, NULL, NULL );
+ BG_FindBBoxForClass( ent->client->pers.classSelection,
+ toMins, toMaxs, NULL, NULL, NULL );
+
+ VectorCopy( ent->s.pos.trBase, infestOrigin );
+
+ infestOrigin[ 2 ] += ( fabs( toMins[ 2 ] ) - fabs( fromMins[ 2 ] ) ) + 1.0f;
+ VectorCopy( infestOrigin, temp );
+ temp[ 2 ] += EVOLVE_TRACE_HEIGHT;
+
+ //compute a place up in the air to start the real trace
+ trap_Trace( &tr, infestOrigin, toMins, toMaxs, temp, ent->s.number, MASK_SHOT );
+ VectorCopy( infestOrigin, temp );
+ temp[ 2 ] += ( EVOLVE_TRACE_HEIGHT * tr.fraction ) - 1.0f;
+
+ //trace down to the ground so that we can evolve on slopes
+ trap_Trace( &tr, temp, toMins, toMaxs, infestOrigin, ent->s.number, MASK_SHOT );
+ VectorCopy( tr.endpos, infestOrigin );
+
+ //make REALLY sure
+ trap_Trace( &tr2, ent->s.pos.trBase, NULL, NULL, infestOrigin, ent->s.number, MASK_SHOT );
+
+ //check there is room to evolve
+ if( !tr.startsolid && tr2.fraction == 1.0f )
+ {
+ //...check we can evolve to that class
+ if( numLevels >= 0 &&
+ BG_FindStagesForClass( ent->client->pers.classSelection, g_alienStage.integer ) &&
+ G_ClassIsAllowed( ent->client->pers.classSelection ) )
+ {
+ ent->client->pers.evolveHealthFraction = (float)ent->client->ps.stats[ STAT_HEALTH ] /
+ (float)BG_FindHealthForClass( currentClass );
+
+ if( ent->client->pers.evolveHealthFraction < 0.0f )
+ ent->client->pers.evolveHealthFraction = 0.0f;
+ else if( ent->client->pers.evolveHealthFraction > 1.0f )
+ ent->client->pers.evolveHealthFraction = 1.0f;
+
+ //remove credit
+ G_AddCreditToClient( ent->client, -(short)numLevels, qtrue );
+
+ ClientUserinfoChanged( clientNum );
+ VectorCopy( infestOrigin, ent->s.pos.trBase );
+ ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase );
+ return;
+ }
+ else
+ {
+ ent->client->pers.classSelection = PCL_NONE;
+ G_SendCommandFromServer( ent-g_entities,
+ va( "print \"You cannot evolve from your current class\n\"" ) );
+ return;
+ }
+ }
+ else
+ {
+ ent->client->pers.classSelection = PCL_NONE;
+ G_TriggerMenu( clientNum, MN_A_NOEROOM );
+ return;
+ }
+ }
+ else
+ {
+ //spawning from an egg
+ ent->client->pers.classSelection =
+ ent->client->ps.stats[ STAT_PCLASS ] = BG_FindClassNumForName( s );
+
+ if( ent->client->pers.classSelection != PCL_NONE )
+ {
+ for( i = 0; i < numClasses; i++ )
+ {
+ if( allowedClasses[ i ] == ent->client->pers.classSelection &&
+ BG_FindStagesForClass( ent->client->pers.classSelection, g_alienStage.integer ) &&
+ G_ClassIsAllowed( ent->client->pers.classSelection ) )
+ {
+ G_PushSpawnQueue( &level.alienSpawnQueue, clientNum );
+ return;
+ }
+ }
+
+ ent->client->pers.classSelection = PCL_NONE;
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You cannot spawn as this class\n\"" ) );
+ }
+ else
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Unknown class\n\"" ) );
+ return;
+ }
+ }
+ }
+ else if( ent->client->pers.teamSelection == PTE_HUMANS )
+ {
+ //humans cannot use this command whilst alive
+ if( ent->client->pers.classSelection != PCL_NONE )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You must be dead to use the class command\n\"" ) );
+ return;
+ }
+
+ ent->client->pers.classSelection =
+ ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN;
+
+ //set the item to spawn with
+ if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) && G_WeaponIsAllowed( WP_MACHINEGUN ) )
+ ent->client->pers.humanItemSelection = WP_MACHINEGUN;
+ else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) && G_WeaponIsAllowed( WP_HBUILD ) )
+ ent->client->pers.humanItemSelection = WP_HBUILD;
+ else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) && G_WeaponIsAllowed( WP_HBUILD2 ) &&
+ BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) )
+ ent->client->pers.humanItemSelection = WP_HBUILD2;
+ else
+ {
+ ent->client->pers.classSelection = PCL_NONE;
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Unknown starting item\n\"" ) );
+ return;
+ }
+
+ G_PushSpawnQueue( &level.humanSpawnQueue, clientNum );
+ }
+ else if( ent->client->pers.teamSelection == PTE_NONE )
+ {
+ //can't use this command unless on a team
+ ent->client->pers.classSelection = PCL_NONE;
+ ent->client->sess.sessionTeam = TEAM_FREE;
+ ClientSpawn( ent, NULL, NULL, NULL );
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Join a team first\n\"" ) );
+ }
+}
+
+
+/*
+=================
+Cmd_Destroy_f
+=================
+*/
+void Cmd_Destroy_f( gentity_t *ent, qboolean deconstruct )
+{
+ vec3_t forward, end;
+ trace_t tr;
+ gentity_t *traceEnt;
+
+ if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING )
+ G_Damage( ent->client->hovel, ent, ent, forward, ent->s.origin, 10000, 0, MOD_SUICIDE );
+
+ if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) )
+ {
+ AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL );
+ VectorMA( ent->client->ps.origin, 100, forward, end );
+
+ trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
+ traceEnt = &g_entities[ tr.entityNum ];
+
+ if( tr.fraction < 1.0f &&
+ ( traceEnt->s.eType == ET_BUILDABLE ) &&
+ ( traceEnt->biteam == ent->client->pers.teamSelection ) &&
+ ( ( ent->client->ps.weapon >= WP_ABUILD ) &&
+ ( ent->client->ps.weapon <= WP_HBUILD ) ) )
+ {
+ if( ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );
+ return;
+ }
+
+ if( !deconstruct )
+ G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE );
+ else
+ G_FreeEntity( traceEnt );
+
+ ent->client->ps.stats[ STAT_MISC ] +=
+ BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2;
+ }
+ }
+}
+
+
+/*
+=================
+Cmd_ActivateItem_f
+
+Activate an item
+=================
+*/
+void Cmd_ActivateItem_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int upgrade, weapon;
+
+ trap_Argv( 1, s, sizeof( s ) );
+ upgrade = BG_FindUpgradeNumForName( s );
+ weapon = BG_FindWeaponNumForName( s );
+
+ if( ent->client->pers.teamSelection != PTE_HUMANS )
+ return;
+
+ if( upgrade != UP_NONE && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ BG_ActivateUpgrade( upgrade, ent->client->ps.stats );
+ else if( weapon != WP_NONE && BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) )
+ G_ForceWeaponChange( ent, weapon );
+ else
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) );
+}
+
+
+/*
+=================
+Cmd_DeActivateItem_f
+
+Deactivate an item
+=================
+*/
+void Cmd_DeActivateItem_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int upgrade;
+
+ trap_Argv( 1, s, sizeof( s ) );
+ upgrade = BG_FindUpgradeNumForName( s );
+
+ if( ent->client->pers.teamSelection != PTE_HUMANS )
+ return;
+
+ if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ BG_DeactivateUpgrade( upgrade, ent->client->ps.stats );
+ else
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) );
+}
+
+
+/*
+=================
+Cmd_ToggleItem_f
+=================
+*/
+void Cmd_ToggleItem_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int upgrade, weapon, i;
+
+ trap_Argv( 1, s, sizeof( s ) );
+ upgrade = BG_FindUpgradeNumForName( s );
+ weapon = BG_FindWeaponNumForName( s );
+
+ if( ent->client->pers.teamSelection != PTE_HUMANS )
+ return;
+
+ if( weapon != WP_NONE )
+ {
+ //special case to allow switching between
+ //the blaster and the primary weapon
+
+ if( ent->client->ps.weapon != WP_BLASTER )
+ weapon = WP_BLASTER;
+ else
+ {
+ //find a held weapon which isn't the blaster
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( i == WP_BLASTER )
+ continue;
+
+ if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) )
+ {
+ weapon = i;
+ break;
+ }
+ }
+
+ if( i == WP_NUM_WEAPONS )
+ weapon = WP_BLASTER;
+ }
+
+ G_ForceWeaponChange( ent, weapon );
+ }
+ else if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ {
+ if( BG_UpgradeIsActive( upgrade, ent->client->ps.stats ) )
+ BG_DeactivateUpgrade( upgrade, ent->client->ps.stats );
+ else
+ BG_ActivateUpgrade( upgrade, ent->client->ps.stats );
+ }
+ else
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) );
+}
+
+/*
+=================
+Cmd_Buy_f
+=================
+*/
+void Cmd_Buy_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int i;
+ int weapon, upgrade, numItems = 0;
+ int maxAmmo, maxClips;
+ qboolean buyingEnergyAmmo = qfalse;
+
+ for( i = UP_NONE; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) )
+ numItems++;
+ }
+
+ for( i = WP_NONE; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) )
+ numItems++;
+ }
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ //aliens don't buy stuff
+ if( ent->client->pers.teamSelection != PTE_HUMANS )
+ return;
+
+ weapon = BG_FindWeaponNumForName( s );
+ upgrade = BG_FindUpgradeNumForName( s );
+
+ //special case to keep norf happy
+ if( weapon == WP_NONE && upgrade == UP_AMMO )
+ buyingEnergyAmmo = BG_FindUsesEnergyForWeapon( ent->client->ps.weapon );
+
+ if( buyingEnergyAmmo )
+ {
+ //no armoury nearby
+ if( ( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) &&
+ !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You must be near a reactor or repeater\n\"" ) );
+ return;
+ }
+ }
+ else
+ {
+ //no armoury nearby
+ if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) );
+ return;
+ }
+ }
+
+ if( weapon != WP_NONE )
+ {
+ //already got this?
+ if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD );
+ return;
+ }
+
+ //can afford this?
+ if( BG_FindPriceForWeapon( weapon ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS );
+ return;
+ }
+
+ //have space to carry this?
+ if( BG_FindSlotsForWeapon( weapon ) & ent->client->ps.stats[ STAT_SLOTS ] )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS );
+ return;
+ }
+
+ if( BG_FindTeamForWeapon( weapon ) != WUT_HUMANS )
+ {
+ //shouldn't need a fancy dialog
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) );
+ return;
+ }
+
+ //are we /allowed/ to buy this?
+ if( !BG_FindPurchasableForWeapon( weapon ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You can't buy this item\n\"" ) );
+ return;
+ }
+
+ //are we /allowed/ to buy this?
+ if( !BG_FindStagesForWeapon( weapon, g_humanStage.integer ) || !G_WeaponIsAllowed( weapon ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You can't buy this item\n\"" ) );
+ return;
+ }
+
+ //add to inventory
+ BG_AddWeaponToInventory( weapon, ent->client->ps.stats );
+ BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips );
+
+ if( BG_FindUsesEnergyForWeapon( weapon ) &&
+ BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) )
+ maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER );
+
+ BG_PackAmmoArray( weapon, ent->client->ps.ammo, ent->client->ps.powerups,
+ maxAmmo, maxClips );
+
+ G_ForceWeaponChange( ent, weapon );
+
+ //set build delay/pounce etc to 0
+ ent->client->ps.stats[ STAT_MISC ] = 0;
+
+ //subtract from funds
+ G_AddCreditToClient( ent->client, -(short)BG_FindPriceForWeapon( weapon ), qfalse );
+ }
+ else if( upgrade != UP_NONE )
+ {
+ //already got this?
+ if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD );
+ return;
+ }
+
+ //can afford this?
+ if( BG_FindPriceForUpgrade( upgrade ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS );
+ return;
+ }
+
+ //have space to carry this?
+ if( BG_FindSlotsForUpgrade( upgrade ) & ent->client->ps.stats[ STAT_SLOTS ] )
+ {
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS );
+ return;
+ }
+
+ if( BG_FindTeamForUpgrade( upgrade ) != WUT_HUMANS )
+ {
+ //shouldn't need a fancy dialog
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) );
+ return;
+ }
+
+ //are we /allowed/ to buy this?
+ if( !BG_FindPurchasableForUpgrade( upgrade ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You can't buy this item\n\"" ) );
+ return;
+ }
+
+ //are we /allowed/ to buy this?
+ if( !BG_FindStagesForUpgrade( upgrade, g_humanStage.integer ) || !G_UpgradeIsAllowed( upgrade ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You can't buy this item\n\"" ) );
+ return;
+ }
+
+ if( upgrade == UP_AMMO )
+ G_GiveClientMaxAmmo( ent, buyingEnergyAmmo );
+ else
+ {
+ //add to inventory
+ BG_AddUpgradeToInventory( upgrade, ent->client->ps.stats );
+ }
+
+ if( upgrade == UP_BATTPACK )
+ G_GiveClientMaxAmmo( ent, qtrue );
+
+ //subtract from funds
+ G_AddCreditToClient( ent->client, -(short)BG_FindPriceForUpgrade( upgrade ), qfalse );
+ }
+ else
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Unknown item\n\"" ) );
+ }
+
+ if( trap_Argc( ) >= 2 )
+ {
+ trap_Argv( 2, s, sizeof( s ) );
+
+ //retrigger the armoury menu
+ if( !Q_stricmp( s, "retrigger" ) )
+ ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES;
+ }
+
+ //update ClientInfo
+ ClientUserinfoChanged( ent->client->ps.clientNum );
+}
+
+
+/*
+=================
+Cmd_Sell_f
+=================
+*/
+void Cmd_Sell_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int i;
+ int weapon, upgrade;
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ //aliens don't sell stuff
+ if( ent->client->pers.teamSelection != PTE_HUMANS )
+ return;
+
+ //no armoury nearby
+ if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) );
+ return;
+ }
+
+ weapon = BG_FindWeaponNumForName( s );
+ upgrade = BG_FindUpgradeNumForName( s );
+
+ if( weapon != WP_NONE )
+ {
+ //are we /allowed/ to sell this?
+ if( !BG_FindPurchasableForWeapon( weapon ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You can't sell this weapon\n\"" ) );
+ return;
+ }
+
+ //remove weapon if carried
+ if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) )
+ {
+ //guard against selling the HBUILD weapons exploit
+ if( ( weapon == WP_HBUILD || weapon == WP_HBUILD2 ) &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Cannot sell until build timer expires\n\"" ) );
+ return;
+ }
+
+ BG_RemoveWeaponFromInventory( weapon, ent->client->ps.stats );
+
+ //add to funds
+ G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( weapon ), qfalse );
+ }
+
+ //if we have this weapon selected, force a new selection
+ if( weapon == ent->client->ps.weapon )
+ G_ForceWeaponChange( ent, WP_NONE );
+ }
+ else if( upgrade != UP_NONE )
+ {
+ //are we /allowed/ to sell this?
+ if( !BG_FindPurchasableForUpgrade( upgrade ) )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"You can't sell this item\n\"" ) );
+ return;
+ }
+ //remove upgrade if carried
+ if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )
+ {
+ BG_RemoveUpgradeFromInventory( upgrade, ent->client->ps.stats );
+
+ if( upgrade == UP_BATTPACK )
+ G_GiveClientMaxAmmo( ent, qtrue );
+
+ //add to funds
+ G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( upgrade ), qfalse );
+ }
+ }
+ else if( !Q_stricmp( s, "weapons" ) )
+ {
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ //guard against selling the HBUILD weapons exploit
+ if( ( i == WP_HBUILD || i == WP_HBUILD2 ) &&
+ ent->client->ps.stats[ STAT_MISC ] > 0 )
+ {
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Cannot sell until build timer expires\n\"" ) );
+ continue;
+ }
+
+ if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) &&
+ BG_FindPurchasableForWeapon( i ) )
+ {
+ BG_RemoveWeaponFromInventory( i, ent->client->ps.stats );
+
+ //add to funds
+ G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( i ), qfalse );
+ }
+
+ //if we have this weapon selected, force a new selection
+ if( i == ent->client->ps.weapon )
+ G_ForceWeaponChange( ent, WP_NONE );
+ }
+ }
+ else if( !Q_stricmp( s, "upgrades" ) )
+ {
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ //remove upgrade if carried
+ if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) &&
+ BG_FindPurchasableForUpgrade( i ) )
+ {
+ BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats );
+
+ if( i == UP_BATTPACK )
+ {
+ int j;
+
+ //remove energy
+ for( j = WP_NONE; j < WP_NUM_WEAPONS; j++ )
+ {
+ if( BG_InventoryContainsWeapon( j, ent->client->ps.stats ) &&
+ BG_FindUsesEnergyForWeapon( j ) &&
+ !BG_FindInfinteAmmoForWeapon( j ) )
+ {
+ BG_PackAmmoArray( j, ent->client->ps.ammo, ent->client->ps.powerups, 0, 0 );
+ }
+ }
+ }
+
+ //add to funds
+ G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( i ), qfalse );
+ }
+ }
+ }
+ else
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Unknown item\n\"" ) );
+
+ if( trap_Argc( ) >= 2 )
+ {
+ trap_Argv( 2, s, sizeof( s ) );
+
+ //retrigger the armoury menu
+ if( !Q_stricmp( s, "retrigger" ) )
+ ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES;
+ }
+
+ //update ClientInfo
+ ClientUserinfoChanged( ent->client->ps.clientNum );
+}
+
+
+/*
+=================
+Cmd_Build_f
+=================
+*/
+void Cmd_Build_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ buildable_t buildable;
+ float dist;
+ vec3_t origin;
+ pTeam_t team;
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ buildable = BG_FindBuildNumForName( s );
+ team = ent->client->ps.stats[ STAT_PTEAM ];
+
+ if( buildable != BA_NONE &&
+ ( ( 1 << ent->client->ps.weapon ) & BG_FindBuildWeaponForBuildable( buildable ) ) &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) &&
+ !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) &&
+ G_BuildableIsAllowed( buildable ) &&
+ ( ( team == PTE_ALIENS && BG_FindStagesForBuildable( buildable, g_alienStage.integer ) ) ||
+ ( team == PTE_HUMANS && BG_FindStagesForBuildable( buildable, g_humanStage.integer ) ) ) )
+ {
+ dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] );
+
+ //these are the errors displayed when the builder first selects something to use
+ switch( G_itemFits( ent, buildable, dist, origin ) )
+ {
+ case IBE_NONE:
+ case IBE_TNODEWARN:
+ case IBE_RPTWARN:
+ case IBE_RPTWARN2:
+ case IBE_SPWNWARN:
+ case IBE_NOROOM:
+ case IBE_NORMAL:
+ case IBE_HOVELEXIT:
+ ent->client->ps.stats[ STAT_BUILDABLE ] = ( buildable | SB_VALID_TOGGLEBIT );
+ break;
+
+ case IBE_NOASSERT:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT );
+ break;
+
+ case IBE_NOOVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND );
+ break;
+
+ case IBE_OVERMIND:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND );
+ break;
+
+ case IBE_REACTOR:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR );
+ break;
+
+ case IBE_REPEATER:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER );
+ break;
+
+ case IBE_NOPOWER:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER );
+ break;
+
+ case IBE_NOCREEP:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP );
+ break;
+
+ case IBE_NODCC:
+ G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC );
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ G_SendCommandFromServer( ent-g_entities, va( "print \"Cannot build this item\n\"" ) );
+}
+
+
+/*
+=================
+Cmd_Boost_f
+=================
+*/
+void Cmd_Boost_f( gentity_t *ent )
+{
+ if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) &&
+ BG_UpgradeIsActive( UP_JETPACK, ent->client->ps.stats ) )
+ return;
+
+ if( ent->client->pers.cmd.buttons & BUTTON_WALKING )
+ return;
+
+ if( ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) &&
+ ( ent->client->ps.stats[ STAT_STAMINA ] > 0 ) )
+ ent->client->ps.stats[ STAT_STATE ] |= SS_SPEEDBOOST;
+}
+
+/*
+=================
+Cmd_Reload_f
+=================
+*/
+void Cmd_Reload_f( gentity_t *ent )
+{
+ if( ent->client->ps.weaponstate != WEAPON_RELOADING )
+ ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD;
+}
+
+/*
+=================
+G_StopFollowing
+
+If the client being followed leaves the game, or you just want to drop
+to free floating spectator mode
+=================
+*/
+void G_StopFollowing( gentity_t *ent )
+{
+ ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR;
+ ent->client->sess.sessionTeam = TEAM_SPECTATOR;
+ ent->client->sess.spectatorState = SPECTATOR_FREE;
+ ent->client->sess.spectatorClient = -1;
+ ent->client->ps.pm_flags &= ~PMF_FOLLOW;
+ ent->client->ps.stats[ STAT_PTEAM ] = PTE_NONE;
+
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING;
+ ent->client->ps.eFlags &= ~EF_WALLCLIMB;
+ ent->client->ps.viewangles[ PITCH ] = 0.0f;
+
+ ent->r.svFlags &= ~SVF_BOT;
+ ent->client->ps.clientNum = ent - g_entities;
+
+ CalculateRanks( );
+}
+
+/*
+=================
+G_FollowNewClient
+
+This was a really nice, elegant function. Then I fucked it up.
+=================
+*/
+qboolean G_FollowNewClient( gentity_t *ent, int dir )
+{
+ int clientnum = ent->client->sess.spectatorClient;
+ int original = clientnum;
+ qboolean selectAny = qfalse;
+
+ if( dir > 1 )
+ dir = 1;
+ else if( dir < -1 )
+ dir = -1;
+ else if( dir == 0 )
+ return qtrue;
+
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR )
+ return qfalse;
+
+ // select any if no target exists
+ if( clientnum < 0 || clientnum >= level.maxclients )
+ {
+ clientnum = original = 0;
+ selectAny = qtrue;
+ }
+
+ do
+ {
+ clientnum += dir;
+
+ if( clientnum >= level.maxclients )
+ clientnum = 0;
+
+ if( clientnum < 0 )
+ clientnum = level.maxclients - 1;
+
+ // avoid selecting existing follow target
+ if( clientnum == original && !selectAny )
+ continue; //effectively break;
+
+ // can't follow self
+ if( &level.clients[ clientnum ] == ent->client )
+ continue;
+
+ // can only follow connected clients
+ if( level.clients[ clientnum ].pers.connected != CON_CONNECTED )
+ continue;
+
+ // can't follow another spectator
+ if( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR )
+ continue;
+
+ // this is good, we can use it
+ ent->client->sess.spectatorClient = clientnum;
+ ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
+ return qtrue;
+
+ } while( clientnum != original );
+
+ return qfalse;
+}
+
+/*
+=================
+Cmd_Follow_f
+=================
+*/
+void Cmd_Follow_f( gentity_t *ent, qboolean toggle )
+{
+ int i;
+ char arg[ MAX_TOKEN_CHARS ];
+
+ if( trap_Argc( ) != 2 || toggle )
+ {
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+ else if( ent->client->sess.spectatorState == SPECTATOR_FREE )
+ G_FollowNewClient( ent, 1 );
+ }
+ else if( ent->client->sess.spectatorState == SPECTATOR_FREE )
+ {
+ trap_Argv( 1, arg, sizeof( arg ) );
+ i = G_ClientNumberFromString( ent, arg );
+
+ if( i == -1 )
+ return;
+
+ // can't follow self
+ if( &level.clients[ i ] == ent->client )
+ return;
+
+ // can't follow another spectator
+ if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR )
+ return;
+
+ // first set them to spectator
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR )
+ return;
+
+ ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
+ ent->client->sess.spectatorClient = i;
+ }
+}
+
+/*
+=================
+Cmd_FollowCycle_f
+=================
+*/
+void Cmd_FollowCycle_f( gentity_t *ent, int dir )
+{
+ // won't work unless spectating
+ if( ent->client->sess.spectatorState == SPECTATOR_NOT )
+ return;
+
+ if( dir != 1 && dir != -1 )
+ G_Error( "Cmd_FollowCycle_f: bad dir %i", dir );
+
+ G_FollowNewClient( ent, dir );
+}
+
+/*
+=================
+Cmd_PTRCVerify_f
+
+Check a PTR code is valid
+=================
+*/
+void Cmd_PTRCVerify_f( gentity_t *ent )
+{
+ connectionRecord_t *connection;
+ char s[ MAX_TOKEN_CHARS ] = { 0 };
+ int code;
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ if( !strlen( s ) )
+ return;
+
+ code = atoi( s );
+
+ if( G_VerifyPTRC( code ) )
+ {
+ connection = G_FindConnectionForCode( code );
+
+ // valid code
+ if( connection->clientTeam != PTE_NONE )
+ G_SendCommandFromServer( ent->client->ps.clientNum, "ptrcconfirm" );
+
+ // restore mapping
+ ent->client->pers.connection = connection;
+ }
+ else
+ {
+ // invalid code -- generate a new one
+ connection = G_GenerateNewConnection( ent->client );
+
+ if( connection )
+ {
+ G_SendCommandFromServer( ent->client->ps.clientNum,
+ va( "ptrcissue %d", connection->ptrCode ) );
+ }
+ }
+}
+
+/*
+=================
+Cmd_PTRCRestore_f
+
+Restore against a PTR code
+=================
+*/
+void Cmd_PTRCRestore_f( gentity_t *ent )
+{
+ char s[ MAX_TOKEN_CHARS ] = { 0 };
+ int code;
+ connectionRecord_t *connection;
+
+ trap_Argv( 1, s, sizeof( s ) );
+
+ if( !strlen( s ) )
+ return;
+
+ code = atoi( s );
+
+ if( G_VerifyPTRC( code ) )
+ {
+ if( ent->client->pers.joinedATeam )
+ {
+ G_SendCommandFromServer( ent - g_entities,
+ "print \"You cannot use a PTR code after joining a team\n\"" );
+ }
+ else
+ {
+ // valid code
+ connection = G_FindConnectionForCode( code );
+
+ if( connection )
+ {
+ // set the correct team
+ G_ChangeTeam( ent, connection->clientTeam );
+
+ // set the correct credit
+ ent->client->ps.persistant[ PERS_CREDIT ] = 0;
+ G_AddCreditToClient( ent->client, connection->clientCredit, qtrue );
+ }
+ }
+ }
+ else
+ {
+ G_SendCommandFromServer( ent - g_entities,
+ va( "print \"\"%d\" is not a valid PTR code\n\"", code ) );
+ }
+}
+
+/*
+=================
+Cmd_Test_f
+=================
+*/
+void Cmd_Test_f( gentity_t *ent )
+{
+ if( !CheatsOk( ent ) )
+ return;
+
+/* ent->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED;
+ ent->client->lastPoisonCloudedTime = level.time;
+ ent->client->lastPoisonCloudedClient = ent;
+ G_SendCommandFromServer( ent->client->ps.clientNum, "poisoncloud" );*/
+
+/* ent->client->ps.stats[ STAT_STATE ] |= SS_POISONED;
+ ent->client->lastPoisonTime = level.time;
+ ent->client->lastPoisonClient = ent;*/
+}
+
+
+/*
+=================
+ClientCommand
+=================
+*/
+void ClientCommand( int clientNum )
+{
+ gentity_t *ent;
+ char cmd[ MAX_TOKEN_CHARS ];
+
+ ent = g_entities + clientNum;
+ if( !ent->client )
+ return; // not fully in game yet
+
+ trap_Argv( 0, cmd, sizeof( cmd ) );
+
+ if( Q_stricmp( cmd, "say" ) == 0 )
+ {
+ Cmd_Say_f( ent, SAY_ALL, qfalse );
+ return;
+ }
+
+ if( Q_stricmp( cmd, "say_team" ) == 0 )
+ {
+ Cmd_Say_f( ent, SAY_TEAM, qfalse );
+ return;
+ }
+
+ if( Q_stricmp( cmd, "tell" ) == 0 )
+ {
+ Cmd_Tell_f( ent );
+ return;
+ }
+
+ if( Q_stricmp( cmd, "score" ) == 0 )
+ {
+ Cmd_Score_f( ent );
+ return;
+ }
+
+ // ignore all other commands when at intermission
+ if( level.intermissiontime )
+ return;
+
+ if( Q_stricmp( cmd, "give" ) == 0 )
+ Cmd_Give_f( ent );
+ else if( Q_stricmp( cmd, "god" ) == 0 )
+ Cmd_God_f( ent );
+ else if( Q_stricmp( cmd, "notarget" ) == 0 )
+ Cmd_Notarget_f( ent );
+ else if( Q_stricmp( cmd, "noclip" ) == 0 )
+ Cmd_Noclip_f( ent );
+ else if( Q_stricmp( cmd, "kill" ) == 0 )
+ Cmd_Kill_f( ent );
+ else if( Q_stricmp( cmd, "levelshot" ) == 0 )
+ Cmd_LevelShot_f( ent );
+ else if( Q_stricmp( cmd, "team" ) == 0 )
+ Cmd_Team_f( ent );
+ else if( Q_stricmp( cmd, "class" ) == 0 )
+ Cmd_Class_f( ent );
+ else if( Q_stricmp( cmd, "build" ) == 0 )
+ Cmd_Build_f( ent );
+ else if( Q_stricmp( cmd, "buy" ) == 0 )
+ Cmd_Buy_f( ent );
+ else if( Q_stricmp( cmd, "sell" ) == 0 )
+ Cmd_Sell_f( ent );
+ else if( Q_stricmp( cmd, "itemact" ) == 0 )
+ Cmd_ActivateItem_f( ent );
+ else if( Q_stricmp( cmd, "itemdeact" ) == 0 )
+ Cmd_DeActivateItem_f( ent );
+ else if( Q_stricmp( cmd, "itemtoggle" ) == 0 )
+ Cmd_ToggleItem_f( ent );
+ else if( Q_stricmp( cmd, "destroy" ) == 0 )
+ Cmd_Destroy_f( ent, qfalse );
+ else if( Q_stricmp( cmd, "deconstruct" ) == 0 )
+ Cmd_Destroy_f( ent, qtrue );
+ else if( Q_stricmp( cmd, "reload" ) == 0 )
+ Cmd_Reload_f( ent );
+ else if( Q_stricmp( cmd, "boost" ) == 0 )
+ Cmd_Boost_f( ent );
+ else if( Q_stricmp( cmd, "where" ) == 0 )
+ Cmd_Where_f( ent );
+ else if( Q_stricmp( cmd, "callvote" ) == 0 )
+ Cmd_CallVote_f( ent );
+ else if( Q_stricmp( cmd, "vote" ) == 0 )
+ Cmd_Vote_f( ent );
+ else if( Q_stricmp( cmd, "callteamvote" ) == 0 )
+ Cmd_CallTeamVote_f( ent );
+ else if( Q_stricmp( cmd, "follow" ) == 0 )
+ Cmd_Follow_f( ent, qfalse );
+ else if( Q_stricmp (cmd, "follownext") == 0)
+ Cmd_FollowCycle_f( ent, 1 );
+ else if( Q_stricmp( cmd, "followprev" ) == 0 )
+ Cmd_FollowCycle_f( ent, -1 );
+ else if( Q_stricmp( cmd, "teamvote" ) == 0 )
+ Cmd_TeamVote_f( ent );
+ else if( Q_stricmp( cmd, "setviewpos" ) == 0 )
+ Cmd_SetViewpos_f( ent );
+ else if( Q_stricmp( cmd, "ptrcverify" ) == 0 )
+ Cmd_PTRCVerify_f( ent );
+ else if( Q_stricmp( cmd, "ptrcrestore" ) == 0 )
+ Cmd_PTRCRestore_f( ent );
+ else if( Q_stricmp( cmd, "test" ) == 0 )
+ Cmd_Test_f( ent );
+ else
+ G_SendCommandFromServer( clientNum, va( "print \"unknown cmd %s\n\"", cmd ) );
+}