diff options
Diffstat (limited to 'mod/src/game/g_cmds.c')
-rw-r--r-- | mod/src/game/g_cmds.c | 2305 |
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 ) ); +} |