/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2006 Tim Angus This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Tremulous; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "g_local.h" /* ================== G_SanitiseName Remove case and control characters from a player name ================== */ void G_SanitiseName( char *in, char *out ) { qboolean skip = qtrue; int spaces = 0; while( *in ) { // strip leading white space if( *in == ' ' ) { if( skip ) { in++; continue; } spaces++; } else { spaces = 0; skip = qfalse; } if( *in == 27 || *in == '^' ) { in += 2; // skip color code continue; } if( *in < 32 ) { in++; continue; } *out++ = tolower( *in++ ); } out -= spaces; *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 ) return -1; cl = &level.clients[ idnum ]; if( cl->pers.connected != CON_CONNECTED ) 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; } return -1; } /* ================== G_MatchOnePlayer This is a companion function to G_ClientNumbersFromString() returns qtrue if the int array plist only has one client id, false otherwise In the case of false, err will be populated with an error message. ================== */ qboolean G_MatchOnePlayer( int *plist, char *err, int len ) { gclient_t *cl; int *p; char line[ MAX_NAME_LENGTH + 10 ] = {""}; err[ 0 ] = '\0'; if( plist[ 0 ] == -1 ) { Q_strcat( err, len, "no connected player by that name or slot #" ); return qfalse; } if( plist[ 1 ] != -1 ) { Q_strcat( err, len, "more than one player name matches. " "be more specific or use the slot #:\n" ); for( p = plist; *p != -1; p++ ) { cl = &level.clients[ *p ]; if( cl->pers.connected == CON_CONNECTED ) { Com_sprintf( line, sizeof( line ), "%2i - %s^7\n", *p, cl->pers.netname ); if( strlen( err ) + strlen( line ) > len ) break; Q_strcat( err, len, line ); } } return qfalse; } return qtrue; } /* ================== G_ClientNumbersFromString Sets plist to an array of integers that represent client numbers that have names that are a partial match for s. List is terminated by a -1. Returns number of matching clientids. ================== */ int G_ClientNumbersFromString( char *s, int *plist ) { gclient_t *p; int i, found = 0; char n2[ MAX_NAME_LENGTH ] = {""}; char s2[ MAX_NAME_LENGTH ] = {""}; qboolean is_slot = qtrue; *plist = -1; // if a number is provided, it might be a slot # for( i = 0; i < (int)strlen( s ); i++ ) { if( s[i] < '0' || s[i] > '9' ) { is_slot = qfalse; break; } } if( is_slot ) { i = atoi( s ); if( i >= 0 && i < level.maxclients ) { p = &level.clients[ i ]; if( p->pers.connected == CON_CONNECTED || p->pers.connected == CON_CONNECTING ) { *plist++ = i; *plist = -1; return 1; } } // we must assume that if only a number is provided, it is a clientNum return 0; } // now look for name matches G_SanitiseName( s, s2 ); if( strlen( s2 ) < 1 ) return 0; for( i = 0; i < level.maxclients; i++ ) { p = &level.clients[ i ]; if(p->pers.connected != CON_CONNECTED && p->pers.connected != CON_CONNECTING) { continue; } G_SanitiseName( p->pers.netname, n2 ); if( strstr( n2, s2 ) ) { *plist++ = i; found++; } } *plist = -1; return found; } /* ================== 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; } trap_SendServerCommand( 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 ) { trap_SendServerCommand( ent-g_entities, va( "print \"Cheats are not enabled on this server\n\"" ) ); return qfalse; } if( ent->health <= 0 ) { trap_SendServerCommand( 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"; trap_SendServerCommand( 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"; trap_SendServerCommand( 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; trap_SendServerCommand( 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( ); trap_SendServerCommand( 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 ) { trap_SendServerCommand( 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 ) { trap_SendServerCommand( ent-g_entities, "print \"You will suicide in 20 seconds\n\"" ); ent->suicideTime = level.time + 20000; } else if( ent->suicideTime > level.time ) { trap_SendServerCommand( 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; if( oldTeam == newTeam ) return; ent->client->pers.teamSelection = newTeam; 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 ); // under certain circumstances, clients can keep their kills and credits // when switching teams if( G_admin_permission( ent, ADMF_TEAMCHANGEFREE ) || ( ( oldTeam == PTE_HUMANS || oldTeam == PTE_ALIENS ) && ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) ) { if( oldTeam == PTE_NONE ) { // ps.persistant[] from a spectator cannot be trusted ent->client->ps.persistant[ PERS_SCORE ] = ent->client->pers.savedScore; ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.savedCredit; } else if( oldTeam == PTE_ALIENS ) { // always save in human credtis ent->client->ps.persistant[ PERS_CREDIT ] *= (float)FREEKILL_HUMAN / FREEKILL_ALIEN; } if( newTeam == PTE_NONE ) { // save values before the client enters the spectator team and their // ps.persistant[] values become trashed ent->client->pers.savedScore = ent->client->ps.persistant[ PERS_SCORE ]; ent->client->pers.savedCredit = ent->client->ps.persistant[ PERS_CREDIT ]; } else if( newTeam == PTE_ALIENS ) { // convert to alien currency ent->client->ps.persistant[ PERS_CREDIT ] *= (float)FREEKILL_ALIEN / FREEKILL_HUMAN; } } else { ent->client->ps.persistant[ PERS_CREDIT ] = 0; ent->client->ps.persistant[ PERS_SCORE ] = 0; ent->client->pers.savedScore = 0; ent->client->pers.savedCredit = 0; } ent->client->pers.classSelection = PCL_NONE; ClientSpawn( ent, NULL, NULL, NULL ); ent->client->pers.joinedATeam = qtrue; ent->client->pers.teamChangeTime = level.time; //update ClientInfo ClientUserinfoChanged( ent->client->ps.clientNum ); } /* ================= Cmd_Team_f ================= */ void Cmd_Team_f( gentity_t *ent ) { pTeam_t team; pTeam_t oldteam = ent->client->pers.teamSelection; char s[ MAX_TOKEN_CHARS ]; qboolean force = G_admin_permission(ent, ADMF_FORCETEAMCHANGE); int aliens = level.numAlienClients; int humans = level.numHumanClients; // stop team join spam if( level.time - ent->client->pers.teamChangeTime < 1000 ) return; if( oldteam == PTE_ALIENS ) aliens--; else if( oldteam == PTE_HUMANS ) humans--; trap_Argv( 1, s, sizeof( s ) ); if( !strlen( s ) ) { trap_SendServerCommand( ent-g_entities, va("print \"team: %i\n\"", oldteam ) ); return; } if( !Q_stricmp( s, "spectate" ) ) team = PTE_NONE; else if( !Q_stricmp( s, "aliens" ) ) { if( level.alienTeamLocked ) { trap_SendServerCommand( ent-g_entities, va( "print \"Alien team has been ^1LOCKED\n\"" ) ); return; } else if( level.humanTeamLocked ) { // if only one team has been locked, let people join the other // regardless of balance force = qtrue; } if( !force && g_teamForceBalance.integer && aliens > humans ) { G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); return; } team = PTE_ALIENS; } else if( !Q_stricmp( s, "humans" ) ) { if( level.humanTeamLocked ) { trap_SendServerCommand( ent-g_entities, va( "print \"Human team has been ^1LOCKED\n\"" ) ); return; } else if( level.alienTeamLocked ) { // if only one team has been locked, let people join the other // regardless of balance force = qtrue; } if( !force && g_teamForceBalance.integer && humans > aliens ) { G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); return; } team = PTE_HUMANS; } else if( !Q_stricmp( s, "auto" ) ) { if( level.humanTeamLocked && level.alienTeamLocked ) team = PTE_NONE; else if( humans > aliens ) team = PTE_ALIENS; else if( humans < aliens ) team = PTE_HUMANS; else team = PTE_ALIENS + ( rand( ) % 2 ); if( team == PTE_ALIENS && level.alienTeamLocked ) team = PTE_HUMANS; else if( team == PTE_HUMANS && level.humanTeamLocked ) team = PTE_ALIENS; } else { trap_SendServerCommand( ent-g_entities, va( "print \"Unknown team: %s\n\"", s ) ); return; } // stop team join spam if( oldteam == team ) return; //guard against build timer exploit if( oldteam != PTE_NONE && ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG || BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) || BG_InventoryContainsWeapon( WP_HBUILD2, ent->client->ps.stats ) ) && ent->client->ps.stats[ STAT_MISC ] > 0 ) { if( ent->client->pers.teamSelection == PTE_ALIENS ) G_TriggerMenu( ent->client->ps.clientNum, MN_A_TEAMCHANGEBUILDTIMER ); else G_TriggerMenu( ent->client->ps.clientNum, MN_H_TEAMCHANGEBUILDTIMER ); return; } G_ChangeTeam( ent, team ); if( team == PTE_ALIENS ) trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " joined the aliens\n\"", ent->client->pers.netname ) ); else if( team == PTE_HUMANS ) trap_SendServerCommand( -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 ) { qboolean ignore = qfalse; 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 ) ) { if( other->client->ps.stats[ STAT_PTEAM ] != PTE_NONE ) return; if( !G_admin_permission( other, ADMF_SPEC_ALLCHAT ) ) return; // specs with ADMF_SPEC_ALLCHAT flag can see team chat } if( BG_ClientListTest( &other->client->sess.ignoreList, ent-g_entities ) ) ignore = qtrue; trap_SendServerCommand( other-g_entities, va( "%s \"%s%s%c%c%s\"", mode == SAY_TEAM ? "tchat" : "chat", ( ignore ) ? "[skipnotify]" : "", 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; const char *prefix; char name[ 64 ]; // don't let text be too long for malicious reasons char text[ MAX_SAY_TEXT ]; char location[ 64 ]; if (g_chatTeamPrefix.integer) { switch( ent->client->pers.teamSelection) { default: case PTE_NONE: prefix = "[S] "; break; case PTE_ALIENS: prefix = "[A] "; break; case PTE_HUMANS: prefix = "[H] "; } } else prefix = ""; switch( mode ) { default: case SAY_ALL: G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); Com_sprintf( name, sizeof( name ), "%s%s%c%c"EC": ", prefix, 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 ); } if( g_adminParseSay.integer ) { G_admin_cmd_check ( ent, qtrue ); } } /* ================== Cmd_Say_f ================== */ static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { char *p; char *args; if( ent->client->pers.muted ) { return; } // support parsing /m out of say text since some people have a hard // time figuring out what the console is. if( g_privateMessages.integer ) { args = G_SayConcatArgs(0); if( !Q_stricmpn( args, "say /m ", 7 ) || !Q_stricmpn( args, "say_team /m ", 12 ) || !Q_stricmpn( args, "say /mt ", 8 ) || !Q_stricmpn( args, "say_team /mt ", 13 ) ) { G_PrivateMessage( ent ); return; } } 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 ) G_Say( ent, ent, SAY_TELL, p ); } /* ================== Cmd_Where_f ================== */ void Cmd_Where_f( gentity_t *ent ) { trap_SendServerCommand( 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 ]; int clientNum = -1; char name[ MAX_NETNAME ]; if( !g_allowVote.integer ) { trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" ); return; } if( level.voteTime ) { trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress\n\"" ); return; } if( g_voteLimit.integer > 0 && ent->client->pers.voteCount >= g_voteLimit.integer && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) { trap_SendServerCommand( ent-g_entities, va( "print \"You have already called the maxium number of votes (%d)\n\"", g_voteLimit.integer ) ); 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, ';' ) ) { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\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 ) ); } // detect clientNum for partial name match votes if( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "mute" ) || !Q_stricmp( arg1, "unmute" ) ) { int clientNums[ MAX_CLIENTS ] = { -1 }; if( G_ClientNumbersFromString( arg2, clientNums ) == 1 ) { // there was only one partial name match clientNum = clientNums[ 0 ]; } else { // look for an exact name match (sets clientNum to -1 if it fails) clientNum = G_ClientNumberFromString( ent, arg2 ); } if( clientNum != -1 && level.clients[ clientNum ].pers.connected == CON_DISCONNECTED ) { clientNum = -1; } if( clientNum != -1 ) { Q_strncpyz( name, level.clients[ clientNum ].pers.netname, sizeof( name ) ); Q_CleanStr( name ); } } if( !Q_stricmp( arg1, "kick" ) ) { if( clientNum == -1 ) { trap_SendServerCommand( ent-g_entities, "print \"callvote: invalid player\n\"" ); return; } if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) { trap_SendServerCommand( ent-g_entities, "print \"callvote: admin is immune from vote kick\n\"" ); return; } // use ip in case this player disconnects before the vote ends Com_sprintf( level.voteString, sizeof( level.voteString ), "!ban %s %d vote kick", level.clients[ clientNum ].pers.ip, g_adminTempBan.integer + 1 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Kick player \'%s\'", name ); } else if( !Q_stricmp( arg1, "mute" ) ) { if( clientNum == -1 ) { trap_SendServerCommand( ent-g_entities, "print \"callvote: invalid player\n\"" ); return; } if( level.clients[ clientNum ].pers.muted ) { trap_SendServerCommand( ent-g_entities, "print \"callvote: player is already muted\n\"" ); return; } if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) { trap_SendServerCommand( ent-g_entities, "print \"callvote: admin is immune from vote mute\n\"" ); return; } Com_sprintf( level.voteString, sizeof( level.voteString ), "!mute %i", clientNum ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Mute player \'%s\'", name ); } else if( !Q_stricmp( arg1, "unmute" ) ) { if( clientNum == -1 ) { trap_SendServerCommand( ent-g_entities, "print \"callvote: invalid player\n\"" ); return; } if( !level.clients[ clientNum ].pers.muted ) { trap_SendServerCommand( ent-g_entities, "print \"callvote: player is not currently muted\n\"" ); return; } Com_sprintf( level.voteString, sizeof( level.voteString ), "!unmute %i", clientNum ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Un-Mute player \'%s\'", name ); } else if( !Q_stricmp( arg1, "map_restart" ) ) { Com_sprintf( level.voteString, sizeof( level.voteString ), "%s", arg1 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Restart current map" ); } else if( !Q_stricmp( arg1, "map" ) ) { if( !trap_FS_FOpenFile( va( "maps/%s.bsp", arg2 ), NULL, FS_READ ) ) { trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " "'maps/%s.bsp' could not be found on the server\n\"", arg2 ) ); return; } Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Change to map '%s'", arg2 ); } else if( !Q_stricmp( arg1, "draw" ) ) { Com_sprintf( level.voteString, sizeof( level.voteString ), "evacuation" ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "End match in a draw" ); } else { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " "map_restart, draw, kick, mute and unmute\n" ); return; } trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " called a vote\n\"", ent->client->pers.netname ) ); ent->client->pers.voteCount++; // 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 ) { trap_SendServerCommand( ent-g_entities, "print \"No vote in progress\n\"" ); return; } if( ent->client->ps.eFlags & EF_VOTED ) { trap_SendServerCommand( ent-g_entities, "print \"Vote already cast\n\"" ); return; } trap_SendServerCommand( 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 ]; int clientNum = -1; char name[ MAX_NETNAME ]; team = ent->client->pers.teamSelection; if( team == PTE_NONE ) { trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a team vote as a spectator\n\"" ); return; } if( team == PTE_HUMANS ) cs_offset = 0; else if( team == PTE_ALIENS ) cs_offset = 1; else return; if( !g_allowVote.integer ) { trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" ); return; } if( level.teamVoteTime[ cs_offset ] ) { trap_SendServerCommand( ent-g_entities, "print \"A team vote is already in progress\n\"" ); return; } if( g_voteLimit.integer > 0 && ent->client->pers.voteCount >= g_voteLimit.integer && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) { trap_SendServerCommand( ent-g_entities, va( "print \"You have already called the maxium number of votes (%d)\n\"", g_voteLimit.integer ) ); 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, ';' ) ) { trap_SendServerCommand( ent-g_entities, "print \"Invalid team vote string\n\"" ); return; } // detect clientNum for partial name match votes if( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "denybuild" ) || !Q_stricmp( arg1, "allowbuild" ) ) { int clientNums[ MAX_CLIENTS ] = { -1 }; if( G_ClientNumbersFromString( arg2, clientNums ) == 1 ) { // there was only one partial name match clientNum = clientNums[ 0 ]; } else { // look for an exact name match (sets clientNum to -1 if it fails) clientNum = G_ClientNumberFromString( ent, arg2 ); } // make sure this player is on the same team if( clientNum != -1 && level.clients[ clientNum ].pers.teamSelection != ent->client->pers.teamSelection ) { clientNum = -1; } if( clientNum != -1 && level.clients[ clientNum ].pers.connected == CON_DISCONNECTED ) { clientNum = -1; } if( clientNum != -1 ) { Q_strncpyz( name, level.clients[ clientNum ].pers.netname, sizeof( name ) ); Q_CleanStr( name ); } } if( !Q_stricmp( arg1, "kick" ) ) { if( clientNum == -1 ) { trap_SendServerCommand( ent-g_entities, "print \"callteamvote: invalid player\n\"" ); return; } if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) { trap_SendServerCommand( ent-g_entities, "print \"callteamvote: admin is immune from vote kick\n\"" ); return; } // use ip in case this player disconnects before the vote ends Com_sprintf( level.teamVoteString[ cs_offset ], sizeof( level.teamVoteString[ cs_offset ] ), "!ban %s %d team vote kick", level.clients[ clientNum ].pers.ip, g_adminTempBan.integer + 1 ); Com_sprintf( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), "Kick player '%s'", name ); } else if( !Q_stricmp( arg1, "denybuild" ) ) { if( clientNum == -1 ) { trap_SendServerCommand( ent-g_entities, "print \"callteamvote: invalid player\n\"" ); return; } if( level.clients[ clientNum ].pers.denyBuild ) { trap_SendServerCommand( ent-g_entities, "print \"callteamvote: player already lost building rights\n\"" ); return; } if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) { trap_SendServerCommand( ent-g_entities, "print \"callteamvote: admin is immune from denybuild\n\"" ); return; } Com_sprintf( level.teamVoteString[ cs_offset ], sizeof( level.teamVoteString[ cs_offset ] ), "!denybuild %i", clientNum ); Com_sprintf( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), "Take away building rights from '%s'", name ); } else if( !Q_stricmp( arg1, "allowbuild" ) ) { if( clientNum == -1 ) { trap_SendServerCommand( ent-g_entities, "print \"callteamvote: invalid player\n\"" ); return; } if( !level.clients[ clientNum ].pers.denyBuild ) { trap_SendServerCommand( ent-g_entities, "print \"callteamvote: player already has building rights\n\"" ); return; } Com_sprintf( level.teamVoteString[ cs_offset ], sizeof( level.teamVoteString[ cs_offset ] ), "!allowbuild %i", clientNum ); Com_sprintf( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), "Allow '%s' to build", name ); } else if( !Q_stricmp( arg1, "admitdefeat" ) ) { Com_sprintf( level.teamVoteString[ cs_offset ], sizeof( level.teamVoteString[ cs_offset ] ), "admitdefeat %i", team ); Com_sprintf( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), "Admit Defeat" ); } else { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Valid team vote commands are: " "kick, denybuild, allowbuild and admitdefeat\n\"" ); return; } ent->client->pers.voteCount++; 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 ) trap_SendServerCommand( i, va("print \"%s " S_COLOR_WHITE "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.teamVoteDisplayString[ 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 ] ) { trap_SendServerCommand( ent-g_entities, "print \"No team vote in progress\n\"" ); return; } if( ent->client->ps.eFlags & EF_TEAMVOTED ) { trap_SendServerCommand( ent-g_entities, "print \"Team vote already cast\n\"" ); return; } if( ent->client->pers.teamSelection == PTE_NONE ) { trap_SendServerCommand( ent-g_entities, "print \"Not allowed to vote as spectator\n\"" ); return; } trap_SendServerCommand( 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 ) { trap_SendServerCommand( ent-g_entities, va( "print \"Cheats are not enabled on this server\n\"" ) ); return; } if( trap_Argc( ) != 5 ) { trap_SendServerCommand( 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 AS_OVER_RT3 ((ALIENSENSE_RANGE*0.5f)/M_ROOT3) static qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin ) { vec3_t fromMins, fromMaxs; vec3_t toMins, toMaxs; vec3_t temp; trace_t tr; float nudgeHeight; float maxHorizGrowth; pClass_t oldClass = ent->client->ps.stats[ STAT_PCLASS ]; BG_FindBBoxForClass( oldClass, fromMins, fromMaxs, NULL, NULL, NULL ); BG_FindBBoxForClass( class, toMins, toMaxs, NULL, NULL, NULL ); VectorCopy( ent->s.origin, newOrigin ); // find max x/y diff maxHorizGrowth = toMaxs[ 0 ] - fromMaxs[ 0 ]; if( toMaxs[ 1 ] - fromMaxs[ 1 ] > maxHorizGrowth ) maxHorizGrowth = toMaxs[ 1 ] - fromMaxs[ 1 ]; if( toMins[ 0 ] - fromMins[ 0 ] > -maxHorizGrowth ) maxHorizGrowth = -( toMins[ 0 ] - fromMins[ 0 ] ); if( toMins[ 1 ] - fromMins[ 1 ] > -maxHorizGrowth ) maxHorizGrowth = -( toMins[ 1 ] - fromMins[ 1 ] ); if( maxHorizGrowth > 0.0f ) { // test by moving the player up the max required on a 60 degree slope nudgeHeight = maxHorizGrowth * 2.0f; } else { // player is shrinking, so there's no need to nudge them upwards nudgeHeight = 0.0f; } // find what the new origin would be on a level surface newOrigin[ 2 ] += fabs( toMins[ 2 ] ) - fabs( fromMins[ 2 ] ); //compute a place up in the air to start the real trace VectorCopy( newOrigin, temp ); temp[ 2 ] += nudgeHeight; trap_Trace( &tr, newOrigin, toMins, toMaxs, temp, ent->s.number, MASK_SHOT ); //trace down to the ground so that we can evolve on slopes VectorCopy( newOrigin, temp ); temp[ 2 ] += ( nudgeHeight * tr.fraction ); trap_Trace( &tr, temp, toMins, toMaxs, newOrigin, ent->s.number, MASK_SHOT ); VectorCopy( tr.endpos, newOrigin ); //make REALLY sure trap_Trace( &tr, newOrigin, toMins, toMaxs, newOrigin, ent->s.number, MASK_SHOT ); //check there is room to evolve if( !tr.startsolid && tr.fraction == 1.0f ) return qtrue; else return qfalse; } /* ================= Cmd_Class_f ================= */ void Cmd_Class_f( gentity_t *ent ) { char s[ MAX_TOKEN_CHARS ]; int clientNum; int i; vec3_t infestOrigin; int allowedClasses[ PCL_NUM_CLASSES ]; int numClasses = 0; pClass_t currentClass = ent->client->ps.stats[ STAT_PCLASS ]; pClass_t newClass; int numLevels; 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( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) ) allowedClasses[ numClasses++ ] = PCL_ALIEN_BUILDER0; if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) && BG_FindStagesForClass( PCL_ALIEN_BUILDER0_UPG, g_alienStage.integer ) ) allowedClasses[ numClasses++ ] = PCL_ALIEN_BUILDER0_UPG; if( BG_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 ) ) { newClass = BG_FindClassNumForName( s ); if( newClass == PCL_NONE ) { trap_SendServerCommand( ent-g_entities, va( "print \"Unknown class\n\"" ) ); return; } //if we are not currently spectating, we are attempting evolution if( ent->client->pers.classSelection != PCL_NONE ) { if( ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) { trap_SendServerCommand( ent-g_entities, va( "print \"You cannot evolve while wallwalking\n\"" ) ); return; } //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 ) ) { G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); return; } } if( !level.overmindPresent ) { 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_TriggerMenu( ent->client->ps.clientNum, MN_A_EVOLVEBUILDTIMER ); return; } numLevels = BG_ClassCanEvolveFromTo( currentClass, newClass, (short)ent->client->ps.persistant[ PERS_CREDIT ], 0 ); if( G_RoomForClassChange( ent, newClass, infestOrigin ) ) { //...check we can evolve to that class if( numLevels >= 0 && BG_FindStagesForClass( newClass, g_alienStage.integer ) && BG_ClassIsAllowed( newClass ) ) { 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 ); ent->client->pers.classSelection = newClass; ClientUserinfoChanged( clientNum ); VectorCopy( infestOrigin, ent->s.pos.trBase ); ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase ); return; } else { trap_SendServerCommand( ent-g_entities, va( "print \"You cannot evolve from your current class\n\"" ) ); return; } } else { G_TriggerMenu( clientNum, MN_A_NOEROOM ); return; } } else { //spawning from an egg for( i = 0; i < numClasses; i++ ) { if( allowedClasses[ i ] == newClass && BG_FindStagesForClass( newClass, g_alienStage.integer ) && BG_ClassIsAllowed( newClass ) ) { ent->client->pers.classSelection = ent->client->ps.stats[ STAT_PCLASS ] = newClass; G_PushSpawnQueue( &level.alienSpawnQueue, clientNum ); return; } } trap_SendServerCommand( ent-g_entities, va( "print \"You cannot spawn as this 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 ) { trap_SendServerCommand( 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 ) ) && BG_WeaponIsAllowed( WP_MACHINEGUN ) ) ent->client->pers.humanItemSelection = WP_MACHINEGUN; else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) && BG_WeaponIsAllowed( WP_HBUILD ) ) ent->client->pers.humanItemSelection = WP_HBUILD; else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) && BG_WeaponIsAllowed( WP_HBUILD2 ) && BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) ) ent->client->pers.humanItemSelection = WP_HBUILD2; else { ent->client->pers.classSelection = PCL_NONE; trap_SendServerCommand( 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 ); trap_SendServerCommand( 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->pers.denyBuild ) { trap_SendServerCommand( ent-g_entities, "print \"Your building rights have been revoked\n\"" ); return; } 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 ) ) ) { // Cancel deconstruction if( g_markDeconstruct.integer && traceEnt->deconstruct ) { traceEnt->deconstruct = qfalse; return; } // Prevent destruction of the last spawn if( !g_markDeconstruct.integer ) { if( ent->client->pers.teamSelection == PTE_ALIENS && traceEnt->s.modelindex == BA_A_SPAWN ) { if( level.numAlienSpawns <= 1 ) return; } else if( ent->client->pers.teamSelection == PTE_HUMANS && traceEnt->s.modelindex == BA_H_SPAWN ) { if( level.numHumanSpawns <= 1 ) return; } } // Don't allow destruction of hovel with granger inside if( traceEnt->s.modelindex == BA_A_HOVEL && traceEnt->active ) return; // Don't allow destruction of buildables that cannot be rebuilt if( G_TimeTilSuddenDeath( ) <= 0 && BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) { return; } if( ent->client->ps.stats[ STAT_MISC ] > 0 ) { G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); return; } if( traceEnt->health > 0 ) { if( g_markDeconstruct.integer ) { traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction traceEnt->deconstructTime = level.time; } else { G_TeamCommand( ent->client->pers.teamSelection, va( "print \"%s ^3DECONSTRUCTED^7 by %s^7\n\"", BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), ent->client->pers.netname ) ); G_LogPrintf( "Decon: %i %i 0: %s deconstructed %s\n", ent->client->ps.clientNum, traceEnt->s.modelindex, ent->client->pers.netname, BG_FindNameForBuildable( traceEnt->s.modelindex ) ); if( !deconstruct && CheatsOk( ent ) ) G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE ); else G_FreeEntity( traceEnt ); if( !g_cheats.integer ) 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( ent->client->pers.classSelection == PCL_NONE ) 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 trap_SendServerCommand( 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( ent->client->pers.classSelection == PCL_NONE ) return; if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); else trap_SendServerCommand( 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 trap_SendServerCommand( 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; qboolean hasEnergyWeapon = 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 ) ) { if( BG_FindUsesEnergyForWeapon( i ) ) hasEnergyWeapon = qtrue; 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 = hasEnergyWeapon; } 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_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOENERGYAMMOHERE ); return; } } else { //no armoury nearby if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); 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 trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) ); return; } //are we /allowed/ to buy this? if( !BG_FindPurchasableForWeapon( weapon ) ) { trap_SendServerCommand( 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 ) || !BG_WeaponIsAllowed( weapon ) ) { trap_SendServerCommand( 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 trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) ); return; } //are we /allowed/ to buy this? if( !BG_FindPurchasableForUpgrade( upgrade ) ) { trap_SendServerCommand( 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 ) || !BG_UpgradeIsAllowed( upgrade ) ) { trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); return; } if( upgrade == UP_AMMO ) G_GiveClientMaxAmmo( ent, buyingEnergyAmmo ); else { if( upgrade == UP_BATTLESUIT ) { vec3_t newOrigin; if( !G_RoomForClassChange( ent, PCL_HUMAN_BSUIT, newOrigin ) ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITON ); return; } VectorCopy( newOrigin, ent->s.pos.trBase ); ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN_BSUIT; } //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 { trap_SendServerCommand( 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_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); return; } weapon = BG_FindWeaponNumForName( s ); upgrade = BG_FindUpgradeNumForName( s ); if( weapon != WP_NONE ) { //are we /allowed/ to sell this? if( !BG_FindPurchasableForWeapon( weapon ) ) { trap_SendServerCommand( 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_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); 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 ) ) { trap_SendServerCommand( 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 ) ) { // shouldn't really need to test for this, but just to be safe if( upgrade == UP_BATTLESUIT ) { vec3_t newOrigin; if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); return; } VectorCopy( newOrigin, ent->s.pos.trBase ); ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN; } //add to inventory 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_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); 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 ) ) { // shouldn't really need to test for this, but just to be safe if( i == UP_BATTLESUIT ) { vec3_t newOrigin; if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); continue; } VectorCopy( newOrigin, ent->s.pos.trBase ); ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN; } 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 trap_SendServerCommand( 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; if( ent->client->pers.denyBuild ) { trap_SendServerCommand( ent-g_entities, "print \"Your building rights have been revoked\n\"" ); return; } 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 ) && BG_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_CanBuild( 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 trap_SendServerCommand( 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->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; int pids[ MAX_CLIENTS ]; 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 || ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { trap_Argv( 1, arg, sizeof( arg ) ); if( G_ClientNumbersFromString( arg, pids ) == 1 ) { i = pids[ 0 ]; } else { i = G_ClientNumberFromString( ent, arg ); if( i == -1 ) { trap_SendServerCommand( ent - g_entities, "print \"follow: invalid player\n\"" ); return; } } // can't follow self if( &level.clients[ i ] == ent->client ) return; // can't follow another spectator if( level.clients[ i ].pers.teamSelection == PTE_NONE ) 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->pers.teamSelection != PTE_NONE ) return; 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 ) trap_SendServerCommand( 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 ) { trap_SendServerCommand( 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 ) { trap_SendServerCommand( 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 { trap_SendServerCommand( 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; trap_SendServerCommand( ent->client->ps.clientNum, "poisoncloud" );*/ /* ent->client->ps.stats[ STAT_STATE ] |= SS_POISONED; ent->client->lastPoisonTime = level.time; ent->client->lastPoisonClient = ent;*/ } static void Cmd_Ignore_f( gentity_t *ent, qboolean ignore ) { int pids[ MAX_CLIENTS ]; char name[ MAX_NAME_LENGTH ]; const char *cmd; int matches = 0; int i; cmd = ( ignore ) ? "ignore" : "unignore"; if( trap_Argc() < 2 ) { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "%s: usage \\%s [clientNum | partial name match]\n\"", cmd, cmd ) ); return; } Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); matches = G_ClientNumbersFromString( name, pids ); if( matches < 1 ) { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "%s: no clients match the name '%s'\n\"", cmd, name ) ); return; } for( i = 0; i < matches; i++ ) { if( ignore ) { if( !BG_ClientListTest( &ent->client->sess.ignoreList, pids[ i ] ) ) { BG_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] ); ClientUserinfoChanged( ent->client->ps.clientNum ); trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "ignore: added %s^7 to your ignore list\n\"", level.clients[ pids[ i ] ].pers.netname ) ); } else { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "ignore: %s^7 is already on your ignore list\n\"", level.clients[ pids[ i ] ].pers.netname ) ); } } else { if( BG_ClientListTest( &ent->client->sess.ignoreList, pids[ i ] ) ) { BG_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] ); ClientUserinfoChanged( ent->client->ps.clientNum ); trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "unignore: removed %s^7 from your ignore list\n\"", level.clients[ pids[ i ] ].pers.netname ) ); } else { trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" "unignore: %s^7 is not on your ignore list\n\"", level.clients[ pids[ i ] ].pers.netname ) ); } } } } /* ================= 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, "m" ) || !Q_stricmp( cmd, "mt" ) ) { G_PrivateMessage( ent ); return; } if( Q_stricmp( cmd, "score" ) == 0 ) { Cmd_Score_f( ent ); return; } if( !Q_stricmp( cmd, "ignore" ) ) { Cmd_Ignore_f( ent, qtrue ); return; } if( !Q_stricmp( cmd, "unignore" ) ) { Cmd_Ignore_f( ent, qfalse ); return; } if( G_admin_cmd_check( ent, qfalse ) ) 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 trap_SendServerCommand( clientNum, va( "print \"unknown cmd %s\n\"", cmd ) ); } int G_SayArgc() { int c = 1; char *s; s = ConcatArgs( 0 ); if( !*s ) return 0; while( *s ) { if( *s == ' ' ) { s++; if( *s != ' ' ) { c++; continue; } while( *s && *s == ' ' ) s++; c++; } s++; } return c; } qboolean G_SayArgv( int n, char *buffer, int bufferLength ) { int bc = 1; int c = 0; char *s; if( bufferLength < 1 ) return qfalse; if(n < 0) return qfalse; *buffer = '\0'; s = ConcatArgs( 0 ); while( *s ) { if( c == n ) { while( *s && ( bc < bufferLength ) ) { if( *s == ' ' ) { *buffer = '\0'; return qtrue; } *buffer = *s; buffer++; s++; bc++; } *buffer = '\0'; return qtrue; } if( *s == ' ' ) { s++; if( *s != ' ' ) { c++; continue; } while( *s && *s == ' ' ) s++; c++; } s++; } return qfalse; } char *G_SayConcatArgs(int start) { char *s; int c = 0; s = ConcatArgs( 0 ); while( *s ) { if( c == start ) return s; if( *s == ' ' ) { s++; if( *s != ' ' ) { c++; continue; } while( *s && *s == ' ' ) s++; c++; } s++; } return s; } void G_DecolorString( char *in, char *out ) { while( *in ) { if( *in == 27 || *in == '^' ) { in++; if( *in ) in++; continue; } *out++ = *in++; } *out = '\0'; } void G_PrivateMessage( gentity_t *ent ) { int pids[ MAX_CLIENTS ]; int ignoreids[ MAX_CLIENTS ]; char name[ MAX_NAME_LENGTH ]; char cmd[ 12 ]; char str[ MAX_STRING_CHARS ]; char *msg; char color; int pcount, matches, ignored = 0; int i; int skipargs = 0; qboolean teamonly = qfalse; gentity_t *tmpent; if( !g_privateMessages.integer && ent ) return; G_SayArgv( 0, cmd, sizeof( cmd ) ); if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) { skipargs = 1; G_SayArgv( 1, cmd, sizeof( cmd ) ); } if( G_SayArgc( ) < 3+skipargs ) { ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) ); return; } if( !Q_stricmp( cmd, "mt" ) || !Q_stricmp( cmd, "/mt" ) ) teamonly = qtrue; G_SayArgv( 1+skipargs, name, sizeof( name ) ); msg = G_SayConcatArgs( 2+skipargs ); pcount = G_ClientNumbersFromString( name, pids ); if( ent ) { int count = 0; for( i=0; i < pcount; i++ ) { tmpent = &g_entities[ pids[ i ] ]; if( teamonly && !OnSameTeam( ent, tmpent ) ) continue; if( BG_ClientListTest( &tmpent->client->sess.ignoreList, ent-g_entities ) ) { ignoreids[ ignored++ ] = pids[ i ]; continue; } pids[ count ] = pids[ i ]; count++; } matches = count; } else { matches = pcount; } color = teamonly ? COLOR_CYAN : COLOR_YELLOW; Q_strncpyz( str, va( "^%csent to %i player%s: ^7", color, matches, ( matches == 1 ) ? "" : "s" ), sizeof( str ) ); for( i=0; i < matches; i++ ) { tmpent = &g_entities[ pids[ i ] ]; if( i > 0 ) Q_strcat( str, sizeof( str ), "^7, " ); Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); trap_SendServerCommand( pids[ i ], va( "chat \"%s^%c -> ^7%s^7: (%d recipients): ^%c%s^7\" %i", ( ent ) ? ent->client->pers.netname : "console", color, name, matches, color, msg, ent ? ent-g_entities : -1 ) ); if( ent ) { trap_SendServerCommand( pids[ i ], va( "print \">> to reply, say: /m %d [your message] <<\n\"", ( ent - g_entities ) ) ); } trap_SendServerCommand( pids[ i ], va( "cp \"^%cprivate message from ^7%s^7\"", color, ( ent ) ? ent->client->pers.netname : "console" ) ); } if( !matches ) ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n", name ) ); else { ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) ); ADMP( va( "%s\n", str ) ); G_LogPrintf( "%s: %s: %s: %s\n", ( teamonly ) ? "tprivmsg" : "privmsg", ( ent ) ? ent->client->pers.netname : "console", name, msg ); } if( ignored ) { Q_strncpyz( str, va( "^%cignored by %i player%s: ^7", color, ignored, ( ignored == 1 ) ? "" : "s" ), sizeof( str ) ); for( i=0; i < ignored; i++ ) { tmpent = &g_entities[ ignoreids[ i ] ]; if( i > 0 ) Q_strcat( str, sizeof( str ), "^7, " ); Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); } ADMP( va( "%s\n", str ) ); } }