diff options
Diffstat (limited to 'src/game/g_admin.c')
-rw-r--r-- | src/game/g_admin.c | 5139 |
1 files changed, 5139 insertions, 0 deletions
diff --git a/src/game/g_admin.c b/src/game/g_admin.c new file mode 100644 index 0000000..ab7f259 --- /dev/null +++ b/src/game/g_admin.c @@ -0,0 +1,5139 @@ +/*
+===========================================================================
+Copyright (C) 2004-2006 Tony J. White
+
+This file is part of Tremulous.
+
+This shrubbot implementation is the original work of Tony J. White.
+
+Contains contributions from Wesley van Beelen, Chris Bajumpaa, Josh Menke,
+and Travis Maurer.
+
+The functionality of this code mimics the behaviour of the currently
+inactive project shrubet (http://www.etstats.com/shrubet/index.php?ver=2)
+by Ryan Mannion. However, shrubet was a closed-source project and
+none of it's code has been copied, only it's functionality.
+
+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"
+
+// big ugly global buffer for use with buffered printing of long outputs
+static char g_bfb[ 32000 ];
+
+// note: list ordered alphabetically
+g_admin_cmd_t g_admin_cmds[ ] =
+ {
+ {"adjustban", G_admin_adjustban, "b",
+ "change the duration or reason of a ban. time is specified as numbers "
+ "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes),"
+ " or seconds if no units are specified",
+ "[^3ban#^7] (^5time^7) (^5reason^7)"
+ },
+
+ {"admintest", G_admin_admintest, "a",
+ "display your current admin level",
+ ""
+ },
+
+ {"allowbuild", G_admin_denybuild, "d",
+ "restore a player's ability to build",
+ "[^3name|slot#^7]"
+ },
+
+ {"allready", G_admin_allready, "y",
+ "makes everyone ready in intermission",
+ ""
+ },
+
+ {"ban", G_admin_ban, "b",
+ "ban a player by IP and GUID with an optional expiration time and reason."
+ " time is specified as numbers followed by units 'w' (weeks), 'd' "
+ "(days), 'h' (hours) or 'm' (minutes), or seconds if no units are "
+ "specified",
+ "[^3name|slot#|IP^7] (^5time^7) (^5reason^7)"
+ },
+
+ {"buildlog", G_admin_buildlog, "U",
+ "display a list of recent builds and deconstructs, optionally specifying"
+ " a team",
+ "(^5xnum^7) (^5#skip^7) (^5-name|num^7) (^5a|h^7)"
+ "\n ^3Example:^7 '!buildlog #10 h' skips 10 events, then shows the previous 10 events affecting human buildables"
+ },
+
+ {"cancelvote", G_admin_cancelvote, "c",
+ "cancel a vote taking place",
+ ""
+ },
+
+ {"cp", G_admin_cp, "Z",
+ "display a CP message to users, optionally specifying team(s) to send to",
+ "(-AHS) [^3message^7]"
+ },
+
+ {"denybuild", G_admin_denybuild, "d",
+ "take away a player's ability to build",
+ "[^3name|slot#^7]"
+ },
+
+ {"designate", G_admin_designate, "g",
+ "give the player designated builder privileges",
+ "[^3name|slot#^7]"
+ },
+
+ {"devmap", G_admin_devmap, "L",
+ "load a map with cheats (and optionally force layout)",
+ "[^3mapname^7] (^5layout^7)"
+ },
+
+ {"help", G_admin_help, "h",
+ "display commands available to you or help on a specific command",
+ "(^5command^7)"
+ },
+
+ {"info", G_admin_info, "H",
+ "display the contents of server info files",
+ "(^5subject^7)"
+ },
+
+ {"kick", G_admin_kick, "k",
+ "kick a player with an optional reason",
+ "[^3name|slot#^7] (^5reason^7)"
+ },
+
+ {"L0", G_admin_L0, "l",
+ "Sets a level 1 to level 0",
+ "[^3name|slot#^7]"
+ },
+
+ {"L1", G_admin_L1, "l",
+ "Sets a level 0 to level 1",
+ "[^3name|slot#^7]"
+ },
+
+ {"layoutsave", G_admin_layoutsave, "L",
+ "save a map layout",
+ "[^3mapname^7]"
+ },
+
+ {"listadmins", G_admin_listadmins, "D",
+ "display a list of all server admins and their levels",
+ "(^5name|start admin#^7) (^5minimum level to display^7)"
+ },
+
+ {"listlayouts", G_admin_listlayouts, "L",
+ "display a list of all available layouts for a map",
+ "(^5mapname^7)"
+ },
+
+ {"listplayers", G_admin_listplayers, "i",
+ "display a list of players, their client numbers and their levels",
+ ""
+ },
+
+ {"listmaps", G_admin_listmaps, "j",
+ "display a list of available maps on the server",
+ "(^5map name^7)"
+ },
+
+ {"lock", G_admin_lock, "K",
+ "lock a team to prevent anyone from joining it",
+ "[^3a|h^7]"
+ },
+
+ {"map", G_admin_map, "M",
+ "load a map (and optionally force layout)",
+ "[^3mapname^7] (^5layout^7)"
+ },
+
+ {"maplog", G_admin_maplog, "o",
+ "show recently played maps",
+ ""
+ },
+
+ {"mute", G_admin_mute, "m",
+ "mute a player",
+ "[^3name|slot#^7]"
+ },
+
+ {"namelog", G_admin_namelog, "e",
+ "display a list of names used by recently connected players",
+ "(^5name^7)"
+ },
+
+ {"nextmap", G_admin_nextmap, "n",
+ "go to the next map in the cycle",
+ ""
+ },
+
+ {"passvote", G_admin_passvote, "V",
+ "pass a vote currently taking place",
+ ""
+ },
+
+ {"pause", G_admin_pause, "S",
+ "Pause (or unpause) the game.",
+ ""
+ },
+
+
+ {"putteam", G_admin_putteam, "p",
+ "move a player to a specified team",
+ "[^3name|slot#^7] [^3h|a|s^7]"
+ },
+
+ {"readconfig", G_admin_readconfig, "G",
+ "reloads the admin config file and refreshes permission flags",
+ ""
+ },
+
+ {"register", G_admin_register, "R",
+ "Registers your name to protect it from being used by others or updates your admin name to your current name.",
+ ""
+ },
+
+ {"rename", G_admin_rename, "N",
+ "rename a player",
+ "[^3name|slot#^7] [^3new name^7]"
+ },
+
+ {"restart", G_admin_restart, "r",
+ "restart the current map (optionally using named layout or keeping/switching teams)",
+ "(^5layout^7) (^5keepteams|switchteams|keepteamslock|switchteamslock^7)"
+ },
+
+ {"revert", G_admin_revert, "v",
+ "revert one or more buildlog events, optionally of only one team",
+ "(^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)"
+ "\n ^3Example:^7 '!revert x5 h' reverts the last 5 events affecting human buildables"
+ },
+
+ {"rotation", G_admin_listrotation, "j",
+ "display a list of maps that are in the active map rotation",
+ ""
+ },
+
+ {"setlevel", G_admin_setlevel, "s",
+ "sets the admin level of a player",
+ "[^3name|slot#|admin#^7] [^3level^7]"
+ },
+
+ {"showbans", G_admin_showbans, "B",
+ "display a (partial) list of active bans",
+ "(^5start at ban#^7) (^5name|IP|'-subnet'^7)"
+ },
+
+ {"spec999", G_admin_spec999, "P",
+ "move 999 pingers to the spectator team",
+ ""},
+
+ //kev: a bit of a hack, but there is no real point to
+ //creating a new admin flag for this, so i stole it from !help
+ {"specme", G_admin_putmespec, "h",
+ "moves you to the spectators",
+ ""
+ },
+
+ {"subnetban", G_admin_subnetban, "E",
+ "Add or change a subnet mask on a ban",
+ "[^3ban#^7] [^5CIDR mask^7]"
+ "\n ^3Example:^7 '!subnetban 10 16' changes ban #10 to be a ban on XXX.XXX.*.*"
+ "\n ^3Example:^7 '!subnetban 10 24' changes ban #10 to be a ban on XXX.XXX.XXX.*"
+ "\n ^3Example:^7 '!subnetban 10 32' changes ban #10 to be a regular (non-subnet) ban"
+ "\n ^1WARNING:^7 Use of this command may make your admin.dat incompatible with other game.qvms"
+ },
+
+ {"time", G_admin_time, "C",
+ "show the current local server time",
+ ""},
+
+ {"unban", G_admin_unban, "b",
+ "unbans a player specified by the slot as seen in showbans",
+ "[^3ban#^7]"
+ },
+
+ {"undesignate", G_admin_designate, "g",
+ "revoke designated builder privileges",
+ "[^3name|slot#^7]"
+ },
+
+ {"unlock", G_admin_unlock, "K",
+ "unlock a locked team",
+ "[^3a|h^7]"
+ },
+
+ {"unmute", G_admin_mute, "m",
+ "unmute a muted player",
+ "[^3name|slot#^7]"
+ },
+
+ {
+ "warn", G_admin_warn, "w",
+ "Warn a player to cease or face admin intervention",
+ "[^3name|slot#^7] [reason]"
+ }
+ };
+
+static int adminNumCmds = sizeof( g_admin_cmds ) / sizeof( g_admin_cmds[ 0 ] );
+
+static int admin_level_maxname = 0;
+g_admin_level_t *g_admin_levels[ MAX_ADMIN_LEVELS ];
+g_admin_admin_t *g_admin_admins[ MAX_ADMIN_ADMINS ];
+g_admin_ban_t *g_admin_bans[ MAX_ADMIN_BANS ];
+g_admin_command_t *g_admin_commands[ MAX_ADMIN_COMMANDS ];
+g_admin_namelog_t *g_admin_namelog[ MAX_ADMIN_NAMELOGS ];
+
+// This function should only be used directly when the client is connecting and thus has no GUID.
+// Else, use G_admin_permission()
+qboolean G_admin_permission_guid( char *guid, char flag )
+{
+ int i;
+ int l = 0;
+ char *flags;
+
+ if(!guid) return qfalse; // since there is a different check for console, here we are just returning false.
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) )
+ {
+ flags = g_admin_admins[ i ]->flags;
+ while( *flags )
+ {
+ if( *flags == flag )
+ return qtrue;
+ else if( *flags == '-' )
+ {
+ while( *flags++ )
+ {
+ if( *flags == flag )
+ return qfalse;
+ else if( *flags == '+' )
+ break;
+ }
+ }
+ else if( *flags == '*' )
+ {
+ while( *flags++ )
+ {
+ if( *flags == flag )
+ return qfalse;
+ }
+ // flags with significance only for individuals (
+ // like ADMF_INCOGNITO and ADMF_IMMUTABLE are NOT covered
+ // by the '*' wildcard. They must be specified manually.
+ switch( flag )
+ {
+ case ADMF_INCOGNITO:
+ case ADMF_IMMUTABLE:
+ case ADMF_DBUILDER:
+ return qfalse;
+ default:
+ return qtrue;
+ }
+ }
+ flags++;
+ }
+ l = g_admin_admins[ i ]->level;
+ }
+ }
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ if( g_admin_levels[ i ]->level == l )
+ {
+ flags = g_admin_levels[ i ]->flags;
+ while( *flags )
+ {
+ if( *flags == flag )
+ return qtrue;
+ if( *flags == '*' )
+ {
+ while( *flags++ )
+ {
+ if( *flags == flag )
+ return qfalse;
+ }
+ // flags with significance only for individuals (
+ // like ADMF_INCOGNITO and ADMF_IMMUTABLE are NOT covered
+ // by the '*' wildcard. They must be specified manually.
+ switch( flag )
+ {
+ case ADMF_INCOGNITO:
+ case ADMF_IMMUTABLE:
+ case ADMF_DBUILDER:
+ return qfalse;
+ default:
+ return qtrue;
+ }
+ }
+ flags++;
+ }
+ }
+ }
+ return qfalse;
+}
+
+
+qboolean G_admin_permission( gentity_t *ent, char flag )
+{
+ if(!ent) return qtrue; //console always wins
+
+ return G_admin_permission_guid(ent->client->pers.guid, flag);
+}
+
+qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len )
+{
+ int i;
+ gclient_t *client;
+ char testName[ MAX_NAME_LENGTH ] = {""};
+ char name2[ MAX_NAME_LENGTH ] = {""};
+ int alphaCount = 0;
+
+ G_SanitiseString( name, name2, sizeof( name2) );
+
+ if( !Q_stricmp( name2, "UnnamedPlayer" ) )
+ return qtrue;
+
+ if( !Q_stricmp( name2, "console" ) )
+ {
+ Q_strncpyz( err, va( "The name '%s^7' is invalid here", name2 ),
+ len );
+ return qfalse;
+ }
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ client = &level.clients[ i ];
+ if( client->pers.connected != CON_CONNECTING
+ && client->pers.connected != CON_CONNECTED )
+ {
+ continue;
+ }
+
+ // can rename ones self to the same name using different colors
+ if( i == ( ent - g_entities ) )
+ continue;
+
+ G_SanitiseString( client->pers.netname, testName, sizeof( testName) );
+ if( !Q_stricmp( name2, testName ) )
+ {
+ Q_strncpyz( err, va( "The name '%s^7' is already in use", name ),
+ len );
+ return qfalse;
+ }
+ }
+
+ if( Q_isdigit( name2[ 0 ] ) || name2[ 0 ] == '-' )
+ {
+ Q_strncpyz( err, "Names cannot begin with a number or with a dash. Please choose another.", len );
+ return qfalse;
+ }
+
+ for( i = 0; name2[ i ] !='\0'; i++)
+ {
+ if( Q_isalpha( name2[ i ] ) )
+ alphaCount++;
+
+ if( name2[ i ] == ' ' )
+ {
+ if( name2[ i + 1 ] == '-' )
+ {
+ Q_strncpyz( err, "Names cannot contain a - preceded by a space. Please choose another.", len );
+ return qfalse;
+ }
+ }
+ }
+
+ if( alphaCount == 0 )
+ {
+ Q_strncpyz( err, va( "The name '%s^7' does not include at least one letter. Please choose another.", name ), len );
+ return qfalse;
+ }
+
+ if( !g_adminNameProtect.string[ 0 ] )
+ return qtrue;
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( g_admin_admins[ i ]->level < 1 )
+ continue;
+ G_SanitiseString( g_admin_admins[ i ]->name, testName, sizeof( testName) );
+ if( !Q_stricmp( name2, testName ) &&
+ Q_stricmp( ent->client->pers.guid, g_admin_admins[ i ]->guid ) )
+ {
+ Q_strncpyz( err, va( "The name '%s^7' belongs to an admin. "
+ "Please choose another.", name ), len );
+ return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+static qboolean admin_higher_guid( char *admin_guid, char *victim_guid )
+{
+ int i;
+ int alevel = 0;
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( admin_guid, g_admin_admins[ i ]->guid ) )
+ {
+ alevel = g_admin_admins[ i ]->level;
+ break;
+ }
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( victim_guid, g_admin_admins[ i ]->guid ) )
+ {
+ if( alevel < g_admin_admins[ i ]->level )
+ return qfalse;
+ if( strstr( g_admin_admins[ i ]->flags, va( "%c", ADMF_IMMUTABLE ) ) )
+ return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+static qboolean admin_higher( gentity_t *admin, gentity_t *victim )
+{
+
+ // console always wins
+ if( !admin )
+ return qtrue;
+ // just in case
+ if( !victim )
+ return qtrue;
+
+ return admin_higher_guid( admin->client->pers.guid,
+ victim->client->pers.guid );
+}
+
+static void admin_writeconfig_string( char *s, fileHandle_t f )
+{
+ char buf[ MAX_STRING_CHARS ];
+
+ buf[ 0 ] = '\0';
+ if( s[ 0 ] )
+ {
+ //Q_strcat(buf, sizeof(buf), s);
+ Q_strncpyz( buf, s, sizeof( buf ) );
+ trap_FS_Write( buf, strlen( buf ), f );
+ }
+ trap_FS_Write( "\n", 1, f );
+}
+
+static void admin_writeconfig_int( int v, fileHandle_t f )
+{
+ char buf[ 32 ];
+
+ Com_sprintf( buf, sizeof(buf), "%d", v );
+ if( buf[ 0 ] )
+ trap_FS_Write( buf, strlen( buf ), f );
+ trap_FS_Write( "\n", 1, f );
+}
+
+static void admin_writeconfig( void )
+{
+ fileHandle_t f;
+ int len, i, j;
+ int t;
+ char levels[ MAX_STRING_CHARS ] = {""};
+
+ if( !g_admin.string[ 0 ] )
+ {
+ G_Printf( S_COLOR_YELLOW "WARNING: g_admin is not set. "
+ " configuration will not be saved to a file.\n" );
+ return;
+ }
+ t = trap_RealTime( NULL );
+ len = trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE );
+ if( len < 0 )
+ {
+ G_Printf( "admin_writeconfig: could not open g_admin file \"%s\"\n",
+ g_admin.string );
+ return;
+ }
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ trap_FS_Write( "[level]\n", 8, f );
+ trap_FS_Write( "level = ", 10, f );
+ admin_writeconfig_int( g_admin_levels[ i ]->level, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( g_admin_levels[ i ]->name, f );
+ trap_FS_Write( "flags = ", 10, f );
+ admin_writeconfig_string( g_admin_levels[ i ]->flags, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ // don't write level 0 users
+ if( g_admin_admins[ i ]->level < 1 )
+ continue;
+
+ trap_FS_Write( "[admin]\n", 8, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( g_admin_admins[ i ]->name, f );
+ trap_FS_Write( "guid = ", 10, f );
+ admin_writeconfig_string( g_admin_admins[ i ]->guid, f );
+ trap_FS_Write( "level = ", 10, f );
+ admin_writeconfig_int( g_admin_admins[ i ]->level, f );
+ trap_FS_Write( "flags = ", 10, f );
+ admin_writeconfig_string( g_admin_admins[ i ]->flags, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ {
+ // don't write expired bans
+ // if expires is 0, then it's a perm ban
+ if( g_admin_bans[ i ]->expires != 0 &&
+ ( g_admin_bans[ i ]->expires - t ) < 1 )
+ continue;
+
+ trap_FS_Write( "[ban]\n", 6, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->name, f );
+ trap_FS_Write( "guid = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->guid, f );
+ trap_FS_Write( "ip = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->ip, f );
+ trap_FS_Write( "reason = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->reason, f );
+ trap_FS_Write( "made = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->made, f );
+ trap_FS_Write( "expires = ", 10, f );
+ admin_writeconfig_int( g_admin_bans[ i ]->expires, f );
+ trap_FS_Write( "banner = ", 10, f );
+ admin_writeconfig_string( g_admin_bans[ i ]->banner, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ levels[ 0 ] = '\0';
+ trap_FS_Write( "[command]\n", 10, f );
+ trap_FS_Write( "command = ", 10, f );
+ admin_writeconfig_string( g_admin_commands[ i ]->command, f );
+ trap_FS_Write( "exec = ", 10, f );
+ admin_writeconfig_string( g_admin_commands[ i ]->exec, f );
+ trap_FS_Write( "desc = ", 10, f );
+ admin_writeconfig_string( g_admin_commands[ i ]->desc, f );
+ trap_FS_Write( "levels = ", 10, f );
+ for( j = 0; g_admin_commands[ i ]->levels[ j ] != -1; j++ )
+ {
+ Q_strcat( levels, sizeof( levels ),
+ va( "%i ", g_admin_commands[ i ]->levels[ j ] ) );
+ }
+ admin_writeconfig_string( levels, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ trap_FS_FCloseFile( f );
+}
+
+static void admin_readconfig_string( char **cnf, char *s, int size )
+{
+ char * t;
+
+ //COM_MatchToken(cnf, "=");
+ t = COM_ParseExt( cnf, qfalse );
+ if( !strcmp( t, "=" ) )
+ {
+ t = COM_ParseExt( cnf, qfalse );
+ }
+ else
+ {
+ G_Printf( "readconfig: warning missing = before "
+ "\"%s\" on line %d\n",
+ t,
+ COM_GetCurrentParseLine() );
+ }
+ s[ 0 ] = '\0';
+ while( t[ 0 ] )
+ {
+ if( ( s[ 0 ] == '\0' && strlen( t ) <= size )
+ || ( strlen( t ) + strlen( s ) < size ) )
+ {
+
+ Q_strcat( s, size, t );
+ Q_strcat( s, size, " " );
+ }
+ t = COM_ParseExt( cnf, qfalse );
+ }
+ // trim the trailing space
+ if( strlen( s ) > 0 && s[ strlen( s ) - 1 ] == ' ' )
+ s[ strlen( s ) - 1 ] = '\0';
+}
+
+static void admin_readconfig_int( char **cnf, int *v )
+{
+ char * t;
+
+ //COM_MatchToken(cnf, "=");
+ t = COM_ParseExt( cnf, qfalse );
+ if( !strcmp( t, "=" ) )
+ {
+ t = COM_ParseExt( cnf, qfalse );
+ }
+ else
+ {
+ G_Printf( "readconfig: warning missing = before "
+ "\"%s\" on line %d\n",
+ t,
+ COM_GetCurrentParseLine() );
+ }
+ *v = atoi( t );
+}
+
+// if we can't parse any levels from readconfig, set up default
+// ones to make new installs easier for admins
+static void admin_default_levels( void )
+{
+ g_admin_level_t * l;
+ int i;
+
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ G_Free( g_admin_levels[ i ] );
+ g_admin_levels[ i ] = NULL;
+ }
+ for( i = 0; i <= 5; i++ )
+ {
+ l = G_Alloc( sizeof( g_admin_level_t ) );
+ l->level = i;
+ *l->name = '\0';
+ *l->flags = '\0';
+ g_admin_levels[ i ] = l;
+ }
+ Q_strncpyz( g_admin_levels[ 0 ]->name, "^4Unknown Player",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 0 ]->flags, "iahC", sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 1 ]->name, "^5Server Regular",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 1 ]->flags, "iahC", sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 2 ]->name, "^6Team Manager",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 2 ]->flags, "iahCpPwd", sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 3 ]->name, "^2Junior Admin",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 3 ]->flags, "iahCpPkmwd?$", sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 4 ]->name, "^3Senior Admin",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 4 ]->flags, "iahCpPkmBbewd?$", sizeof( l->flags ) );
+
+ Q_strncpyz( g_admin_levels[ 5 ]->name, "^1Server Operator",
+ sizeof( l->name ) );
+ Q_strncpyz( g_admin_levels[ 5 ]->flags, "*", sizeof( l->flags ) );
+}
+
+// return a level for a player entity.
+int G_admin_level( gentity_t *ent )
+{
+ int i;
+ qboolean found = qfalse;
+
+ if( !ent )
+ {
+ return MAX_ADMIN_LEVELS;
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ {
+
+ found = qtrue;
+ break;
+ }
+ }
+
+ if( found )
+ {
+ return g_admin_admins[ i ]->level;
+ }
+
+ return 0;
+}
+
+// set a player's adminname
+void G_admin_set_adminname( gentity_t *ent )
+{
+ int i;
+ qboolean found = qfalse;
+
+ if( !ent )
+ {
+ return;
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ {
+ found = qtrue;
+ break;
+ }
+ }
+
+ if( found )
+ {
+ Q_strncpyz( ent->client->pers.adminName, g_admin_admins[ i ]->name, sizeof( ent->client->pers.adminName ) );
+ }
+ else
+ {
+ Q_strncpyz( ent->client->pers.adminName, "", sizeof( ent->client->pers.adminName ) );
+ }
+}
+
+char* G_admin_adminPrintName( gentity_t *ent )
+{
+ char *out;
+
+ if( !ent->client->pers.adminLevel )
+ {
+ out = "";
+ return out;
+ }
+
+ if( G_admin_permission( ent, ADMF_ADMINSTEALTH ) )
+ {
+ out = ent->client->pers.adminName;
+ }
+ else
+ {
+ out = ent->client->pers.netname;
+ }
+
+
+ return out;
+}
+
+static qboolean admin_command_permission( gentity_t *ent, char *command )
+{
+ int i, j;
+ int level;
+
+ if( !ent )
+ return qtrue;
+ level = ent->client->pers.adminLevel;
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ if( !Q_stricmp( command, g_admin_commands[ i ]->command ) )
+ {
+ for( j = 0; g_admin_commands[ i ]->levels[ j ] != -1; j++ )
+ {
+ if( g_admin_commands[ i ]->levels[ j ] == level )
+ {
+ return qtrue;
+ }
+ }
+ }
+ }
+ return qfalse;
+}
+
+static void admin_log( gentity_t *admin, char *cmd, int skiparg )
+{
+ fileHandle_t f;
+ int len, i, j;
+ char string[ MAX_STRING_CHARS ], decoloured[ MAX_STRING_CHARS ];
+ int min, tens, sec;
+ g_admin_admin_t *a;
+ g_admin_level_t *l;
+ char flags[ MAX_ADMIN_FLAGS * 2 ];
+ gentity_t *victim = NULL;
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ];
+
+ if( !g_adminLog.string[ 0 ] )
+ return ;
+
+
+ len = trap_FS_FOpenFile( g_adminLog.string, &f, FS_APPEND );
+ if( len < 0 )
+ {
+ G_Printf( "admin_log: error could not open %s\n", g_adminLog.string );
+ return ;
+ }
+
+ sec = (level.time - level.startTime) / 1000;
+ min = sec / 60;
+ sec -= min * 60;
+ tens = sec / 10;
+ sec -= tens * 10;
+
+ *flags = '\0';
+ if( admin )
+ {
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid , admin->client->pers.guid ) )
+ {
+
+ a = g_admin_admins[ i ];
+ Q_strncpyz( flags, a->flags, sizeof( flags ) );
+ for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
+ {
+ if( g_admin_levels[ j ]->level == a->level )
+ {
+ l = g_admin_levels[ j ];
+ Q_strcat( flags, sizeof( flags ), l->flags );
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if( G_SayArgc() > 1 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) == 1 )
+ {
+ victim = &g_entities[ pids[ 0 ] ];
+ }
+ }
+
+ if( victim && Q_stricmp( cmd, "attempted" ) )
+ {
+ Com_sprintf( string, sizeof( string ),
+ "%3i:%i%i: %i: %s: %s (%s): %s: %s: %s: %s: \"%s\"\n",
+ min,
+ tens,
+ sec,
+ ( admin ) ? admin->s.clientNum : -1,
+ ( admin ) ? admin->client->pers.guid
+ : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ ( admin ) ? admin->client->pers.netname : "console",
+ ( admin ) ? admin->client->pers.adminName : "console",
+ flags,
+ cmd,
+ victim->client->pers.guid,
+ victim->client->pers.netname,
+ G_SayConcatArgs( 2 + skiparg ) );
+ }
+ else
+ {
+ Com_sprintf( string, sizeof( string ),
+ "%3i:%i%i: %i: %s: %s (%s): %s: %s: \"%s\"\n",
+ min,
+ tens,
+ sec,
+ ( admin ) ? admin->s.clientNum : -1,
+ ( admin ) ? admin->client->pers.guid
+ : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+ ( admin ) ? admin->client->pers.netname : "console",
+ ( admin ) ? admin->client->pers.adminName : "console",
+ flags,
+ cmd,
+ G_SayConcatArgs( 1 + skiparg ) );
+ }
+
+ if( g_decolourLogfiles.integer )
+ {
+ G_DecolorString( string, decoloured );
+ trap_FS_Write( decoloured, strlen( decoloured ), f );
+ }
+ else
+ {
+ trap_FS_Write( string, strlen( string ), f );
+ }
+ trap_FS_FCloseFile( f );
+
+ if ( !Q_stricmp( cmd, "attempted" ) )
+ {
+ Com_sprintf( string, sizeof( string ),
+ "%s^7 (%i) %s: %s",
+ ( admin ) ? admin->client->pers.netname : "console",
+ ( admin ) ? admin->s.clientNum : -1,
+ cmd,
+ G_SayConcatArgs( 1 + skiparg ) );
+ G_AdminsPrintf("%s\n",string);
+ }
+
+ G_LogPrintf("Admin Command: %s^7 (%s): %s %s\n",( admin ) ? admin->client->pers.netname : "console", ( admin ) ? admin->client->pers.adminName : "console", cmd, G_SayConcatArgs( 1 + skiparg ));
+}
+
+static int admin_listadmins( gentity_t *ent, int start, char *search, int minlevel )
+{
+ int drawn = 0;
+ char guid_stub[9];
+ char name[ MAX_NAME_LENGTH ] = {""};
+ char name2[ MAX_NAME_LENGTH ] = {""};
+ char lname[ MAX_NAME_LENGTH ] = {""};
+ char lname_fmt[ 5 ];
+ int i,j;
+ gentity_t *vic;
+ int l = 0;
+ qboolean dup = qfalse;
+
+ ADMBP_begin();
+
+ // print out all connected players regardless of level if name searching
+ for( i = 0; i < level.maxclients && search[ 0 ]; i++ )
+ {
+ vic = &g_entities[ i ];
+
+ if( vic->client && vic->client->pers.connected != CON_CONNECTED )
+ continue;
+
+ l = vic->client->pers.adminLevel;
+
+ G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) );
+ if( !strstr( name, search ) )
+ continue;
+
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = vic->client->pers.guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+
+ lname[ 0 ] = '\0';
+ Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
+ for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
+ {
+ if( g_admin_levels[ j ]->level == l )
+ {
+ G_DecolorString( g_admin_levels[ j ]->name, lname );
+ Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
+ ( admin_level_maxname + strlen( g_admin_levels[ j ]->name )
+ - strlen( lname ) ) );
+ Com_sprintf( lname, sizeof( lname ), lname_fmt,
+ g_admin_levels[ j ]->name );
+ break;
+ }
+ }
+ ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n",
+ i,
+ l,
+ lname,
+ guid_stub,
+ vic->client->pers.netname ) );
+ drawn++;
+ }
+
+ for( i = start; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]
+ && drawn < MAX_ADMIN_LISTITEMS; i++ )
+ if( g_admin_admins[ i ]->level >= minlevel )
+ {
+ if( search[ 0 ] )
+ {
+ G_SanitiseString( g_admin_admins[ i ]->name, name, sizeof( name ) );
+ if( !strstr( name, search ) )
+ continue;
+
+ // verify we don't have the same guid/name pair in connected players
+ // since we don't want to draw the same player twice
+ dup = qfalse;
+ for( j = 0; j < level.maxclients; j++ )
+ {
+ vic = &g_entities[ j ];
+ if( !vic->client || vic->client->pers.connected != CON_CONNECTED )
+ continue;
+ G_SanitiseString( vic->client->pers.netname, name2, sizeof( name2) );
+ if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid )
+ && strstr( name2, search ) )
+ {
+ dup = qtrue;
+ break;
+ }
+ }
+ if( dup )
+ continue;
+ }
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = g_admin_admins[ i ]->guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+
+ lname[ 0 ] = '\0';
+ Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
+ for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
+ {
+ if( g_admin_levels[ j ]->level == g_admin_admins[ i ]->level )
+ {
+ G_DecolorString( g_admin_levels[ j ]->name, lname );
+ Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
+ ( admin_level_maxname + strlen( g_admin_levels[ j ]->name )
+ - strlen( lname ) ) );
+ Com_sprintf( lname, sizeof( lname ), lname_fmt,
+ g_admin_levels[ j ]->name );
+ break;
+ }
+ }
+ ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n",
+ ( i + MAX_CLIENTS ),
+ g_admin_admins[ i ]->level,
+ lname,
+ guid_stub,
+ g_admin_admins[ i ]->name ) );
+ drawn++;
+ }
+ ADMBP_end();
+ return drawn;
+}
+
+void G_admin_duration( int secs, char *duration, int dursize )
+{
+
+ if( secs > ( 60 * 60 * 24 * 365 * 50 ) || secs < 0 )
+ Q_strncpyz( duration, "PERMANENT", dursize );
+ else if( secs >= ( 60 * 60 * 24 * 365 ) )
+ Com_sprintf( duration, dursize, "%1.1f years",
+ ( secs / ( 60 * 60 * 24 * 365.0f ) ) );
+ else if( secs >= ( 60 * 60 * 24 * 90 ) )
+ Com_sprintf( duration, dursize, "%1.1f weeks",
+ ( secs / ( 60 * 60 * 24 * 7.0f ) ) );
+ else if( secs >= ( 60 * 60 * 24 ) )
+ Com_sprintf( duration, dursize, "%1.1f days",
+ ( secs / ( 60 * 60 * 24.0f ) ) );
+ else if( secs >= ( 60 * 60 ) )
+ Com_sprintf( duration, dursize, "%1.1f hours",
+ ( secs / ( 60 * 60.0f ) ) );
+ else if( secs >= 60 )
+ Com_sprintf( duration, dursize, "%1.1f minutes",
+ ( secs / 60.0f ) );
+ else
+ Com_sprintf( duration, dursize, "%i seconds", secs );
+}
+
+qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen )
+{
+ //Problem ip 213.168.183.24
+
+ static char lastConnectIP[ 16 ] = {""};
+ static int lastConnectTime = 0;
+ char guid[ 33 ];
+ char ip[ 16 ];
+ char *value;
+ int i;
+ int userIP = 0, intIP = 0, IP[5], k, tempIP, mask, ipscanfcount;
+ int t;
+ char notice[51];
+ qboolean ignoreIP = qfalse;
+
+ trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) );
+
+ *reason = '\0';
+
+ if( !*userinfo )
+ return qfalse;
+
+ value = Info_ValueForKey( userinfo, "ip" );
+ Q_strncpyz( ip, value, sizeof( ip ) );
+ // strip port
+ value = strchr( ip, ':' );
+ if ( value )
+ *value = '\0';
+ if( !*ip )
+ return qfalse;
+ value = Info_ValueForKey( userinfo, "cl_guid" );
+ Q_strncpyz( guid, value, sizeof( guid ) );
+ //rotax start
+ //level 3+ cant be banned, even if his ip is banned
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( *guid && !Q_stricmp( g_admin_admins[ i ]->guid, guid ) && g_admin_admins[ i ]->level > 2 )
+ {
+ return qfalse;
+ }
+ }
+ //rotax end
+ t = trap_RealTime( NULL );
+ sscanf(ip, "%i.%i.%i.%i", &IP[4], &IP[3], &IP[2], &IP[1]);
+ for(k = 4; k >= 1; k--)
+ {
+ if(!IP[k]) continue;
+ userIP |= IP[k] << 8*(k-1);
+ }
+ ignoreIP = G_admin_permission_guid( guid , 'W');
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ {
+ // 0 is for perm ban
+ if( g_admin_bans[ i ]->expires != 0 &&
+ ( g_admin_bans[ i ]->expires - t ) < 1 )
+ continue;
+ if( !ignoreIP )
+ {
+ tempIP = userIP;
+ intIP = 0;
+ mask = -1;
+
+ ipscanfcount = sscanf(g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &IP[4], &IP[3], &IP[2], &IP[1], &IP[0]);
+
+ if(ipscanfcount==5) mask = IP[0];
+ else mask = -1;
+
+ for(k = 4; k >= 1; k--)
+ {
+ if(!IP[k]) continue;
+ intIP |= IP[k] << 8*(k-1);
+ }
+
+ if(mask > 0 && mask <= 32)
+ {
+ tempIP &= ~((1 << (32-mask)) - 1); // FIXME: can overflow
+ intIP &= ~((1 << (32-mask)) - 1);
+ }
+
+ if( intIP == tempIP || mask == 0 )
+ {
+ char duration[ 32 ];
+ G_admin_duration( ( g_admin_bans[ i ]->expires - t ),
+ duration, sizeof( duration ) );
+
+ // flood protected
+ if( t - lastConnectTime >= 300 ||
+ Q_stricmp( lastConnectIP, ip ) )
+ {
+ lastConnectTime = t;
+ Q_strncpyz( lastConnectIP, ip, sizeof( lastConnectIP ) );
+
+ G_AdminsPrintf(
+ "Banned player %s^7 (%s^7) tried to connect (ban #%i on %s by %s^7 expires %s reason: %s^7 )\n",
+ Info_ValueForKey( userinfo, "name" ),
+ g_admin_bans[ i ]->name,
+ i+1,
+ ip,
+ g_admin_bans[ i ]->banner,
+ duration,
+ g_admin_bans[ i ]->reason );
+ }
+
+ Com_sprintf(
+ reason,
+ rlen,
+ "You have been banned by %s^7 reason: %s^7 expires: %s %s",
+ g_admin_bans[ i ]->banner,
+ g_admin_bans[ i ]->reason,
+ duration,
+ notice
+ );
+ G_LogPrintf("Banned player tried to connect from IP %s\n", ip);
+ return qtrue;
+ }
+ }
+ if( *guid && !Q_stricmp( g_admin_bans[ i ]->guid, guid ) )
+ {
+ char duration[ 32 ];
+ G_admin_duration( ( g_admin_bans[ i ]->expires - t ),
+ duration, sizeof( duration ) );
+ Com_sprintf(
+ reason,
+ rlen,
+ "You have been banned by %s^7 reason: %s^7 expires: %s",
+ g_admin_bans[ i ]->banner,
+ g_admin_bans[ i ]->reason,
+ duration
+ );
+ G_Printf("Banned player tried to connect with GUID %s\n", guid);
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+qboolean G_admin_cmd_check( gentity_t *ent, qboolean say )
+{
+ int i;
+ char command[ MAX_ADMIN_CMD_LEN ];
+ char *cmd;
+ int skip = 0;
+
+ command[ 0 ] = '\0';
+ G_SayArgv( 0, command, sizeof( command ) );
+ if( !Q_stricmp( command, "say" ) ||
+ ( G_admin_permission( ent, ADMF_TEAMCHAT_CMD ) &&
+ ( !Q_stricmp( command, "say_team" ) ) ) )
+ {
+ skip = 1;
+ G_SayArgv( 1, command, sizeof( command ) );
+ }
+ if( !command[ 0 ] )
+ return qfalse;
+
+ if( command[ 0 ] == '!' )
+ {
+ cmd = &command[ 1 ];
+ }
+ else
+ {
+ return qfalse;
+ }
+
+ // Flood limit. If they're talking too fast, determine that and return.
+ if( g_floodMinTime.integer )
+ if ( G_Flood_Limited( ent ) )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" );
+ return qtrue;
+ }
+
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ if( Q_stricmp( cmd, g_admin_commands[ i ]->command ) )
+ continue;
+
+ if( admin_command_permission( ent, cmd ) )
+ {
+ trap_SendConsoleCommand( EXEC_APPEND, g_admin_commands[ i ]->exec );
+ admin_log( ent, cmd, skip );
+ }
+ else
+ {
+ ADMP( va( "^3!%s: ^7permission denied\n", g_admin_commands[ i ]->command ) );
+ admin_log( ent, "attempted", skip - 1 );
+ }
+ return qtrue;
+ }
+
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) )
+ continue;
+
+ if( G_admin_permission( ent, g_admin_cmds[ i ].flag[ 0 ] ) )
+ {
+ g_admin_cmds[ i ].handler( ent, skip );
+ admin_log( ent, cmd, skip );
+ }
+ else
+ {
+ ADMP( va( "^3!%s: ^7permission denied\n", g_admin_cmds[ i ].keyword ) );
+ admin_log( ent, "attempted", skip - 1 );
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+void G_admin_namelog_cleanup( )
+{
+ int i;
+
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ G_Free( g_admin_namelog[ i ] );
+ g_admin_namelog[ i ] = NULL;
+ }
+}
+
+void G_admin_namelog_update( gclient_t *client, qboolean disconnect )
+{
+ int i, j;
+ g_admin_namelog_t *namelog;
+ char n1[ MAX_NAME_LENGTH ];
+ char n2[ MAX_NAME_LENGTH ];
+ int clientNum = ( client - level.clients );
+
+ G_SanitiseString( client->pers.netname, n1, sizeof( n1 ) );
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ if( disconnect && g_admin_namelog[ i ]->slot != clientNum )
+ continue;
+
+ if( !disconnect && !( g_admin_namelog[ i ]->slot == clientNum ||
+ g_admin_namelog[ i ]->slot == -1 ) )
+ {
+ continue;
+ }
+
+ if( !Q_stricmp( client->pers.ip, g_admin_namelog[ i ]->ip )
+ && !Q_stricmp( client->pers.guid, g_admin_namelog[ i ]->guid ) )
+ {
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
+ && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( !Q_stricmp( n1, n2 ) )
+ break;
+ }
+ if( j == MAX_ADMIN_NAMELOG_NAMES )
+ j = MAX_ADMIN_NAMELOG_NAMES - 1;
+ Q_strncpyz( g_admin_namelog[ i ]->name[ j ], client->pers.netname,
+ sizeof( g_admin_namelog[ i ]->name[ j ] ) );
+ g_admin_namelog[ i ]->slot = ( disconnect ) ? -1 : clientNum;
+
+ g_admin_namelog[ i ]->ip2c = client->pers.ip2c;//ROTAXfun
+
+ // if this player is connecting, they are no longer banned
+ if( !disconnect )
+ g_admin_namelog[ i ]->banned = qfalse;
+
+ return;
+ }
+ }
+ if( i >= MAX_ADMIN_NAMELOGS )
+ {
+ G_Printf( "G_admin_namelog_update: warning, g_admin_namelogs overflow\n" );
+ return;
+ }
+ namelog = G_Alloc( sizeof( g_admin_namelog_t ) );
+ memset( namelog, 0, sizeof( namelog ) );
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES ; j++ )
+ namelog->name[ j ][ 0 ] = '\0';
+ Q_strncpyz( namelog->ip, client->pers.ip, sizeof( namelog->ip ) );
+ Q_strncpyz( namelog->guid, client->pers.guid, sizeof( namelog->guid ) );
+ Q_strncpyz( namelog->name[ 0 ], client->pers.netname,
+ sizeof( namelog->name[ 0 ] ) );
+ namelog->slot = ( disconnect ) ? -1 : clientNum;
+
+ namelog->ip2c = client->pers.ip2c;//ROTAXfun
+
+ g_admin_namelog[ i ] = namelog;
+}
+
+qboolean G_admin_readconfig( gentity_t *ent, int skiparg )
+{
+ g_admin_level_t * l = NULL;
+ g_admin_admin_t *a = NULL;
+ g_admin_ban_t *b = NULL;
+ g_admin_command_t *c = NULL;
+ int lc = 0, ac = 0, bc = 0, cc = 0;
+ fileHandle_t f;
+ int len;
+ char *cnf, *cnf2;
+ char *t;
+ qboolean level_open, admin_open, ban_open, command_open;
+ char levels[ MAX_STRING_CHARS ] = {""};
+ int i;
+
+ G_admin_cleanup();
+
+ if( !g_admin.string[ 0 ] )
+ {
+ ADMP( "^3!readconfig: g_admin is not set, not loading configuration "
+ "from a file\n" );
+ admin_default_levels();
+ return qfalse;
+ }
+
+ len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ ) ;
+ if( len < 0 )
+ {
+ ADMP( va( "^3!readconfig: ^7could not open admin config file %s\n",
+ g_admin.string ) );
+ admin_default_levels();
+ return qfalse;
+ }
+ cnf = G_Alloc( len + 1 );
+ cnf2 = cnf;
+ trap_FS_Read( cnf, len, f );
+ *( cnf + len ) = '\0';
+ trap_FS_FCloseFile( f );
+
+ t = COM_Parse( &cnf );
+ level_open = admin_open = ban_open = command_open = qfalse;
+ while( *t )
+ {
+ if( !Q_stricmp( t, "[level]" ) ||
+ !Q_stricmp( t, "[admin]" ) ||
+ !Q_stricmp( t, "[ban]" ) ||
+ !Q_stricmp( t, "[command]" ) )
+ {
+
+ if( level_open )
+ g_admin_levels[ lc++ ] = l;
+ else if( admin_open )
+ g_admin_admins[ ac++ ] = a;
+ else if( ban_open )
+ g_admin_bans[ bc++ ] = b;
+ else if( command_open )
+ g_admin_commands[ cc++ ] = c;
+ level_open = admin_open =
+ ban_open = command_open = qfalse;
+ }
+
+ if( level_open )
+ {
+ if( !Q_stricmp( t, "level" ) )
+ {
+ admin_readconfig_int( &cnf, &l->level );
+ }
+ else if( !Q_stricmp( t, "name" ) )
+ {
+ admin_readconfig_string( &cnf, l->name, sizeof( l->name ) );
+ }
+ else if( !Q_stricmp( t, "flags" ) )
+ {
+ admin_readconfig_string( &cnf, l->flags, sizeof( l->flags ) );
+ }
+ else
+ {
+ ADMP( va( "^3!readconfig: ^7[level] parse error near %s on line %d\n",
+ t,
+ COM_GetCurrentParseLine() ) );
+ }
+ }
+ else if( admin_open )
+ {
+ if( !Q_stricmp( t, "name" ) )
+ {
+ admin_readconfig_string( &cnf, a->name, sizeof( a->name ) );
+ }
+ else if( !Q_stricmp( t, "guid" ) )
+ {
+ admin_readconfig_string( &cnf, a->guid, sizeof( a->guid ) );
+ }
+ else if( !Q_stricmp( t, "level" ) )
+ {
+ admin_readconfig_int( &cnf, &a->level );
+ }
+ else if( !Q_stricmp( t, "flags" ) )
+ {
+ admin_readconfig_string( &cnf, a->flags, sizeof( a->flags ) );
+ }
+ else
+ {
+ ADMP( va( "^3!readconfig: ^7[admin] parse error near %s on line %d\n",
+ t,
+ COM_GetCurrentParseLine() ) );
+ }
+
+ }
+ else if( ban_open )
+ {
+ if( !Q_stricmp( t, "name" ) )
+ {
+ admin_readconfig_string( &cnf, b->name, sizeof( b->name ) );
+ }
+ else if( !Q_stricmp( t, "guid" ) )
+ {
+ admin_readconfig_string( &cnf, b->guid, sizeof( b->guid ) );
+ }
+ else if( !Q_stricmp( t, "ip" ) )
+ {
+ admin_readconfig_string( &cnf, b->ip, sizeof( b->ip ) );
+ }
+ else if( !Q_stricmp( t, "reason" ) )
+ {
+ admin_readconfig_string( &cnf, b->reason, sizeof( b->reason ) );
+ }
+ else if( !Q_stricmp( t, "made" ) )
+ {
+ admin_readconfig_string( &cnf, b->made, sizeof( b->made ) );
+ }
+ else if( !Q_stricmp( t, "expires" ) )
+ {
+ admin_readconfig_int( &cnf, &b->expires );
+ }
+ else if( !Q_stricmp( t, "banner" ) )
+ {
+ admin_readconfig_string( &cnf, b->banner, sizeof( b->banner ) );
+ }
+ else
+ {
+ ADMP( va( "^3!readconfig: ^7[ban] parse error near %s on line %d\n",
+ t,
+ COM_GetCurrentParseLine() ) );
+ }
+ }
+ else if( command_open )
+ {
+ if( !Q_stricmp( t, "command" ) )
+ {
+ admin_readconfig_string( &cnf, c->command, sizeof( c->command ) );
+ }
+ else if( !Q_stricmp( t, "exec" ) )
+ {
+ admin_readconfig_string( &cnf, c->exec, sizeof( c->exec ) );
+ }
+ else if( !Q_stricmp( t, "desc" ) )
+ {
+ admin_readconfig_string( &cnf, c->desc, sizeof( c->desc ) );
+ }
+ else if( !Q_stricmp( t, "levels" ) )
+ {
+ char level[ 4 ] = {""};
+ char *lp = levels;
+ int cmdlevel = 0;
+
+ admin_readconfig_string( &cnf, levels, sizeof( levels ) );
+ while( *lp )
+ {
+ if( *lp == ' ' )
+ {
+ c->levels[ cmdlevel++ ] = atoi( level );
+ level[ 0 ] = '\0';
+ lp++;
+ continue;
+ }
+ Q_strcat( level, sizeof( level ), va( "%c", *lp ) );
+ lp++;
+ }
+ if( level[ 0 ] )
+ c->levels[ cmdlevel++ ] = atoi( level );
+ // ensure the list is -1 terminated
+ c->levels[ MAX_ADMIN_LEVELS ] = -1;
+ }
+ else
+ {
+ ADMP( va( "^3!readconfig: ^7[command] parse error near %s on line %d\n",
+ t,
+ COM_GetCurrentParseLine() ) );
+ }
+ }
+
+ if( !Q_stricmp( t, "[level]" ) )
+ {
+ if( lc >= MAX_ADMIN_LEVELS )
+ return qfalse;
+ l = G_Alloc( sizeof( g_admin_level_t ) );
+ l->level = 0;
+ *l->name = '\0';
+ *l->flags = '\0';
+ level_open = qtrue;
+ }
+ else if( !Q_stricmp( t, "[admin]" ) )
+ {
+ if( ac >= MAX_ADMIN_ADMINS )
+ return qfalse;
+ a = G_Alloc( sizeof( g_admin_admin_t ) );
+ *a->name = '\0';
+ *a->guid = '\0';
+ a->level = 0;
+ *a->flags = '\0';
+ admin_open = qtrue;
+ }
+ else if( !Q_stricmp( t, "[ban]" ) )
+ {
+ if( bc >= MAX_ADMIN_BANS )
+ return qfalse;
+ b = G_Alloc( sizeof( g_admin_ban_t ) );
+ *b->name = '\0';
+ *b->guid = '\0';
+ *b->ip = '\0';
+ *b->made = '\0';
+ b->expires = 0;
+ *b->reason = '\0';
+ ban_open = qtrue;
+ }
+ else if( !Q_stricmp( t, "[command]" ) )
+ {
+ if( cc >= MAX_ADMIN_COMMANDS )
+ return qfalse;
+ c = G_Alloc( sizeof( g_admin_command_t ) );
+ *c->command = '\0';
+ *c->exec = '\0';
+ *c->desc = '\0';
+ memset( c->levels, -1, sizeof( c->levels ) );
+ command_open = qtrue;
+ }
+ t = COM_Parse( &cnf );
+ }
+ if( level_open )
+ {
+
+ g_admin_levels[ lc++ ] = l;
+ }
+ if( admin_open )
+ g_admin_admins[ ac++ ] = a;
+ if( ban_open )
+ g_admin_bans[ bc++ ] = b;
+ if( command_open )
+ g_admin_commands[ cc++ ] = c;
+ G_Free( cnf2 );
+ ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n",
+ lc, ac, bc, cc ) );
+ if( lc == 0 )
+ admin_default_levels();
+ else
+ {
+ char n[ MAX_NAME_LENGTH ] = {""};
+
+ // max printable name length for formatting
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ G_DecolorString( l->name, n );
+ if( strlen( n ) > admin_level_maxname )
+ admin_level_maxname = strlen( n );
+ }
+ }
+ // reset adminLevel
+ for( i = 0; i < level.maxclients; i++ )
+ if( level.clients[ i ].pers.connected != CON_DISCONNECTED )
+ level.clients[ i ].pers.adminLevel = G_admin_level( &g_entities[ i ] );
+ return qtrue;
+}
+
+qboolean G_admin_time( gentity_t *ent, int skiparg )
+{
+ qtime_t qt;
+ int t;
+
+ t = trap_RealTime( &qt );
+ ADMP( va( "^3!time: ^7local time is %02i:%02i:%02i\n",
+ qt.tm_hour, qt.tm_min, qt.tm_sec ) );
+ return qtrue;
+}
+
+qboolean G_admin_setlevel( gentity_t *ent, int skiparg )
+{
+ char name[ MAX_NAME_LENGTH ] = {""};
+ char lstr[ 11 ]; // 10 is max strlen() for 32-bit int
+ char adminname[ MAX_NAME_LENGTH ] = {""};
+ char testname[ MAX_NAME_LENGTH ] = {""};
+ char guid[ 33 ];
+ int l, i;
+ gentity_t *vic = NULL;
+ qboolean updated = qfalse;
+ g_admin_admin_t *a;
+ qboolean found = qfalse;
+ qboolean numeric = qtrue;
+ int matches = 0;
+ int id = -1;
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!setlevel: ^7usage: !setlevel [name|slot#] [level]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, testname, sizeof( testname ) );
+ G_SayArgv( 2 + skiparg, lstr, sizeof( lstr ) );
+ l = atoi( lstr );
+ G_SanitiseString( testname, name, sizeof( name ) );
+ for( i = 0; i < sizeof( name ) && name[ i ] ; i++ )
+ {
+ if( name[ i ] < '0' || name[ i ] > '9' )
+ {
+ numeric = qfalse;
+ break;
+ }
+ }
+ if( numeric )
+ id = atoi( name );
+
+ if( ent && l > ent->client->pers.adminLevel )
+ {
+ ADMP( "^3!setlevel: ^7you may not use !setlevel to set a level higher "
+ "than your current level\n" );
+ return qfalse;
+ }
+
+ // if admin is activated for the first time on a running server, we need
+ // to ensure at least the default levels get created
+ if( !ent && !g_admin_levels[ 0 ] )
+ G_admin_readconfig(NULL, 0);
+
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ if( g_admin_levels[ i ]->level == l )
+ {
+ found = qtrue;
+ break;
+ }
+ }
+ if( !found )
+ {
+ ADMP( "^3!setlevel: ^7level is not defined\n" );
+ return qfalse;
+ }
+
+ if( numeric && id >= 0 && id < level.maxclients )
+ vic = &g_entities[ id ];
+
+ if( vic && vic->client && vic->client->pers.connected != CON_DISCONNECTED )
+ {
+ vic = &g_entities[ id ];
+ Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) );
+ Q_strncpyz( guid, vic->client->pers.guid, sizeof( guid ) );
+ matches = 1;
+ }
+ else if( numeric && id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS
+ && g_admin_admins[ id - MAX_CLIENTS ] )
+ {
+ Q_strncpyz( adminname, g_admin_admins[ id - MAX_CLIENTS ]->name,
+ sizeof( adminname ) );
+ Q_strncpyz( guid, g_admin_admins[ id - MAX_CLIENTS ]->guid,
+ sizeof( guid ) );
+ matches = 1;
+ }
+ else
+ {
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && matches < 2; i++ )
+ {
+ G_SanitiseString( g_admin_admins[ i ]->name, testname, sizeof( testname ) );
+ if( strstr( testname, name ) )
+ {
+ Q_strncpyz( adminname, g_admin_admins[ i ]->name, sizeof( adminname ) );
+ Q_strncpyz( guid, g_admin_admins[ i ]->guid, sizeof( guid ) );
+ matches++;
+ }
+ }
+ for( i = 0; i < level.maxclients && matches < 2; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+ if( matches && !Q_stricmp( level.clients[ i ].pers.guid, guid ) )
+ {
+ vic = &g_entities[ i ];
+ continue;
+ }
+ G_SanitiseString( level.clients[ i ].pers.netname, testname, sizeof( testname ) );
+ if( strstr( testname, name ) )
+ {
+ vic = &g_entities[ i ];
+ matches++;
+ Q_strncpyz( guid, vic->client->pers.guid, sizeof( guid ) );
+ }
+ }
+ if( vic )
+ Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) );
+ }
+
+ if( matches == 0 )
+ {
+ ADMP( "^3!setlevel:^7 no match. use !listplayers or !listadmins to "
+ "find an appropriate number to use instead of name.\n" );
+ return qfalse;
+ }
+ else if( matches > 1 )
+ {
+ ADMP( "^3!setlevel:^7 more than one match. Use the admin number "
+ "instead:\n" );
+ admin_listadmins( ent, 0, name, 0 );
+ return qfalse;
+ }
+
+ if( !Q_stricmp( guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) )
+ {
+ ADMP( va( "^3!setlevel: ^7%s does not have a valid GUID\n", adminname ) );
+ return qfalse;
+ }
+ if( ent && !admin_higher_guid( ent->client->pers.guid, guid ) )
+ {
+ ADMP( "^3!setlevel: ^7sorry, but your intended victim has a higher"
+ " admin level than you\n" );
+ return qfalse;
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ];i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) )
+ {
+ g_admin_admins[ i ]->level = l;
+ Q_strncpyz( g_admin_admins[ i ]->name, adminname,
+ sizeof( g_admin_admins[ i ]->name ) );
+ updated = qtrue;
+ }
+ }
+ if( !updated )
+ {
+ if( i == MAX_ADMIN_ADMINS )
+ {
+ ADMP( "^3!setlevel: ^7too many admins\n" );
+ return qfalse;
+ }
+ a = G_Alloc( sizeof( g_admin_admin_t ) );
+ a->level = l;
+ Q_strncpyz( a->name, adminname, sizeof( a->name ) );
+ Q_strncpyz( a->guid, guid, sizeof( a->guid ) );
+ *a->flags = '\0';
+ g_admin_admins[ i ] = a;
+ }
+
+ AP( va(
+ "print \"^3!setlevel: ^7%s^7 was given level %d admin rights by %s\n\"",
+ adminname, l, ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ if( vic )
+ {
+ vic->client->pers.adminLevel = l;
+ G_admin_set_adminname( vic );
+ }
+
+ if( !g_admin.string[ 0 ] )
+ ADMP( "^3!setlevel: ^7WARNING g_admin not set, not saving admin record "
+ "to a file\n" );
+ else
+ admin_writeconfig();
+ return qtrue;
+}
+
+int G_admin_parse_time( const char *time )
+{
+ int seconds = 0, num = 0;
+ int i;
+ for( i = 0; time[ i ]; i++ )
+ {
+ if( isdigit( time[ i ] ) )
+ {
+ num = num * 10 + time[ i ] - '0';
+ continue;
+ }
+ if( i == 0 || !isdigit( time[ i - 1 ] ) )
+ return -1;
+ switch( time[ i ] )
+ {
+ case 'w': num *= 7;
+ case 'd': num *= 24;
+ case 'h': num *= 60;
+ case 'm': num *= 60;
+ case 's': break;
+ default: return -1;
+ }
+ seconds += num;
+ num = 0;
+ }
+ if( num )
+ seconds += num;
+ // overflow
+ if( seconds < 0 )
+ seconds = 0;
+ return seconds;
+}
+
+static qboolean admin_create_ban( gentity_t *ent,
+ char *netname,
+ char *guid,
+ char *ip,
+ int seconds,
+ char *reason )
+{
+ g_admin_ban_t *b = NULL;
+ qtime_t qt;
+ int t;
+ int i;
+ int j;
+ qboolean foundAdminTrueName=qfalse;
+
+ t = trap_RealTime( &qt );
+ b = G_Alloc( sizeof( g_admin_ban_t ) );
+
+ if( !b )
+ return qfalse;
+
+ Q_strncpyz( b->name, netname, sizeof( b->name ) );
+ Q_strncpyz( b->guid, guid, sizeof( b->guid ) );
+ Q_strncpyz( b->ip, ip, sizeof( b->ip ) );
+
+ //strftime( b->made, sizeof( b->made ), "%m/%d/%y %H:%M:%S", lt );
+ Q_strncpyz( b->made, va( "%02i/%02i/%02i %02i:%02i:%02i",
+ (qt.tm_mon + 1), qt.tm_mday, (qt.tm_year - 100),
+ qt.tm_hour, qt.tm_min, qt.tm_sec ),
+ sizeof( b->made ) );
+
+ if( ent ) {
+ //Get admin true name
+ for(j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ j ]->guid, ent->client->pers.guid ) )
+ {
+ Q_strncpyz( b->banner, g_admin_admins[ j ]->name, sizeof( b->banner ) );
+ foundAdminTrueName=qtrue;
+ break;
+ }
+ }
+ if(foundAdminTrueName==qfalse) Q_strncpyz( b->banner, ent->client->pers.netname, sizeof( b->banner ) );
+ }
+ else
+ Q_strncpyz( b->banner, "console", sizeof( b->banner ) );
+ if( !seconds )
+ b->expires = 0;
+ else
+ b->expires = t + seconds;
+ if( !*reason )
+ Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) );
+ else
+ Q_strncpyz( b->reason, reason, sizeof( b->reason ) );
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ ;
+ if( i == MAX_ADMIN_BANS )
+ {
+ ADMP( "^3!ban: ^7too many bans\n" );
+ G_Free( b );
+ return qfalse;
+ }
+ g_admin_bans[ i ] = b;
+ return qtrue;
+}
+
+qboolean G_admin_kick( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
+ int minargc;
+ gentity_t *vic;
+ char notice[51];
+
+ trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) );
+
+ minargc = 3 + skiparg;
+ if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!kick: ^7usage: !kick [name] [reason]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ reason = G_SayConcatArgs( 2 + skiparg );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!kick: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( "^3!kick: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+ admin_create_ban( ent,
+ vic->client->pers.netname,
+ vic->client->pers.guid,
+ vic->client->pers.ip, G_admin_parse_time( g_adminTempBan.string ),
+ ( *reason ) ? reason : "kicked by admin" );
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+
+ trap_SendServerCommand( pids[ 0 ],
+ va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"",
+ ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole",
+ ( *reason ) ? reason : "kicked by admin", notice ) );
+
+ trap_DropClient( pids[ 0 ], va( "kicked%s^7, reason: %s",
+ ( ent ) ? va( " by %s", G_admin_adminPrintName( ent ) ) : " by console",
+ ( *reason ) ? reason : "kicked by admin" ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_ban( gentity_t *ent, int skiparg )
+{
+ int seconds;
+ char search[ MAX_NAME_LENGTH ];
+ char secs[ 7 ];
+ char *reason;
+ int minargc;
+ char duration[ 32 ];
+ int logmatch = -1, logmatches = 0;
+ int i, j;
+ qboolean exactmatch = qfalse;
+ char n2[ MAX_NAME_LENGTH ];
+ char s2[ MAX_NAME_LENGTH ];
+ char guid_stub[ 9 ];
+ char notice[51];
+
+ trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) );
+
+ if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ {
+ minargc = 2 + skiparg;
+ }
+ else if( ( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) || g_adminMaxBan.integer ) ||
+ G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ {
+ minargc = 3 + skiparg;
+ }
+ else
+ {
+ minargc = 4 + skiparg;
+ }
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!ban: ^7usage: !ban [name|slot|ip] [time] [reason]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, search, sizeof( search ) );
+ G_SanitiseString( search, s2, sizeof( s2 ) );
+ G_SayArgv( 2 + skiparg, secs, sizeof( secs ) );
+
+ seconds = G_admin_parse_time( secs );
+ if( seconds <= 0 )
+ {
+ if( g_adminMaxBan.integer && !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ ADMP( va( "^3!ban: ^7using your admin level's maximum ban length of %s\n",
+ g_adminMaxBan.string ) );
+ seconds = G_admin_parse_time( g_adminMaxBan.string );
+ }
+ else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ seconds = 0;
+ }
+ else
+ {
+ ADMP( "^3!ban: ^7ban time must be positive\n" );
+ return qfalse;
+ }
+ reason = G_SayConcatArgs( 2 + skiparg );
+ }
+ else
+ {
+ reason = G_SayConcatArgs( 3 + skiparg );
+
+ if( g_adminMaxBan.integer &&
+ seconds > G_admin_parse_time( g_adminMaxBan.string ) &&
+ !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ seconds = G_admin_parse_time( g_adminMaxBan.string );
+ ADMP( va( "^3!ban: ^7ban length limited to %s for your admin level\n",
+ g_adminMaxBan.string ) );
+ }
+ }
+
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ // skip players in the namelog who have already been banned
+ if( g_admin_namelog[ i ]->banned )
+ continue;
+
+ // skip disconnected players when banning on slot number
+ if( g_admin_namelog[ i ]->slot == -1 )
+ continue;
+
+ if( !Q_stricmp( va( "%d", g_admin_namelog[ i ]->slot ), s2 ) )
+ {
+ logmatches = 1;
+ logmatch = i;
+ exactmatch = qtrue;
+ break;
+ }
+ }
+
+ for( i = 0;
+ !exactmatch && i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ];
+ i++ )
+ {
+ // skip players in the namelog who have already been banned
+ if( g_admin_namelog[ i ]->banned )
+ continue;
+
+ if( !Q_stricmp( g_admin_namelog[ i ]->ip, s2 ) )
+ {
+ logmatches = 1;
+ logmatch = i;
+ exactmatch = qtrue;
+ break;
+ }
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
+ && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( strstr( n2, s2 ) )
+ {
+ if( logmatch != i )
+ logmatches++;
+ logmatch = i;
+ }
+ }
+ }
+
+ if( !logmatches )
+ {
+ ADMP( "^3!ban: ^7no player found by that name, IP, or slot number\n" );
+ return qfalse;
+ }
+ else if( logmatches > 1 )
+ {
+ ADMBP_begin();
+ ADMBP( "^3!ban: ^7multiple recent clients match name, use IP or slot#:\n" );
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES
+ && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( strstr( n2, s2 ) )
+ {
+ if( g_admin_namelog[ i ]->slot > -1 )
+ ADMBP( "^3" );
+ ADMBP( va( "%-2s (*%s) %15s ^7'%s^7'\n",
+ (g_admin_namelog[ i ]->slot > -1) ?
+ va( "%d", g_admin_namelog[ i ]->slot ) : "-",
+ guid_stub,
+ g_admin_namelog[ i ]->ip,
+ g_admin_namelog[ i ]->name[ j ] ) );
+ }
+ }
+ }
+ ADMBP_end();
+ return qfalse;
+ }
+
+ G_admin_duration( ( seconds ) ? seconds : -1,
+ duration, sizeof( duration ) );
+
+ if( ent && !admin_higher_guid( ent->client->pers.guid,
+ g_admin_namelog[ logmatch ]->guid ) )
+ {
+
+ ADMP( "^3!ban: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+
+ admin_create_ban( ent,
+ g_admin_namelog[ logmatch ]->name[ 0 ],
+ g_admin_namelog[ logmatch ]->guid,
+ g_admin_namelog[ logmatch ]->ip,
+ seconds, reason );
+
+ g_admin_namelog[ logmatch ]->banned = qtrue;
+
+ if( !g_admin.string[ 0 ] )
+ ADMP( "^3!ban: ^7WARNING g_admin not set, not saving ban to a file\n" );
+ else
+ admin_writeconfig();
+
+ if( g_admin_namelog[ logmatch ]->slot == -1 )
+ {
+ // client is already disconnected so stop here
+ AP( va( "print \"^3!ban:^7 %s^7 has been banned by %s^7 "
+ "duration: %s, reason: %s\n\"",
+ g_admin_namelog[ logmatch ]->name[ 0 ],
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ duration,
+ ( *reason ) ? reason : "banned by admin" ) );
+ return qtrue;
+ }
+
+ trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot,
+ va( "disconnect \"You have been banned.\n"
+ "admin:\n%s^7\nduration:\n%s\nreason:\n%s\n%s\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ duration,
+ ( *reason ) ? reason : "banned by admin", notice ) );
+
+ trap_DropClient( g_admin_namelog[ logmatch ]->slot,
+ va( "banned by %s^7, duration: %s, reason: %s",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ duration,
+ ( *reason ) ? reason : "banned by admin" ) );
+ return qtrue;
+}
+
+qboolean G_admin_adjustban( gentity_t *ent, int skiparg )
+{
+ int bnum;
+ int length;
+ int expires;
+ char duration[ 32 ] = {""};
+ char *reason;
+ char bs[ 5 ];
+ char secs[ 7 ];
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!adjustban: ^7usage: !adjustban [ban#] [time] [reason]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1] )
+ {
+ ADMP( "^3!adjustban: ^7invalid ban#\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 2 + skiparg, secs, sizeof( secs ) );
+ length = G_admin_parse_time( secs );
+
+ if( g_adminMaxBan.integer &&
+ !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ ( length > G_admin_parse_time( g_adminMaxBan.string ) || length == 0 ) )
+ {
+ ADMP( va("^3!adjustban: ^7ban length is limited to %s for your admin level\n", g_adminMaxBan.string ) );
+ return qfalse;
+ }
+
+ if( length < 0 )
+ reason = G_SayConcatArgs( 2 + skiparg );
+ else
+ {
+ if( length != 0 )
+ expires = trap_RealTime( NULL ) + length;
+ else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ expires = 0;
+ else
+ {
+ ADMP( "^3!adjustban: ^7ban time must be positive\n" );
+ return qfalse;
+ }
+
+ g_admin_bans[ bnum - 1 ]->expires = expires;
+ G_admin_duration( ( length ) ? length : -1, duration, sizeof( duration ) );
+ reason = G_SayConcatArgs( 3 + skiparg );
+ }
+ if( *reason )
+ Q_strncpyz( g_admin_bans[ bnum - 1 ]->reason, reason,
+ sizeof( g_admin_bans[ bnum - 1 ]->reason ) );
+ AP( va( "print \"^3!adjustban: ^7ban #%d for %s^7 has been updated by %s^7 "
+ "%s%s%s%s%s\n\"",
+ bnum,
+ g_admin_bans[ bnum - 1 ]->name,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( length >= 0 ) ? "duration: " : "",
+ duration,
+ ( length >= 0 && *reason ) ? ", " : "",
+ ( *reason ) ? "reason: " : "",
+ reason ) );
+ if( ent )
+ {
+ qboolean found = qfalse;
+ int i;
+
+ // real admin name
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ {
+ Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, g_admin_admins[ i ]->name,
+ sizeof( g_admin_bans[ bnum - 1 ]->banner ) );
+ found = qtrue;
+ break;
+ }
+ }
+ if( !found )
+ Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, ent->client->pers.netname,
+ sizeof( g_admin_bans[ bnum - 1 ]->banner ) );
+ }
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+ return qtrue;
+}
+
+
+qboolean G_admin_subnetban( gentity_t *ent, int skiparg )
+{
+ int bnum;
+ int mask;
+ int IPRlow = 0, IPRhigh = 0;
+ char cIPRlow[ 32 ], cIPRhigh[ 32 ];
+ char bs[ 5 ];
+ char strmask[ 5 ];
+ char exl[2];
+ int k, IP[5];
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!subnetban: ^7usage: !subnetban [ban#] [mask]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1] )
+ {
+ ADMP( "^3!subnetban: ^7invalid ban#\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( 2 + skiparg, strmask, sizeof( strmask ) );
+ mask = atoi( strmask );
+
+ if( mask >= 0 && mask <= 32)
+ {
+ G_SayArgv( 3 + skiparg, exl, sizeof( exl ) );
+ if( mask >= 0 && mask < 16 && strcmp(exl, "!") )
+ {
+ if( ent )
+ {
+ ADMP( "^3!subnetban: ^7Only console may ban such a large network. Regular admins may only ban >=16.\n" );
+ return qfalse;
+ }
+
+ ADMP( "^3!subnetban: ^1WARNING:^7 you are about to ban a large network, use !subnetban [ban] [mask] ! to force^7\n" );
+ return qfalse;
+ }
+ else
+ {
+ sscanf(g_admin_bans[ bnum - 1 ]->ip, "%d.%d.%d.%d/%d", &IP[4], &IP[3], &IP[2], &IP[1], &IP[0]);
+ for(k = 4; k >= 1; k--)
+ {
+ if(!IP[k]) IP[k] = 0;
+ IPRlow |= IP[k] << 8*(k-1);
+ }
+ IPRhigh = IPRlow;
+ if( mask == 32 )
+ {
+ Q_strncpyz(
+ g_admin_bans[ bnum - 1 ]->ip,
+ va("%i.%i.%i.%i", IP[4], IP[3], IP[2], IP[1]),
+ sizeof( g_admin_bans[ bnum - 1 ]->ip )
+ );
+ }
+ else
+ {
+ Q_strncpyz(
+ g_admin_bans[ bnum - 1 ]->ip,
+ va("%i.%i.%i.%i/%i", IP[4], IP[3], IP[2], IP[1], mask ),
+ sizeof( g_admin_bans[ bnum - 1 ]->ip )
+ );
+ IPRlow &= ~((1 << (32-mask)) - 1);
+ IPRhigh |= ((1 << (32-mask)) - 1);
+ }
+ }
+ }
+ else
+ {
+ ADMP( "^3!subnetban: ^7mask is out of range, please use 0-32 inclusive\n" );
+ return qfalse;
+ }
+ if( mask > 0 )
+ {
+ Q_strncpyz(
+ cIPRlow,
+ va("%i.%i.%i.%i", (IPRlow & (255 << 24)) >> 24, (IPRlow & (255 << 16)) >> 16, (IPRlow & (255 << 8)) >> 8, IPRlow & 255),
+ sizeof( cIPRlow )
+ );
+ Q_strncpyz(
+ cIPRhigh,
+ va("%i.%i.%i.%i", (IPRhigh & (255 << 24)) >> 24, (IPRhigh & (255 << 16)) >> 16, (IPRhigh & (255 << 8)) >> 8, IPRhigh & 255),
+ sizeof( cIPRhigh )
+ );
+ }
+ else
+ {
+ Q_strncpyz( cIPRlow, "0.0.0.0", sizeof( cIPRlow ) );
+ Q_strncpyz( cIPRhigh, "255.255.255.255", sizeof( cIPRhigh ) );
+
+ }
+
+ AP( va( "print \"^3!subnetban: ^7ban #%d for %s^7 has been updated by %s^7 "
+ "%s (%s - %s)\n\"",
+ bnum,
+ g_admin_bans[ bnum - 1 ]->name,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ g_admin_bans[ bnum - 1 ]->ip,
+ cIPRlow,
+ cIPRhigh) );
+ if( ent )
+ Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, ent->client->pers.netname,
+ sizeof( g_admin_bans[ bnum - 1 ]->banner ) );
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+ return qtrue;
+}
+
+
+qboolean G_admin_unban( gentity_t *ent, int skiparg )
+{
+ int bnum;
+ char bs[ 5 ];
+ int t;
+
+ t = trap_RealTime( NULL );
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!unban: ^7usage: !unban [ban#]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1 ] )
+ {
+ ADMP( "^3!unban: ^7invalid ban#\n" );
+ return qfalse;
+ }
+ g_admin_bans[ bnum -1 ]->expires = t;
+ AP( va( "print \"^3!unban: ^7ban #%d for %s^7 has been removed by %s\n\"",
+ bnum,
+ g_admin_bans[ bnum - 1 ]->name,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ if( g_admin.string[ 0 ] )
+ admin_writeconfig();
+ return qtrue;
+}
+
+qboolean G_admin_putteam( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], team[ 7 ], err[ MAX_STRING_CHARS ];
+ gentity_t *vic;
+ pTeam_t teamnum = PTE_NONE;
+ char teamdesc[ 32 ] = {"spectators"};
+
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ G_SayArgv( 2 + skiparg, team, sizeof( team ) );
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!putteam: ^7usage: !putteam [name] [h|a|s]\n" );
+ return qfalse;
+ }
+
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!putteam: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( "^3!putteam: ^7sorry, but your intended victim has a higher "
+ " admin level than you\n" );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+ switch( team[ 0 ] )
+ {
+ case 'a':
+ teamnum = PTE_ALIENS;
+ Q_strncpyz( teamdesc, "aliens", sizeof( teamdesc ) );
+ break;
+ case 'h':
+ teamnum = PTE_HUMANS;
+ Q_strncpyz( teamdesc, "humans", sizeof( teamdesc ) );
+ break;
+ case 's':
+ teamnum = PTE_NONE;
+ break;
+ default:
+ ADMP( va( "^3!putteam: ^7unknown team %c\n", team[ 0 ] ) );
+ return qfalse;
+ }
+ if( vic->client->pers.teamSelection == teamnum )
+ return qfalse;
+ if( level.demoState == DS_PLAYBACK )//rotax
+ {
+ ADMP( "^3!putteam: ^7cannot join a team while a demo is playing\n" );
+ return qfalse;
+ }
+ G_ChangeTeam( vic, teamnum );
+
+ AP( va( "print \"^3!putteam: ^7%s^7 put %s^7 on to the %s team\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ vic->client->pers.netname, teamdesc ) );
+ return qtrue;
+}
+
+qboolean G_admin_map( gentity_t *ent, int skiparg )
+{
+ char map[ MAX_QPATH ];
+ char layout[ MAX_QPATH ] = { "" };
+
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!map: ^7usage: !map [map] (layout)\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( skiparg + 1, map, sizeof( map ) );
+
+ if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) )
+ {
+ ADMP( va( "^3!map: ^7invalid map name '%s'\n", map ) );
+ return qfalse;
+ }
+
+ if( G_SayArgc( ) > 2 + skiparg )
+ {
+ G_SayArgv( skiparg + 2, layout, sizeof( layout ) );
+ if( !Q_stricmp( layout, "*BUILTIN*" ) ||
+ trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ),
+ NULL, FS_READ ) > 0 )
+ {
+ trap_Cvar_Set( "g_layouts", layout );
+ }
+ else
+ {
+ ADMP( va( "^3!map: ^7invalid layout name '%s'\n", layout ) );
+ return qfalse;
+ }
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) );
+ level.restarted = qtrue;
+ AP( va( "print \"^3!map: ^7map '%s' started by %s^7 %s\n\"", map,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) );
+ G_admin_maplog_result( "M" );
+ return qtrue;
+}
+
+qboolean G_admin_devmap( gentity_t *ent, int skiparg )
+{
+ char map[ MAX_QPATH ];
+ char layout[ MAX_QPATH ] = { "" };
+
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!devmap: ^7usage: !devmap [map] (layout)\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( skiparg + 1, map, sizeof( map ) );
+
+ if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) )
+ {
+ ADMP( va( "^3!devmap: ^7invalid map name '%s'\n", map ) );
+ return qfalse;
+ }
+
+ if( G_SayArgc( ) > 2 + skiparg )
+ {
+ G_SayArgv( skiparg + 2, layout, sizeof( layout ) );
+ if( !Q_stricmp( layout, "*BUILTIN*" ) ||
+ trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ),
+ NULL, FS_READ ) > 0 )
+ {
+ trap_Cvar_Set( "g_layouts", layout );
+ }
+ else
+ {
+ ADMP( va( "^3!devmap: ^7invalid layout name '%s'\n", layout ) );
+ return qfalse;
+ }
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "devmap %s", map ) );
+ level.restarted = qtrue;
+ AP( va( "print \"^3!devmap: ^7map '%s' started by %s^7 with cheats %s\n\"", map,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) );
+ G_admin_maplog_result( "D" );
+ return qtrue;
+}
+
+void G_admin_maplog_update( void )
+{
+ char map[ 64 ];
+ char maplog[ MAX_CVAR_VALUE_STRING ];
+ char *ptr;
+ int count = 0;
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+
+ Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) );
+ ptr = maplog;
+ while( *ptr && count < MAX_ADMIN_MAPLOG_LENGTH )
+ {
+ while( *ptr != ' ' && *ptr != '\0' ) ptr++;
+
+ count++;
+ if( count >= MAX_ADMIN_MAPLOG_LENGTH )
+ {
+ *ptr = '\0';
+ }
+
+ if( *ptr == ' ' ) ptr++;
+ }
+
+ trap_Cvar_Set( "g_adminMapLog", va( "%s%s%s",
+ map,
+ ( maplog[0] != '\0' ) ? " " : "",
+ maplog ) );
+}
+
+void G_admin_maplog_result( char *flag )
+{
+ static int lastTime = 0;
+ char maplog[ MAX_CVAR_VALUE_STRING ];
+ int t;
+
+ if( !flag )
+ return;
+
+ // avoid race when called in same frame
+ if( level.time == lastTime )
+ return;
+
+ lastTime = level.time;
+
+ if( g_adminMapLog.string[ 0 ] &&
+ g_adminMapLog.string[ 1 ] == ';' )
+ {
+ // only one result allowed
+ return;
+ }
+
+ if ( level.surrenderTeam != PTE_NONE )
+ {
+ if( flag[ 0 ] == 'a' )
+ {
+ if( level.surrenderTeam == PTE_HUMANS )
+ flag = "A";
+ }
+ else if( flag[ 0 ] == 'h' )
+ {
+ if( level.surrenderTeam == PTE_ALIENS )
+ flag = "H";
+ }
+ }
+
+ t = ( level.time - level.startTime ) / 1000;
+ Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) );
+ trap_Cvar_Set( "g_adminMapLog", va( "%1s;%03d:%02d;%s",
+ flag,
+ t / 60, t % 60,
+ maplog ) );
+}
+
+
+qboolean G_admin_maplog( gentity_t *ent, int skiparg )
+{
+ char maplog[ MAX_CVAR_VALUE_STRING ];
+ char *ptr;
+ int count = 0;
+
+ Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) );
+
+ ADMBP_begin( );
+ ptr = maplog;
+ while( *ptr != '\0' && count < MAX_ADMIN_MAPLOG_LENGTH + 1 )
+ {
+ char *end;
+ const char *result = NULL;
+ char *clock = NULL;
+ char *colon;
+
+ end = ptr;
+ while( *end != ' ' && *end != '\0' ) end++;
+ if( *end == ' ' )
+ {
+ *end = '\0';
+ end++;
+ }
+
+ if( ptr[ 0 ] && ptr[ 1 ] == ';' )
+ {
+ switch( ptr[ 0 ] )
+ {
+ case 't':
+ result = "^7tie";
+ break;
+ case 'a':
+ result = "^1Alien win";
+ break;
+ case 'A':
+ result = "^1Alien win ^7/ Humans admitted defeat";
+ break;
+ case 'h':
+ result = "^4Human win";
+ break;
+ case 'H':
+ result = "^4Human win ^7/ Aliens admitted defeat";
+ break;
+ case 'd':
+ result = "^5draw vote";
+ break;
+ case 'N':
+ result = "^6admin loaded next map";
+ break;
+ case 'r':
+ result = "^2restart vote";
+ break;
+ case 'R':
+ result = "^6admin restarted map";
+ break;
+ case 'm':
+ result = "^2map vote";
+ break;
+ case 'M':
+ result = "^6admin changed map";
+ break;
+ case 'D':
+ result = "^6admin loaded devmap";
+ break;
+ default:
+ result = "";
+ break;
+ }
+ ptr += 2;
+ colon = strchr( ptr, ';' );
+ if ( colon )
+ {
+ clock = ptr;
+ ptr = colon + 1;
+ *colon = '\0';
+
+ // right justification with -6%s doesnt work..
+ if( clock[ 0 ] == '0' && clock[ 1 ] != ':' )
+ {
+ if( clock[ 1 ] == '0' && clock[ 2 ] != ':' )
+ clock[ 1 ] = ' ';
+ clock[ 0 ] = ' ';
+ }
+ }
+ }
+ else if( count == 0 )
+ {
+ result = "^3current map";
+ clock = " -:--";
+ }
+
+ ADMBP( va( "%s%20s %-6s %s^7\n",
+ ( count == 0 ) ? "^3" : "^7",
+ ptr,
+ ( clock ) ? clock : "",
+ ( result ) ? result : "" ) );
+
+ ptr = end;
+ count++;
+ }
+ ADMBP_end( );
+
+ return qtrue;
+}
+
+qboolean G_admin_layoutsave( gentity_t *ent, int skiparg )
+{
+ char layout[ MAX_QPATH ];
+
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!layoutsave: ^7usage: !layoutsave [layout]\n" );
+ return qfalse;
+ }
+
+ G_SayArgv( skiparg + 1, layout, sizeof( layout ) );
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "layoutsave %s", layout ) );
+ AP( va( "print \"^3!layoutsave: ^7layout saved as '%s' by %s\n\"", layout,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_mute( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ gentity_t *vic;
+
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+ if( cmd && *cmd == '!' )
+ cmd++;
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( va( "^3!%s: ^7usage: !%s [name|slot#]\n", cmd, cmd ) );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!%s: ^7%s\n", cmd, err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n", cmd ) );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+ if( vic->client->pers.muted == qtrue )
+ {
+ if( !Q_stricmp( cmd, "mute" ) )
+ {
+ ADMP( "^3!mute: ^7player is already muted\n" );
+ return qtrue;
+ }
+ vic->client->pers.muted = qfalse;
+ CPx( pids[ 0 ], "cp \"^1You have been unmuted\"" );
+ AP( va( "print \"^3!unmute: ^7%s^7 has been unmuted by %s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ else
+ {
+ if( !Q_stricmp( cmd, "unmute" ) )
+ {
+ ADMP( "^3!unmute: ^7player is not currently muted\n" );
+ return qtrue;
+ }
+ vic->client->pers.muted = qtrue;
+ CPx( pids[ 0 ], "cp \"^1You've been muted\"" );
+ AP( va( "print \"^3!mute: ^7%s^7 has been muted by ^7%s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ ClientUserinfoChanged( pids[ 0 ] );
+ return qtrue;
+}
+
+qboolean G_admin_cp( gentity_t *ent, int skiparg )
+{
+ int minargc;
+ char *s;
+
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!cp: ^7usage: !cp [message]\n" );
+ return qfalse;
+ }
+
+ s = G_SayConcatArgs( 1 + skiparg );
+ G_CP(ent);
+ return qtrue;
+}
+
+qboolean G_admin_denybuild( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ gentity_t *vic;
+
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+ if( cmd && *cmd == '!' )
+ cmd++;
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( va( "^3!%s: ^7usage: !%s [name|slot#]\n", cmd, cmd ) );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!%s: ^7%s\n", cmd, err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n", cmd ) );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+ if( vic->client->pers.denyBuild )
+ {
+ if( !Q_stricmp( cmd, "denybuild" ) )
+ {
+ ADMP( "^3!denybuild: ^7player already has no building rights\n" );
+ return qtrue;
+ }
+ vic->client->pers.denyBuild = qfalse;
+ CPx( pids[ 0 ], "cp \"^1You've regained your building rights\"" );
+ AP( va(
+ "print \"^3!allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ else
+ {
+ if( !Q_stricmp( cmd, "allowbuild" ) )
+ {
+ ADMP( "^3!allowbuild: ^7player already has building rights\n" );
+ return qtrue;
+ }
+ vic->client->pers.denyBuild = qtrue;
+ vic->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
+ if( vic->client->ps.stats[ STAT_PCLASS ]== PCL_ALIEN_BUILDER0 || vic->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG )
+ {
+ vic->suicideTime = level.time + 1000;
+ }
+ CPx( pids[ 0 ], "cp \"^1You've lost your building rights\"" );
+ AP( va(
+ "print \"^3!denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ ClientUserinfoChanged( pids[ 0 ] );
+ return qtrue;
+}
+
+qboolean G_admin_listadmins( gentity_t *ent, int skiparg )
+{
+ int i, found = 0;
+ char search[ MAX_NAME_LENGTH ] = {""};
+ char s[ MAX_NAME_LENGTH ] = {""};
+ int start = 0;
+ qboolean numeric = qtrue;
+ int drawn = 0;
+ int minlevel = 1;
+
+ if( G_SayArgc() == 3 + skiparg )
+ {
+ G_SayArgv( 2 + skiparg, s, sizeof( s ) );
+ if( s[ 0 ] >= '0' && s[ 0] <= '9' )
+ {
+ minlevel = atoi( s );
+ if( minlevel < 1 )
+ minlevel = 1;
+ }
+ }
+
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( g_admin_admins[ i ]->level >= minlevel )
+ found++;
+ }
+ if( !found )
+ {
+ if( minlevel > 1 )
+ {
+ ADMP( va( "^3!listadmins: ^7no admins level %i or greater found\n", minlevel ) );
+ }
+ else
+ {
+ ADMP( "^3!listadmins: ^7no admins defined\n" );
+ }
+ return qfalse;
+ }
+
+ if( G_SayArgc() >= 2 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, s, sizeof( s ) );
+ for( i = 0; i < sizeof( s ) && s[ i ]; i++ )
+ {
+ if( s[ i ] >= '0' && s[ i ] <= '9' )
+ continue;
+ numeric = qfalse;
+ }
+ if( numeric )
+ {
+ start = atoi( s );
+ if( start > 0 )
+ start -= 1;
+ else if( start < 0 )
+ start = found + start;
+ }
+ else
+ G_SanitiseString( s, search, sizeof( search ) );
+ }
+
+ if( start >= found || start < 0 )
+ start = 0;
+
+ drawn = admin_listadmins( ent, start, search, minlevel );
+
+ if( search[ 0 ] )
+ {
+ if( drawn <= 20 )
+ {
+ ADMP( va( "^3!listadmins:^7 found %d admins level %i or greater matching '%s^7'\n",
+ drawn, minlevel, search ) );
+ }
+ else
+ {
+ ADMP( va( "^3!listadmins:^7 found >20 admins level %i or greater matching '%s^7. Try a more narrow search.'\n",
+ minlevel, search ) );
+ }
+ }
+ else
+ {
+ ADMBP_begin();
+ ADMBP( va( "^3!listadmins:^7 showing admins level %i or greater %d - %d of %d. ",
+ minlevel,
+ ( found ) ? ( start + 1 ) : 0,
+ ( ( start + MAX_ADMIN_LISTITEMS ) > found ) ?
+ found : ( start + MAX_ADMIN_LISTITEMS ),
+ found ) );
+ if( ( start + MAX_ADMIN_LISTITEMS ) < found )
+ {
+ if( minlevel > 1)
+ {
+ ADMBP( va( "run '!listadmins %d %d' to see more",
+ ( start + MAX_ADMIN_LISTITEMS + 1 ), minlevel ) );
+ }
+ else
+ {
+ ADMBP( va( "run '!listadmins %d' to see more",
+ ( start + MAX_ADMIN_LISTITEMS + 1 ) ) );
+ }
+ }
+ ADMBP( "\n" );
+ ADMBP_end();
+ }
+ return qtrue;
+}
+
+qboolean G_admin_listlayouts( gentity_t *ent, int skiparg )
+{
+ char list[ MAX_CVAR_VALUE_STRING ];
+ char map[ MAX_QPATH ];
+ int count = 0;
+ char *s;
+ char layout[ MAX_QPATH ] = { "" };
+ int i = 0;
+
+ if( G_SayArgc( ) == 2 + skiparg )
+ G_SayArgv( 1 +skiparg, map, sizeof( map ) );
+ else
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+
+ count = G_LayoutList( map, list, sizeof( list ) );
+ ADMBP_begin( );
+ ADMBP( va( "^3!listlayouts:^7 %d layouts found for '%s':\n", count, map ) );
+ s = &list[ 0 ];
+ while( *s )
+ {
+ if( *s == ' ' )
+ {
+ ADMBP( va ( " %s\n", layout ) );
+ layout[ 0 ] = '\0';
+ i = 0;
+ }
+ else if( i < sizeof( layout ) - 2 )
+ {
+ layout[ i++ ] = *s;
+ layout[ i ] = '\0';
+ }
+ s++;
+ }
+ if( layout[ 0 ] )
+ ADMBP( va ( " %s\n", layout ) );
+ ADMBP_end( );
+ return qtrue;
+}
+
+qboolean G_admin_listplayers( gentity_t *ent, int skiparg )
+{
+ int i, j;
+ gclient_t *p;
+ char c[ 3 ], t[ 2 ]; // color and team letter
+ char n[ MAX_NAME_LENGTH ] = {""};
+ char n2[ MAX_NAME_LENGTH ] = {""};
+ char n3[ MAX_NAME_LENGTH ] = {""};
+ char lname[ MAX_NAME_LENGTH ];
+ char lname2[ MAX_NAME_LENGTH ];
+ char guid_stub[ 9 ];
+ char aimbot[ 4 ];//ROTAX
+ char muted[ 2 ], denied[ 2 ], dbuilder[ 2 ];
+ int l;
+ char lname_fmt[ 5 ];
+
+ ADMBP_begin();
+ ADMBP( va( "^3!listplayers^7: %d players connected:\n",
+ level.numConnectedClients ) );
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ p = &level.clients[ i ];
+ Q_strncpyz( t, "S", sizeof( t ) );
+ Q_strncpyz( c, S_COLOR_YELLOW, sizeof( c ) );
+ if( p->pers.teamSelection == PTE_HUMANS )
+ {
+ Q_strncpyz( t, "H", sizeof( t ) );
+ Q_strncpyz( c, S_COLOR_BLUE, sizeof( c ) );
+ }
+ else if( p->pers.teamSelection == PTE_ALIENS )
+ {
+ Q_strncpyz( t, "A", sizeof( t ) );
+ Q_strncpyz( c, S_COLOR_RED, sizeof( c ) );
+ }
+
+ if( p->pers.connected == CON_CONNECTING )
+ {
+ Q_strncpyz( t, "C", sizeof( t ) );
+ Q_strncpyz( c, S_COLOR_CYAN, sizeof( c ) );
+ }
+ else if( p->pers.connected != CON_CONNECTED )
+ {
+ continue;
+ }
+
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = p->pers.guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+
+ //ROTAX
+ aimbot[ 0 ] = '\0';
+ if( p->pers.aimbot )
+ Q_strncpyz( aimbot, "AAA", sizeof( aimbot ) );
+ else
+ Q_strncpyz( aimbot, "---", sizeof( aimbot ) );
+
+ muted[ 0 ] = '\0';
+ if( p->pers.muted )
+ {
+ Q_strncpyz( muted, "M", sizeof( muted ) );
+ }
+ denied[ 0 ] = '\0';
+ if( p->pers.denyBuild )
+ {
+ Q_strncpyz( denied, "B", sizeof( denied ) );
+ }
+
+ dbuilder[ 0 ] = '\0';
+ if( p->pers.designatedBuilder )
+ {
+ if( !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) &&
+ G_admin_permission( &g_entities[ i ], ADMF_DBUILDER ) &&
+ G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) )
+ {
+ Q_strncpyz( dbuilder, "P", sizeof( dbuilder ) );
+ }
+ else
+ {
+ Q_strncpyz( dbuilder, "D", sizeof( dbuilder ) );
+ }
+ }
+
+ l = 0;
+ G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) );
+ n[ 0 ] = '\0';
+ for( j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ j ]->guid, p->pers.guid ) )
+ {
+
+ // don't gather aka or level info if the admin is incognito
+ if( ent && G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) )
+ {
+ break;
+ }
+ l = g_admin_admins[ j ]->level;
+ G_SanitiseString( g_admin_admins[ j ]->name, n3, sizeof( n3 ) );
+ if( Q_stricmp( n2, n3 ) )
+ {
+ Q_strncpyz( n, g_admin_admins[ j ]->name, sizeof( n ) );
+ }
+ break;
+ }
+ }
+ lname[ 0 ] = '\0';
+ Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) );
+ for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ )
+ {
+ if( g_admin_levels[ j ]->level == l )
+ {
+ Q_strncpyz( lname, g_admin_levels[ j ]->name, sizeof( lname ) );
+ if( *lname )
+ {
+ G_DecolorString( lname, lname2 );
+ Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is",
+ ( admin_level_maxname + strlen( lname ) - strlen( lname2 ) ) );
+ Com_sprintf( lname2, sizeof( lname2 ), lname_fmt, lname );
+ }
+ break;
+ }
+
+ }
+
+ if( G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) {
+
+ //ROTAX
+ ADMBP( va( "%2i %s%s^7 %-2i ^6%s^7 %s^7 (*%s) ^1%1s%1s%1s^7 %s^7 %s%s^7%s\n",
+ i,
+ c,
+ t,
+ l,
+ aimbot,
+ ( *lname ) ? lname2 : "",
+ guid_stub,
+ muted,
+ dbuilder,
+ denied,
+ p->pers.netname,
+ ( *n ) ? "(a.k.a. " : "",
+ n,
+ ( *n ) ? ")" : ""
+ ) );
+ }
+ else
+ {
+ ADMBP( va( "%2i %s%s^7 ^1%1s%1s%1s^7 %s^7\n",
+ i,
+ c,
+ t,
+ muted,
+ dbuilder,
+ denied,
+ p->pers.netname
+ ) );
+ }
+ }
+ ADMBP_end();
+ return qtrue;
+}
+
+#define MAX_LISTMAPS_MAPS 256
+
+static int SortMaps(const void *a, const void *b)
+{
+ return strcmp(*(char **)a, *(char **)b);
+}
+
+qboolean G_admin_listmaps( gentity_t *ent, int skiparg )
+{
+ char fileList[ 4096 ] = {""};
+ char *fileSort[ MAX_LISTMAPS_MAPS ];
+ char search[ 16 ] = {""};
+ int numFiles;
+ int i;
+ int fileLen = 0;
+ int count = 0;
+ char *filePtr;
+ int rows;
+
+ if( G_SayArgc( ) > 1 + skiparg )
+ {
+ G_SayArgv( skiparg + 1, search, sizeof( search ) );
+ }
+
+ numFiles = trap_FS_GetFileList( "maps/", ".bsp",
+ fileList, sizeof( fileList ) );
+ filePtr = fileList;
+ for( i = 0; i < numFiles && count < MAX_LISTMAPS_MAPS; i++, filePtr += fileLen + 1 )
+ {
+ fileLen = strlen( filePtr );
+ if (fileLen < 5)
+ continue;
+
+ filePtr[ fileLen - 4 ] = '\0';
+
+ if( search[ 0 ] && !strstr( filePtr, search ) )
+ continue;
+
+ fileSort[ count ] = filePtr;
+ count++;
+ }
+
+ qsort(fileSort, count, sizeof(fileSort[ 0 ]), SortMaps);
+
+ rows = count / 3;
+ if ( rows * 3 < count ) rows++;
+
+ ADMBP_begin();
+ for( i = 0; i < rows; i++ )
+ {
+ ADMBP( va( "^7%20s %20s %20s\n",
+ fileSort[ i ],
+ ( rows + i < count ) ? fileSort[ rows + i ] : "",
+ ( rows * 2 + i < count ) ? fileSort[ rows * 2 + i ] : "" ) );
+ }
+ if ( search[ 0 ] )
+ ADMBP( va( "^3!listmaps: ^7found %d maps matching '%s^7'.\n", count, search ) );
+ else
+ ADMBP( va( "^3!listmaps: ^7listing %d maps.\n", count ) );
+
+ ADMBP_end();
+
+ return qtrue;
+}
+
+qboolean G_admin_listrotation( gentity_t *ent, int skiparg )
+{
+ int i, j, statusColor;
+ char *status = '\0';
+
+ extern mapRotations_t mapRotations;
+
+ // Check for an active map rotation
+ if ( !G_MapRotationActive() ||
+ g_currentMapRotation.integer == NOT_ROTATING )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \"^3!rotation: ^7There is no active map rotation on this server\n\"" );
+ return qfalse;
+ }
+
+ // Locate the active map rotation and output its contents
+ for( i = 0; i < mapRotations.numRotations; i++ )
+ {
+ if ( i == g_currentMapRotation.integer )
+ {
+ ADMBP_begin();
+ ADMBP( va( "^3!rotation: ^7%s\n", mapRotations.rotations[ i ].name ) );
+
+ for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ )
+ {
+ if ( G_GetCurrentMap( i ) == j )
+ {
+ statusColor = 3;
+ status = "current slot";
+ }
+ else if ( !G_MapExists( mapRotations.rotations[ i ].maps[ j ].name ) )
+ {
+ statusColor = 1;
+ status = "missing";
+ }
+ else
+ {
+ statusColor = 7;
+ status = "";
+ }
+ ADMBP( va( "^%i%3i %-20s ^%i%s\n", statusColor, j + 1, mapRotations.rotations[ i ].maps[ j ].name, statusColor, status ) );
+ }
+
+ ADMBP_end();
+
+ // No maps were found in the active map rotation
+ if ( mapRotations.rotations[ i ].numMaps < 1 )
+ {
+ trap_SendServerCommand( ent-g_entities, "print \" - ^7Empty!\n\"" );
+ return qfalse;
+ }
+ }
+ }
+
+ if( g_nextMap.string[0] )
+ {
+ ADMP( va ("^5 Next map overriden by g_nextMap to: %s\n", g_nextMap.string ) );
+ }
+
+ return qtrue;
+}
+
+
+qboolean G_admin_showbans( gentity_t *ent, int skiparg )
+{
+ int i, found = 0;
+ int t;
+ char duration[ 32 ];
+ char name_fmt[ 32 ] = { "%s" };
+ char banner_fmt[ 32 ] = { "%s" };
+ int max_name = 1, max_banner = 1;
+ int secs;
+ int start = 0;
+ char filter[ MAX_NAME_LENGTH ];
+ char date[ 11 ];
+ char *made;
+ int j;
+ char n1[ MAX_NAME_LENGTH * 2 ] = {""};
+ char n2[ MAX_NAME_LENGTH * 2 ] = {""};
+ qboolean numeric = qtrue;
+ char *ip_match = NULL;
+ int ip_match_len = 0;
+ char name_match[ MAX_NAME_LENGTH ] = {""};
+ int show_count = 0;
+ qboolean subnetfilter = qfalse;
+
+ t = trap_RealTime( NULL );
+
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ {
+ if( g_admin_bans[ i ]->expires != 0
+ && ( g_admin_bans[ i ]->expires - t ) < 1 )
+ {
+ continue;
+ }
+ found++;
+ }
+
+ if( G_SayArgc() >= 2 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, filter, sizeof( filter ) );
+ if( G_SayArgc() >= 3 + skiparg )
+ {
+ start = atoi( filter );
+ G_SayArgv( 2 + skiparg, filter, sizeof( filter ) );
+ }
+ for( i = 0; i < sizeof( filter ) && filter[ i ] ; i++ )
+ {
+ if( ( filter[ i ] < '0' || filter[ i ] > '9' )
+ && filter[ i ] != '.' && filter[ i ] != '-' )
+ {
+ numeric = qfalse;
+ break;
+ }
+ }
+
+ if (!numeric)
+ {
+ if( filter[ 0 ] != '-' )
+ {
+ G_SanitiseString( filter, name_match, sizeof( name_match) );
+
+ }
+ else
+ {
+ if( !Q_strncmp( filter, "-sub", 4 ) )
+ {
+ subnetfilter = qtrue;
+ }
+ else
+ {
+ ADMP( va( "^3!showbans: ^7invalid argument %s\n", filter ) );
+ return qfalse;
+ }
+ }
+ }
+ else if( strchr( filter, '.' ) != NULL )
+ {
+ ip_match = filter;
+ ip_match_len = strlen(ip_match);
+ }
+ else
+ {
+ start = atoi( filter );
+ filter[0] = '\0';
+ }
+ // showbans 1 means start with ban 0
+ if( start > 0 )
+ start -= 1;
+ else if( start < 0 )
+ start = found + start;
+ }
+
+ if( start >= MAX_ADMIN_BANS || start < 0 )
+ start = 0;
+
+ for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ]
+ && show_count < MAX_ADMIN_SHOWBANS; i++ )
+ {
+ qboolean match = qfalse;
+
+ if (!numeric)
+ {
+ if( !subnetfilter )
+ {
+ G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) );
+ if (strstr( n1, name_match) )
+ match = qtrue;
+ }
+ else
+ {
+ int mask = -1;
+ int dummy;
+ int scanflen = 0;
+ scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask );
+ if( scanflen == 5 && mask < 32 )
+ {
+ match = qtrue;
+ }
+ }
+ }
+
+ if ( ( match ) || !ip_match
+ || Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len) == 0 )
+ {
+ G_DecolorString( g_admin_bans[ i ]->name, n1 );
+ G_DecolorString( g_admin_bans[ i ]->banner, n2 );
+ if( strlen( n1 ) > max_name )
+ {
+ max_name = strlen( n1 );
+ }
+ if( strlen( n2 ) > max_banner )
+ max_banner = strlen( n2 );
+
+ show_count++;
+ }
+ }
+
+ if( start >= found )
+ {
+ ADMP( va( "^3!showbans: ^7there are %d active bans\n", found ) );
+ return qfalse;
+ }
+ ADMBP_begin();
+ show_count = 0;
+ for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ]
+ && show_count < MAX_ADMIN_SHOWBANS; i++ )
+ {
+ if( g_admin_bans[ i ]->expires != 0
+ && ( g_admin_bans[ i ]->expires - t ) < 1 )
+ continue;
+
+ if (!numeric)
+ {
+ if( !subnetfilter )
+ {
+ G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) );
+ if ( strstr ( n1, name_match ) == NULL )
+ continue;
+ }
+ else
+ {
+ int mask = -1;
+ int dummy;
+ int scanflen = 0;
+ scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask );
+ if( scanflen != 5 || mask >= 32 )
+ {
+ continue;
+ }
+ }
+ }
+ else if( ip_match != NULL
+ && Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len ) != 0)
+ continue;
+
+ // only print out the the date part of made
+ date[ 0 ] = '\0';
+ made = g_admin_bans[ i ]->made;
+ for( j = 0; made && *made; j++ )
+ {
+ if( ( j + 1 ) >= sizeof( date ) )
+ break;
+ if( *made == ' ' )
+ break;
+ date[ j ] = *made;
+ date[ j + 1 ] = '\0';
+ made++;
+ }
+
+ secs = ( g_admin_bans[ i ]->expires - t );
+ G_admin_duration( secs, duration, sizeof( duration ) );
+
+ G_DecolorString( g_admin_bans[ i ]->name, n1 );
+ Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is",
+ ( max_name + strlen( g_admin_bans[ i ]->name ) - strlen( n1 ) ) );
+ Com_sprintf( n1, sizeof( n1 ), name_fmt, g_admin_bans[ i ]->name );
+
+ G_DecolorString( g_admin_bans[ i ]->banner, n2 );
+ Com_sprintf( banner_fmt, sizeof( banner_fmt ), "%%%is",
+ ( max_banner + strlen( g_admin_bans[ i ]->banner ) - strlen( n2 ) ) );
+ Com_sprintf( n2, sizeof( n2 ), banner_fmt, g_admin_bans[ i ]->banner );
+
+ ADMBP( va( "%4i %s^7 %-15s %-8s %s^7 %-10s\n \\__ %s\n",
+ ( i + 1 ),
+ n1,
+ g_admin_bans[ i ]->ip,
+ date,
+ n2,
+ duration,
+ g_admin_bans[ i ]->reason ) );
+
+ show_count++;
+ }
+
+ if (!numeric || ip_match)
+ {
+ char matchmethod[50];
+ if( numeric )
+ Com_sprintf( matchmethod, sizeof(matchmethod), "IP" );
+ else if( !subnetfilter )
+ Com_sprintf( matchmethod, sizeof(matchmethod), "name" );
+ else
+ Com_sprintf( matchmethod, sizeof(matchmethod), "ip range size" );
+
+
+ ADMBP( va( "^3!showbans:^7 found %d matching bans by %s. ",
+ show_count,
+ matchmethod ) );
+ }
+ else
+ {
+ ADMBP( va( "^3!showbans:^7 showing bans %d - %d of %d. ",
+ ( found ) ? ( start + 1 ) : 0,
+ ( ( start + MAX_ADMIN_SHOWBANS ) > found ) ?
+ found : ( start + MAX_ADMIN_SHOWBANS ),
+ found ) );
+ }
+
+ if( ( start + MAX_ADMIN_SHOWBANS ) < found )
+ {
+ ADMBP( va( "run !showbans %d %s to see more",
+ ( start + MAX_ADMIN_SHOWBANS + 1 ),
+ (filter[0]) ? filter : "" ) );
+ }
+ ADMBP( "\n" );
+ ADMBP_end();
+ return qtrue;
+}
+
+qboolean G_admin_help( gentity_t *ent, int skiparg )
+{
+ int i;
+ char additional[ MAX_STRING_CHARS ] = "\nThe following non-standard /commands may also be available to you: \n^3";
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ int j = 0;
+ int count = 0;
+
+ ADMBP_begin();
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( G_admin_permission( ent, g_admin_cmds[ i ].flag[ 0 ] ) )
+ {
+ ADMBP( va( "^3!%-12s", g_admin_cmds[ i ].keyword ) );
+ j++;
+ count++;
+ }
+ // show 6 commands per line
+ if( j == 6 )
+ {
+ ADMBP( "\n" );
+ j = 0;
+ }
+ }
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ if( ! admin_command_permission( ent, g_admin_commands[ i ]->command ) )
+ continue;
+ ADMBP( va( "^3!%-12s", g_admin_commands[ i ]->command ) );
+ j++;
+ count++;
+ // show 6 commands per line
+ if( j == 6 )
+ {
+ ADMBP( "\n" );
+ j = 0;
+ }
+ }
+
+ if( ent )
+ strcat( additional, " /builder /say_area" );
+ if( g_publicSayadmins.integer || G_admin_permission( ent, ADMF_ADMINCHAT ) )
+ strcat( additional, " /a /say_admins" );
+ if( g_privateMessages.integer )
+ strcat( additional, " /m" );
+ if( ent && g_actionPrefix.string[0] )
+ strcat( additional, " /me /mt /me_team" );
+ if( ent && g_myStats.integer )
+ strcat( additional, " /mystats" );
+ if( ent && ent->client )
+ {
+ if( ent->client->pers.designatedBuilder )
+ {
+ strcat( additional, " /protect /resign" );
+ }
+ }
+ if( ent && g_allowShare.integer )
+ strcat( additional, " /share /donate" );
+
+ if( count )
+ ADMBP( "\n" );
+ ADMBP( va( "^3!help: ^7%i available commands\n", count ) );
+ ADMBP( "run !help [^3command^7] for help with a specific command.\n" );
+ ADMBP( va( "%s\n", additional ) );
+ ADMBP_end();
+
+ return qtrue;
+ }
+ else
+ {
+ //!help param
+ char param[ MAX_ADMIN_CMD_LEN ];
+ char *cmd;
+
+ G_SayArgv( 1 + skiparg, param, sizeof( param ) );
+ cmd = ( param[0] == '!' ) ? ¶m[1] : ¶m[0];
+ ADMBP_begin();
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( !Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) )
+ {
+ if( !G_admin_permission( ent, g_admin_cmds[ i ].flag[ 0 ] ) )
+ {
+ ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n",
+ g_admin_cmds[ i ].keyword ) );
+ ADMBP_end();
+ return qfalse;
+ }
+ ADMBP( va( "^3!help: ^7help for '!%s':\n",
+ g_admin_cmds[ i ].keyword ) );
+ ADMBP( va( " ^3Function: ^7%s\n", g_admin_cmds[ i ].function ) );
+ ADMBP( va( " ^3Syntax: ^7!%s %s\n", g_admin_cmds[ i ].keyword,
+ g_admin_cmds[ i ].syntax ) );
+ ADMBP( va( " ^3Flag: ^7'%c'\n", g_admin_cmds[ i ].flag[ 0 ] ) );
+ ADMBP_end();
+ return qtrue;
+ }
+ }
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ if( !Q_stricmp( cmd, g_admin_commands[ i ]->command ) )
+ {
+ if( !admin_command_permission( ent, g_admin_commands[ i ]->command ) )
+ {
+ ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n",
+ g_admin_commands[ i ]->command ) );
+ ADMBP_end();
+ return qfalse;
+ }
+ ADMBP( va( "^3!help: ^7help for '%s':\n",
+ g_admin_commands[ i ]->command ) );
+ ADMBP( va( " ^3Description: ^7%s\n", g_admin_commands[ i ]->desc ) );
+ ADMBP( va( " ^3Syntax: ^7!%s\n", g_admin_commands[ i ]->command ) );
+ ADMBP_end();
+ return qtrue;
+ }
+ }
+ ADMBP( va( "^3!help: ^7no help found for '%s'\n", cmd ) );
+ ADMBP_end();
+ return qfalse;
+ }
+}
+
+qboolean G_admin_admintest( gentity_t *ent, int skiparg )
+{
+ int i, l = 0;
+ qboolean found = qfalse;
+ qboolean lname = qfalse;
+
+ if( !ent )
+ {
+ ADMP( "^3!admintest: ^7you are on the console.\n" );
+ return qtrue;
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) )
+ {
+ found = qtrue;
+ break;
+ }
+ }
+
+ if( found )
+ {
+ l = g_admin_admins[ i ]->level;
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ if( g_admin_levels[ i ]->level != l )
+ continue;
+ if( *g_admin_levels[ i ]->name )
+ {
+ lname = qtrue;
+ break;
+ }
+ }
+ }
+ AP( va( "print \"^3!admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"",
+ ent->client->pers.netname,
+ l,
+ ( lname ) ? "(" : "",
+ ( lname ) ? g_admin_levels[ i ]->name : "",
+ ( lname ) ? ")" : "" ) );
+ return qtrue;
+}
+
+qboolean G_admin_allready( gentity_t *ent, int skiparg )
+{
+ int i = 0;
+ gclient_t *cl;
+
+ if( !level.intermissiontime )
+ {
+ ADMP( "^3!allready: ^7this command is only valid during intermission\n" );
+ return qfalse;
+ }
+
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->pers.teamSelection == PTE_NONE )
+ continue;
+
+ cl->readyToExit = 1;
+ }
+ AP( va( "print \"^3!allready:^7 %s^7 says everyone is READY now\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_cancelvote( gentity_t *ent, int skiparg )
+{
+
+ if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] )
+ {
+ ADMP( "^3!cancelvote^7: no vote in progress\n" );
+ return qfalse;
+ }
+ level.voteNo = level.numConnectedClients;
+ level.voteYes = 0;
+ CheckVote( );
+ level.teamVoteNo[ 0 ] = level.numConnectedClients;
+ level.teamVoteYes[ 0 ] = 0;
+ CheckTeamVote( PTE_HUMANS );
+ level.teamVoteNo[ 1 ] = level.numConnectedClients;
+ level.teamVoteYes[ 1 ] = 0;
+ CheckTeamVote( PTE_ALIENS );
+ AP( va( "print \"^3!cancelvote: ^7%s^7 decided that everyone voted No\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_passvote( gentity_t *ent, int skiparg )
+{
+ if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] )
+ {
+ ADMP( "^3!passvote^7: no vote in progress\n" );
+ return qfalse;
+ }
+ level.voteYes = level.numConnectedClients;
+ level.voteNo = 0;
+ CheckVote( );
+ level.teamVoteYes[ 0 ] = level.numConnectedClients;
+ level.teamVoteNo[ 0 ] = 0;
+ CheckTeamVote( PTE_HUMANS );
+ level.teamVoteYes[ 1 ] = level.numConnectedClients;
+ level.teamVoteNo[ 1 ] = 0;
+ CheckTeamVote( PTE_ALIENS );
+ AP( va( "print \"^3!passvote: ^7%s^7 decided that everyone voted Yes\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_pause( gentity_t *ent, int skiparg )
+{
+ if(!level.paused)
+ {
+ AP( va( "print \"^3!pause: ^7%s^7 paused the game.\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ level.paused = qtrue;
+ trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" );
+ }
+ else
+ {
+ // Prevent accidental pause->unpause race conditions by two admins doing !pause at once
+ if( level.pausedTime < 1000 ) return qfalse;
+
+ AP( va( "print \"^3!pause: ^7%s^7 unpaused the game (Paused for %d msec) \n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console",level.pausedTime ) );
+ trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" );
+ level.paused = qfalse;
+ }
+ return qtrue;
+}
+
+qboolean G_admin_spec999( gentity_t *ent, int skiparg )
+{
+ int i;
+ gentity_t *vic;
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ vic = &g_entities[ i ];
+ if( !vic->client )
+ continue;
+ if( vic->client->pers.connected != CON_CONNECTED )
+ continue;
+ if( vic->client->pers.teamSelection == PTE_NONE )
+ continue;
+ if( vic->client->ps.ping == 999 )
+ {
+ G_ChangeTeam( vic, PTE_NONE );
+ AP( va( "print \"^3!spec999: ^7%s^7 moved ^7%s^7 to spectators\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ vic->client->pers.netname ) );
+ }
+ }
+ return qtrue;
+}
+
+qboolean G_admin_register(gentity_t *ent, int skiparg ){
+ int level = 0;
+
+ if( !ent ) return qtrue;
+
+ level = G_admin_level(ent);
+
+ if( level == 0 )
+ level = 1;
+
+ if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) )
+ {
+ ADMP( va( "^3!register: ^7 You cannot register for name protection until you update your client. Please replace your client executable with the one at http://trem.tjw.org/backport/ and reconnect. Updating your client will also allow you to have faster map downloads.\n" ) );
+ return qfalse;
+ }
+
+ if( g_newbieNumbering.integer
+ && g_newbieNamePrefix.string[ 0 ]
+ && !Q_stricmpn ( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string ) ) )
+ {
+ ADMP( va( "^3!register: ^7 You cannot register names that begin with '%s^7'.\n",
+ g_newbieNamePrefix.string ) );
+ return qfalse;
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d %d;",ent - g_entities, level) );
+ ClientUserinfoChanged( ent - g_entities );
+
+ AP( va( "print \"^3!register: ^7%s^7 is now a protected nickname.\n\"", ent->client->pers.netname) );
+
+ return qtrue;
+}
+
+qboolean G_admin_rename( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ];
+ char newname[ MAX_NAME_LENGTH ];
+ char oldname[ MAX_NAME_LENGTH ];
+ char err[ MAX_STRING_CHARS ];
+ char userinfo[ MAX_INFO_STRING ];
+ char *s;
+ gentity_t *victim = NULL;
+
+ if( G_SayArgc() < 3 + skiparg )
+ {
+ ADMP( "^3!rename: ^7usage: !rename [name] [newname]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ s = G_SayConcatArgs( 2 + skiparg );
+ Q_strncpyz( newname, s, sizeof( newname ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!rename: ^7%s\n", err ) );
+ return qfalse;
+ }
+ victim = &g_entities[ pids[ 0 ] ] ;
+ if( !admin_higher( ent, victim ) )
+ {
+ ADMP( "^3!rename: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ if( !G_admin_name_check( victim, newname, err, sizeof( err ) ) )
+ {
+ G_admin_print( ent, va( "^3!rename: Invalid name: ^7%s\n", err ) );
+ return qfalse;
+ }
+ level.clients[ pids[ 0 ] ].pers.nameChanges--;
+ level.clients[ pids[ 0 ] ].pers.nameChangeTime = 0;
+ trap_GetUserinfo( pids[ 0 ], userinfo, sizeof( userinfo ) );
+ s = Info_ValueForKey( userinfo, "name" );
+ Q_strncpyz( oldname, s, sizeof( oldname ) );
+ Info_SetValueForKey( userinfo, "name", newname );
+ trap_SetUserinfo( pids[ 0 ], userinfo );
+ ClientUserinfoChanged( pids[ 0 ] );
+ if( strcmp( oldname, level.clients[ pids[ 0 ] ].pers.netname ) )
+ AP( va( "print \"^3!rename: ^7%s^7 has been renamed to %s^7 by %s\n\"",
+ oldname,
+ level.clients[ pids[ 0 ] ].pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_restart( gentity_t *ent, int skiparg )
+{
+ char layout[ MAX_CVAR_VALUE_STRING ] = { "" };
+ char teampref[ MAX_CVAR_VALUE_STRING ] = { "" };
+ int i;
+ gclient_t *cl;
+
+ if( G_SayArgc( ) > 1 + skiparg )
+ {
+ char map[ MAX_QPATH ];
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ G_SayArgv( skiparg + 1, layout, sizeof( layout ) );
+
+ if( Q_stricmp( layout, "keepteams" ) && Q_stricmp( layout, "keepteamslock" ) && Q_stricmp( layout, "switchteams" ) && Q_stricmp( layout, "switchteamslock" ) )
+ {
+ if( !Q_stricmp( layout, "*BUILTIN*" ) ||
+ trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ),
+ NULL, FS_READ ) > 0 )
+ {
+ trap_Cvar_Set( "g_layouts", layout );
+ }
+ else
+ {
+ ADMP( va( "^3!restart: ^7layout '%s' does not exist\n", layout ) );
+ return qfalse;
+ }
+ }
+ else
+ {
+ strcpy(layout,"");
+ G_SayArgv( skiparg + 1, teampref, sizeof( teampref ) );
+ }
+ }
+
+ if( G_SayArgc( ) > 2 + skiparg )
+ {
+ G_SayArgv( skiparg + 2, teampref, sizeof( teampref ) );
+ }
+
+
+ if( !Q_stricmp( teampref, "keepteams" ) || !Q_stricmp( teampref, "keepteamslock" ) )
+ {
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->pers.teamSelection == PTE_NONE )
+ continue;
+
+ cl->sess.restartTeam = cl->pers.teamSelection;
+ }
+ }
+ else if(!Q_stricmp( teampref, "switchteams" ) || !Q_stricmp( teampref, "switchteamslock" ))
+ {
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->pers.teamSelection == PTE_NONE )
+ continue;
+
+ if( cl->pers.teamSelection == PTE_ALIENS )
+ cl->sess.restartTeam = PTE_HUMANS;
+ else if(cl->pers.teamSelection == PTE_HUMANS )
+ cl->sess.restartTeam = PTE_ALIENS;
+ }
+ }
+
+ if( !Q_stricmp( teampref, "switchteamslock" ) || !Q_stricmp( teampref, "keepteamslock" ) )
+ {
+ trap_Cvar_Set( "g_lockTeamsAtStart", "1" );
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, "map_restart" );
+
+ if(teampref[ 0 ])
+ strcpy(teampref,va( "^7(with teams option: '%s^7')", teampref ));
+
+ G_admin_maplog_result( "R" );
+
+ AP( va( "print \"^3!restart: ^7map restarted by %s %s %s\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ ( layout[ 0 ] ) ? va( "^7(forcing layout '%s^7')", layout ) : "",
+ teampref ) );
+ return qtrue;
+}
+
+qboolean G_admin_nextmap( gentity_t *ent, int skiparg )
+{
+ AP( va( "print \"^3!nextmap: ^7%s^7 decided to load the next map\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ level.lastWin = PTE_NONE;
+ trap_SetConfigstring( CS_WINNER, "Evacuation" );
+ LogExit( va( "nextmap was run by %s",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ G_admin_maplog_result( "N" );
+ return qtrue;
+}
+
+qboolean G_admin_namelog( gentity_t *ent, int skiparg )
+{
+ int i, j;
+ char search[ MAX_NAME_LENGTH ] = {""};
+ char s2[ MAX_NAME_LENGTH ] = {""};
+ char n2[ MAX_NAME_LENGTH ] = {""};
+ char guid_stub[ 9 ];
+ qboolean found = qfalse;
+ int printed = 0;
+
+ if( G_SayArgc() > 1 + skiparg )
+ {
+ G_SayArgv( 1 + skiparg, search, sizeof( search ) );
+ G_SanitiseString( search, s2, sizeof( s2 ) );
+ }
+ ADMBP_begin();
+ for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
+ {
+ if( search[0] )
+ {
+ found = qfalse;
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&
+ g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( strstr( n2, s2 ) )
+ {
+ found = qtrue;
+ break;
+ }
+ }
+ if( !found )
+ continue;
+ }
+ printed++;
+ for( j = 0; j < 8; j++ )
+ guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ];
+ guid_stub[ j ] = '\0';
+ if( g_admin_namelog[ i ]->slot > -1 )
+ ADMBP( "^3" );
+
+ //ROTAXfun
+ //ADMBP( va( "%-2s (*%s) %15s^7",
+ ADMBP( va( "%-2s (*%s) %15s (%3s)^7",
+ (g_admin_namelog[ i ]->slot > -1 ) ?
+ va( "%d", g_admin_namelog[ i ]->slot ) : "-",
+ guid_stub, g_admin_namelog[ i ]->ip, (g_admin_namelog[ i ]->ip2c != NULL) ? g_admin_namelog[ i ]->ip2c->name3 : "---" ) );//ROTAXfun
+ //ROTAXfun
+ //guid_stub, g_admin_namelog[ i ]->ip ) );
+
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&
+ g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ {
+ ADMBP( va( " '%s^7'", g_admin_namelog[ i ]->name[ j ] ) );
+ }
+ ADMBP( "\n" );
+ }
+ ADMBP( va( "^3!namelog:^7 %d recent clients found\n", printed ) );
+ ADMBP_end();
+ return qtrue;
+}
+
+qboolean G_admin_lock( gentity_t *ent, int skiparg )
+{
+ char teamName[2] = {""};
+ pTeam_t team;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!lock: ^7usage: !lock [a|h]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) );
+ if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' )
+ team = PTE_ALIENS;
+ else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' )
+ team = PTE_HUMANS;
+ else
+ {
+ ADMP( va( "^3!lock: ^7invalid team\"%c\"\n", teamName[0] ) );
+ return qfalse;
+ }
+
+ if( team == PTE_ALIENS )
+ {
+ if( level.alienTeamLocked )
+ {
+ ADMP( "^3!lock: ^7Alien team is already locked\n" );
+ return qfalse;
+ }
+ else
+ level.alienTeamLocked = qtrue;
+ }
+ else if( team == PTE_HUMANS ) {
+ if( level.humanTeamLocked )
+ {
+ ADMP( "^3!lock: ^7Human team is already locked\n" );
+ return qfalse;
+ }
+ else
+ level.humanTeamLocked = qtrue;
+ }
+
+ AP( va( "print \"^3!lock: ^7%s team has been locked by %s\n\"",
+ ( team == PTE_ALIENS ) ? "Alien" : "Human",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_unlock( gentity_t *ent, int skiparg )
+{
+ char teamName[2] = {""};
+ pTeam_t team;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!unlock: ^7usage: !unlock [a|h]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) );
+ if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' )
+ team = PTE_ALIENS;
+ else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' )
+ team = PTE_HUMANS;
+ else
+ {
+ ADMP( va( "^3!unlock: ^7invalid team\"%c\"\n", teamName[0] ) );
+ return qfalse;
+ }
+
+ if( team == PTE_ALIENS )
+ {
+ if( !level.alienTeamLocked )
+ {
+ ADMP( "^3!unlock: ^7Alien team is not currently locked\n" );
+ return qfalse;
+ }
+ else
+ level.alienTeamLocked = qfalse;
+ }
+ else if( team == PTE_HUMANS ) {
+ if( !level.humanTeamLocked )
+ {
+ ADMP( "^3!unlock: ^7Human team is not currently locked\n" );
+ return qfalse;
+ }
+ else
+ level.humanTeamLocked = qfalse;
+ }
+
+ AP( va( "print \"^3!unlock: ^7%s team has been unlocked by %s\n\"",
+ ( team == PTE_ALIENS ) ? "Alien" : "Human",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_designate( gentity_t *ent, int skiparg )
+{
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ];
+ char command[ MAX_ADMIN_CMD_LEN ], *cmd;
+ gentity_t *vic;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!designate: ^7usage: designate [name|slot#]\n" );
+ return qfalse;
+ }
+ G_SayArgv( skiparg, command, sizeof( command ) );
+ cmd = command;
+ if( cmd && *cmd == '!' )
+ cmd++;
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!designate: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) &&
+ !Q_stricmp( cmd, "undesignate" ) )
+ {
+ ADMP( "^3!mute: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ vic = &g_entities[ pids[ 0 ] ];
+ if( vic->client->pers.designatedBuilder == qtrue )
+ {
+ if( !Q_stricmp( cmd, "designate" ) )
+ {
+ ADMP( "^3!designate: ^7player is already designated builder\n" );
+ return qtrue;
+ }
+ vic->client->pers.designatedBuilder = qfalse;
+ CPx( pids[ 0 ], "cp \"^1Your designation has been revoked\"" );
+ AP( va(
+ "print \"^3!designate: ^7%s^7's designation has been revoked by %s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ G_CheckDBProtection( );
+ }
+ else
+ {
+ if( !Q_stricmp( cmd, "undesignate" ) )
+ {
+ ADMP( "^3!undesignate: ^7player is not currently designated builder\n" );
+ return qtrue;
+ }
+ vic->client->pers.designatedBuilder = qtrue;
+ CPx( pids[ 0 ], "cp \"^1You've been designated\"" );
+ AP( va( "print \"^3!designate: ^7%s^7 has been designated by ^7%s\n\"",
+ vic->client->pers.netname,
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );
+ }
+ ClientUserinfoChanged( pids[ 0 ] );
+ return qtrue;
+}
+
+ //!Warn by Gate (Daniel Evans)
+qboolean G_admin_warn( gentity_t *ent, int skiparg )
+{//mostly copy and paste with the proper lines altered from !mute and !kick
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
+ int minargc;
+ gentity_t *vic;
+
+ minargc = 3 + skiparg;
+ if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!warn: ^7usage: warn [name] [reason]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ reason = G_SayConcatArgs( 2 + skiparg );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!warn: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) )
+ {
+ ADMP( "^3!warn: ^7sorry, but your intended victim has a higher admin"
+ " level than you.\n" );
+ return qfalse;
+ }
+
+ vic = &g_entities[ pids[ 0 ] ];
+ //next line is the onscreen warning
+ CPx( pids[ 0 ],va("cp \"^1You have been warned by admin!\n^1 %s%s\"",(*reason)? "REASON: " : "" ,(*reason)? reason : "") );//ROTAX trochu umazano a zmeneno
+ AP( va( "print \"^3!warn: ^7%s^7 has been warned: %s by %s \n\"",//ROTAX trochu umazano
+ vic->client->pers.netname, (*reason) ? reason : "his current activity",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );//console announcement
+ return qtrue;
+}
+
+qboolean G_admin_putmespec( gentity_t *ent, int skiparg )
+{
+ if( !ent )
+ {
+ ADMP( "!specme: sorry, but console isn't allowed on the spectators team\n");
+ return qfalse;
+ }
+
+ if(ent->client->pers.teamSelection == PTE_NONE)
+ return qfalse;
+
+ //guard against build timer exploit
+ if( ent->client->pers.teamSelection != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR &&
+ ( 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 )
+ {
+ ADMP("!specme: You cannot leave your team until the build timer expires");
+ return qfalse;
+ }
+
+ G_ChangeTeam( ent, PTE_NONE );
+ AP( va("print \"^3!specme: ^7%s^7 decided to join the spectators\n\"", ent->client->pers.netname ) );
+ return qtrue;
+}
+
+qboolean G_admin_buildlog( gentity_t *ent, int skiparg )
+{
+#define LOG_DISPLAY_LENGTH 10
+ buildHistory_t *ptr;
+ gentity_t *builder = NULL;
+ int skip = 0, start = 0, lastID = -1, firstID = -1, i, len, matchlen = 0;
+ pTeam_t team = PTE_NONE;
+ char message[ MAX_STRING_CHARS ], *teamchar;
+ char *name, *action, *buildablename, markstring[ MAX_STRING_CHARS ];
+ if( !g_buildLogMaxLength.integer )
+ {
+ ADMP( "^3!buildlog: ^7build logging is disabled" );
+ return qfalse;
+ }
+ if( G_SayArgc( ) >= 2 + skiparg )
+ {
+ for( i = 1; i + skiparg < G_SayArgc( ); i++ )
+ {
+ char argbuf[ 64 ], err[ MAX_STRING_CHARS ];
+ int x = 0, pids[ MAX_CLIENTS ];
+ G_SayArgv( i + skiparg, argbuf, sizeof argbuf );
+ switch( argbuf[ 0 ])
+ {
+ case 'x':
+ x = 1;
+ default:
+ skip = atoi( argbuf + x );
+ start = 0;
+ break;
+ case '#':
+ start = atoi( argbuf + 1 );
+ skip = 0;
+ break;
+ case '-':
+ if(G_ClientNumbersFromString(argbuf + 1, pids) != 1)
+ {
+ G_MatchOnePlayer(pids, err, sizeof(err));
+ ADMP(va("^3!revert: ^7%s\n", err));
+ return qfalse;
+ }
+ builder = g_entities + *pids;
+ break;
+ case 'A':
+ case 'a':
+ team = PTE_ALIENS;
+ break;
+ case 'H':
+ case 'h':
+ team = PTE_HUMANS;
+ break;
+ }
+ }
+ }
+ // !buildlog can be abused, so let everyone know when it is used
+ /*//ROTAX
+ AP( va( "print \"^3!buildlog: ^7%s^7 requested a log of recent building"
+ " activity\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );*/
+ len = G_CountBuildLog( ); // also clips the log if too long
+ if( !len )
+ {
+ ADMP( "^3!buildlog: ^7no build log found\n" );
+ return qfalse;
+ }
+ if( start )
+ {
+ // set skip based on start
+ for( ptr = level.buildHistory; ptr && ptr->ID != start;
+ ptr = ptr->next, skip++ );
+ if( !ptr )
+ {
+ ADMP( "^3!buildlog: ^7log ID not found\n" );
+ skip = 0;
+ }
+ }
+ // ensure skip is a useful value
+ if( skip > len - LOG_DISPLAY_LENGTH )
+ skip = len - LOG_DISPLAY_LENGTH;
+ *message = '\0';
+ // skip to start entry
+ for( ptr = level.buildHistory, i = len; ptr && i > len - skip;
+ ptr = ptr->next )
+ {
+ // these checks could perhaps be done more efficiently but they are cheap
+ // in processor time so I'm not worrying
+ if( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) )
+ continue;
+ if( builder && builder != ptr->ent )
+ continue;
+ matchlen++;
+ i--;
+ }
+ for( ; i + LOG_DISPLAY_LENGTH > len - skip && i > 0; i--, ptr = ptr->next )
+ {
+ if( !ptr )
+ break; // run out of log
+ *markstring = '\0'; // reinit markstring
+ // check team
+ if( ( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) )
+ || ( builder && builder != ptr->ent ) )
+ {
+ skip++; // loop an extra time because we skipped one
+ continue;
+ }
+ if( lastID < 0 )
+ lastID = ptr->ID;
+ firstID = ptr->ID;
+ matchlen++;
+ // set name to the ent's current name or last recorded name
+ if( ptr->ent )
+ {
+ if( ptr->ent->client )
+ name = ptr->ent->client->pers.netname;
+ else
+ name = "<world>"; // any non-client action
+ }
+ else
+ name = ptr->name;
+ switch( ptr->fate )
+ {
+ case BF_BUILT:
+ action = "^2built^7 a";
+ break;
+ case BF_DECONNED:
+ action = "^3DECONSTRUCTED^7 a";
+ break;
+ case BF_DESTROYED:
+ action = "destroyed a";
+ break;
+ case BF_TEAMKILLED:
+ action = "^1TEAMKILLED^7 a";
+ break;
+ default:
+ action = "\0"; // erm
+ break;
+ }
+ // handle buildables removed by markdecon
+ if( ptr->marked )
+ {
+ buildHistory_t *mark;
+ int j, markdecon[ BA_NUM_BUILDABLES ], and = 2;
+ char bnames[ 32 ], *article;
+ mark = ptr;
+ // count the number of buildables
+ memset( markdecon, 0, sizeof( markdecon ) );
+ while( ( mark = mark->marked ) )
+ markdecon[ mark->buildable ]++;
+ // reverse order makes grammar easier
+ for( j = BA_NUM_BUILDABLES; j >= 0; j-- )
+ {
+ buildablename = BG_FindHumanNameForBuildable( j );
+ // plural is easy
+ if( markdecon[ j ] > 1 )
+ Com_sprintf( bnames, 32, "%d %ss", markdecon[ j ], buildablename );
+ // use an appropriate article
+ else if( markdecon[ j ] == 1 )
+ {
+ if( BG_FindUniqueTestForBuildable( j ) )
+ article = "the"; // if only one
+ else if( strchr( "aeiouAEIOU", *buildablename ) )
+ article = "an"; // if first char is vowel
+ else
+ article = "a";
+ Com_sprintf( bnames, 32, "%s %s", article, buildablename );
+ }
+ else
+ continue; // none of this buildable
+ // C grammar: x, y, and z
+ // the integer and is 2 initially, the test means it is used on the
+ // second sprintf only, the reverse order makes this second to last
+ // the comma is only printed if there is already some markstring i.e.
+ // not the first time ( which would put it on the end of the string )
+ Com_sprintf( markstring, sizeof( markstring ), "%s%s %s%s", bnames,
+ ( *markstring ) ? "," : "", ( and-- == 1 ) ? "and " : "", markstring );
+ }
+ }
+ buildablename = BG_FindHumanNameForBuildable( ptr->buildable );
+ switch( BG_FindTeamForBuildable( ptr->buildable ) )
+ {
+ case PTE_ALIENS:
+ teamchar = "^1A";
+ break;
+ case PTE_HUMANS:
+ teamchar = "^4H";
+ break;
+ default:
+ teamchar = " "; // space so it lines up neatly
+ break;
+ }
+ // prepend the information to the string as we go back in buildhistory
+ // so the earliest events are at the top
+ Com_sprintf( message, MAX_STRING_CHARS, "%3d %s^7 %s^7 %s%s %s%s%s\n%s",
+ ptr->ID, teamchar, name, action,
+ ( strchr( "aeiouAEIOU", buildablename[ 0 ] ) ) ? "n" : "",
+ buildablename, ( markstring[ 0 ] ) ? ", removing " : "",
+ markstring, message );
+ }
+ for( ; ptr; ptr = ptr->next )
+ {
+ if( builder && builder != ptr->ent )
+ continue;
+ if( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) )
+ continue;
+ matchlen++;
+ }
+ if( matchlen )
+ ADMP( va( "%s^3!buildlog: showing log entries %d - %d of %d\n", message,
+ firstID, lastID, matchlen ) );
+ else
+ ADMP( "^3!buildlog: ^7no log entries match those criteria\n" );
+ return qtrue;
+}
+
+qboolean G_admin_revert( gentity_t *ent, int skiparg )
+{
+ int i = 0, j = 0, repeat = 1, ID = 0, len, matchlen=0;
+ pTeam_t team = PTE_NONE;
+ qboolean force = qfalse, reached = qfalse;
+ gentity_t *builder = NULL, *targ;
+ buildHistory_t *ptr, *tmp, *mark, *prev;
+ vec3_t dist;
+ char argbuf[ 64 ], *name, *bname, *action, *article;
+ len = G_CountBuildLog( );
+ if( !len )
+ {
+ ADMP( "^3!revert: ^7no build log found\n" );
+ return qfalse;
+ }
+ if( G_SayArgc( ) < 2 + skiparg )
+ {
+ ADMP( "^3!revert: ^7usage: !revert (^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)\n" );
+ return qfalse;
+ }
+ for( i = 1; i + skiparg < G_SayArgc( ); i++ )
+ {
+ char arg[ 64 ], err[ MAX_STRING_CHARS ];
+ int pids[ MAX_CLIENTS ];
+ G_SayArgv( i + skiparg, arg, sizeof arg );
+ switch( arg[ 0 ])
+ {
+ case 'x':
+ repeat = atoi( arg + 1 );
+ break;
+ case '#':
+ ID = atoi( arg + 1 );
+ break;
+ case '-':
+ if(G_ClientNumbersFromString(arg + 1, pids) != 1)
+ {
+ G_MatchOnePlayer(pids, err, sizeof err);
+ ADMP(va("^3!revert: ^7%s\n", err));
+ return qfalse;
+ }
+ builder = g_entities + *pids;
+ break;
+ case 'A':
+ case 'a':
+ team = PTE_ALIENS;
+ break;
+ case 'H':
+ case 'h':
+ team = PTE_HUMANS;
+ break;
+ case '!':
+ force = qtrue;
+ break;
+ default:
+ ADMP( "^3!revert: ^7usage: !revert (^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)\n" );
+ return qfalse;
+ }
+ }
+ if( repeat > 25 )
+ {
+ ADMP( "^3!revert: ^7to avoid flooding, can only revert 25 builds at a time\n" );
+ repeat = 25;
+ }
+ for( i = 0, ptr = prev = level.buildHistory; repeat > 0; repeat--, j = 0 )
+ {
+ if( !ptr )
+ break; // run out of bhist
+ if( !reached && ID )
+ {
+ if( ptr->ID == ID )
+ reached = qtrue;
+ else
+ {
+ prev = ptr;
+ ptr = ptr->next;
+ repeat++;
+ continue;
+ }
+ }
+ if( ( team != PTE_NONE &&
+ team != BG_FindTeamForBuildable( ptr->buildable ) ) ||
+ ( builder && builder != ptr->ent ))
+ {
+ // team doesn't match, so skip this ptr and reset prev
+ prev = ptr;
+ ptr = ptr->next;
+ // we don't want to count this one so counteract the decrement by the for
+ repeat++;
+ continue;
+ }
+ // get the ent's current or last recorded name
+ if( ptr->ent )
+ {
+ if( ptr->ent->client )
+ name = ptr->ent->client->pers.netname;
+ else
+ name = "<world>"; // non-client actions
+ }
+ else
+ name = ptr->name;
+ bname = BG_FindHumanNameForBuildable( ptr->buildable );
+ action = "";
+ switch( ptr->fate )
+ {
+ case BF_BUILT:
+ action = "^2build^7";
+ for( j = MAX_CLIENTS, targ = g_entities + j;
+ j < level.num_entities; j++, targ++ )
+ {
+ // easy checks first to save time
+ if( targ->s.eType != ET_BUILDABLE )
+ continue;
+ if( targ->s.modelindex != ptr->buildable )
+ continue;
+ VectorSubtract( targ->s.pos.trBase, ptr->origin, dist );
+#define FIND_BUILDABLE_TOLERANCE 5
+ if( VectorLength( dist ) > FIND_BUILDABLE_TOLERANCE )
+ continue; // number is somewhat arbitrary, watch for false pos/neg
+ // if we didn't continue then it's this one, unlink it but we can't
+ // free it yet, because the markdecon buildables might not place
+ trap_UnlinkEntity( targ );
+ break;
+ }
+ // if there are marked buildables to replace, and we aren't overriding
+ // space check, check they can fit before acting
+ if( ptr->marked && !force )
+ {
+ for( mark = ptr->marked; mark; mark = mark->marked )
+ if( !G_RevertCanFit( mark ) )
+ {
+ trap_LinkEntity( targ ); // put it back, we failed
+ // scariest sprintf ever:
+ Com_sprintf( argbuf, sizeof argbuf, "%s%s%s%s%s%s%s!",
+ ( repeat > 1 ) ? "x" : "", ( repeat > 1 ) ? va( "%d ", repeat ) : "",
+ ( ID ) ? "#" : "", ( ID ) ? va( "%d ", ptr->ID ) : "",
+ ( builder ) ? "-" : "", ( builder ) ? va( "%d ", builder - g_entities ) : "",
+ ( team == PTE_ALIENS ) ? "a " : ( team == PTE_HUMANS ) ? "h " : "" );
+ ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would conflict with "
+ "another buildable, use ^3!revert %s ^7to override\n", action, argbuf ) );
+ return qfalse;
+ }
+ }
+ // if we haven't returned yet then we're good to go, free it
+ G_FreeEntity( targ );
+ // put the marked buildables back and mark them again
+ if( ptr->marked ) // there may be a more efficient way of doing this
+ {
+ for( mark = ptr->marked; mark; mark = mark->marked )
+ G_SpawnRevertedBuildable( mark, qtrue );
+ }
+ break;
+ case BF_DECONNED:
+ if( !action[ 0 ] ) action = "^3deconstruction^7";
+ case BF_TEAMKILLED:
+ if( !action[ 0 ] ) action ="^1TEAMKILL^7";
+ case BF_DESTROYED:
+ if( !action[ 0 ] ) action = "destruction";
+ // if we're not overriding and the replacement can't fit, as before
+ if( !force && !G_RevertCanFit( ptr ) )
+ {
+ Com_sprintf( argbuf, sizeof argbuf, "%s%s%s%s%s%s%s!",
+ ( repeat > 1 ) ? "x" : "", ( repeat > 1 ) ? va( "%d ", repeat ) : "",
+ ( ID ) ? "#" : "", ( ID ) ? va( "%d ", ptr->ID ) : "",
+ ( builder ) ? "-" : "", ( builder ) ? va( "%d ", builder - g_entities ) : "",
+ ( team == PTE_ALIENS ) ? "a " : ( team == PTE_HUMANS ) ? "h " : "" );
+ ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would "
+ "conflict with another buildable, use ^3!revert %s ^7to override\n",
+ action, argbuf ) );
+ return qfalse;
+ }
+ // else replace it but don't mark it ( it might have been marked before
+ // but it isn't that important )
+ G_SpawnRevertedBuildable( ptr, qfalse );
+ break;
+ default:
+ // if this happens something has gone wrong
+ ADMP( "^3!revert: ^7incomplete or corrupted build log entry\n" );
+ /* quarantine and dispose of the log, it's dangerous
+ trap_Cvar_Set( "g_buildLogMaxLength", "0" );
+ G_CountBuildLog( );
+ */
+ return qfalse;
+ }
+ if( j == level.num_entities )
+ {
+ ADMP( va( "^3!revert: ^7could not find logged buildable #%d\n", ptr->ID ));
+ prev = ptr;
+ ptr = ptr->next;
+ continue;
+ }
+ // this is similar to the buildlog stuff
+ if( BG_FindUniqueTestForBuildable( ptr->buildable ) )
+ article = "the";
+ else if( strchr( "aeiouAEIOU", *bname ) )
+ article = "an";
+ else
+ article = "a";
+ AP( va( "print \"%s^7 reverted %s^7'%s %s of %s %s\n\"",
+ ( ent ) ? G_admin_adminPrintName( ent ) : "console",
+ name, strchr( "Ss", name[ strlen( name ) - 1 ] ) ? "" : "s",
+ action, article, bname ) );
+ matchlen++;
+ // remove the reverted entry
+ // ptr moves on, prev just readjusts ->next unless it is about to be
+ // freed, in which case it is forced to move on too
+ tmp = ptr;
+ if( ptr == level.buildHistory )
+ prev = level.buildHistory = ptr = ptr->next;
+ else
+ prev->next = ptr = ptr->next;
+ G_Free( tmp );
+ }
+ if( ID && !reached )
+ {
+ ADMP( "^3!revert: ^7no buildlog entry with that ID\n" );
+ return qfalse;
+ }
+
+ if( !matchlen )
+ {
+ ADMP( "^3!revert: ^7no log entries match those criteria\n" );
+ return qfalse;
+ }
+ else
+ {
+ ADMP( va( "^3!revert: ^7reverted %d buildlog events\n", matchlen ) );
+ }
+
+ return qtrue;
+}
+
+void G_Unescape( char *input, char *output, int len );
+qboolean G_StringReplaceCvars( char *input, char *output, int len );
+
+qboolean G_admin_info( gentity_t *ent, int skiparg )
+{
+ fileHandle_t infoFile;
+ int length;
+ char filename[ MAX_OSPATH ], message[ MAX_STRING_CHARS ];
+ if( G_SayArgc() == 2 + skiparg )
+ G_SayArgv( 1 + skiparg, filename, sizeof( filename ) );
+ else if( G_SayArgc() == 1 + skiparg )
+ Q_strncpyz( filename, "default", sizeof( filename ) );
+ else
+ {
+ ADMP( "^3!info: ^7usage: ^3!info ^7(^5subject^7)\n" );
+ return qfalse;
+ }
+ Com_sprintf( filename, sizeof( filename ), "info/info-%s.txt", filename );
+ length = trap_FS_FOpenFile( filename, &infoFile, FS_READ );
+ if( length <= 0 || !infoFile )
+ {
+ trap_FS_FCloseFile( infoFile );
+ ADMP( "^3!info: ^7no relevant information is available\n" );
+ return qfalse;
+ }
+ else
+ {
+ int i;
+ char *cr;
+ trap_FS_Read( message, sizeof( message ), infoFile );
+ if( length < sizeof( message ) )
+ message[ length ] = '\0';
+ else
+ message[ sizeof( message ) - 1 ] = '\0';
+ trap_FS_FCloseFile( infoFile );
+ // strip carriage returns for windows platforms
+ while( ( cr = strchr( message, '\r' ) ) )
+ memmove( cr, cr + 1, strlen( cr + 1 ) + 1 );
+#define MAX_INFO_PARSE_LOOPS 100
+ for( i = 0; i < MAX_INFO_PARSE_LOOPS &&
+ G_StringReplaceCvars( message, message, sizeof( message ) ); i++ );
+ G_Unescape( message, message, sizeof( message ) );
+ if( i == MAX_INFO_PARSE_LOOPS )
+ G_Printf( S_COLOR_YELLOW "WARNING: %s exceeds MAX_INFO_PARSE_LOOPS\n", filename );
+ ADMP( va( "%s\n", message ) );
+ return qtrue;
+ }
+}
+
+void G_Unescape( char *input, char *output, int len )
+{
+ // \n -> newline, \%c -> %c
+ // output is terminated at output[len - 1]
+ // it's OK for input to equal output, because our position in input is always
+ // equal or greater than our position in output
+ // however, if output is later in the same string as input, a crash is pretty
+ // much inevitable
+ int i, j;
+ for( i = j = 0; input[i] && j + 1 < len; i++, j++ )
+ {
+ if( input[i] == '\\' )
+ {
+ if( !input[++i] )
+ {
+ output[j] = '\0';
+ return;
+ }
+ else if( input[i] == 'n' )
+ output[j] = '\n';
+ else
+ output[j] = input[i];
+ }
+ else
+ output[j] = input[i];
+ }
+ output[j] = '\0';
+}
+
+qboolean G_StringReplaceCvars( char *input, char *output, int len )
+{
+ int i, outNum = 0;
+ char cvarName[ 64 ], cvarValue[ MAX_CVAR_VALUE_STRING ];
+ char *outputBuffer;
+ qboolean doneAnything = qfalse;
+ if( len <= 0 )
+ return qfalse;
+ // use our own internal buffer in case output == input
+ outputBuffer = G_Alloc( len );
+ len -= 1; // fit in a terminator
+ while( *input && outNum < len )
+ {
+ if( *input == '\\' && input[1] && outNum < len - 1 )
+ {
+ outputBuffer[ outNum++ ] = *input++;
+ outputBuffer[ outNum++ ] = *input++;
+ }
+ else if( *input == '$' )
+ {
+ doneAnything = qtrue;
+ input++;
+ if( *input == '{' )
+ input++;
+ for( i = 0; *input && ( isalnum( *input ) || *input == '_' ) &&
+ i < 63; i++ )
+ cvarName[ i ] = *input++;
+ cvarName[ i ] = '\0';
+ if( *input == '}' )
+ input++;
+ trap_Cvar_VariableStringBuffer( cvarName, cvarValue, sizeof( cvarValue ) );
+ if( cvarValue[ 0 ] )
+ {
+ for( i = 0; cvarValue[ i ] && outNum < len; i++ )
+ outputBuffer[ outNum++ ] = cvarValue[ i ];
+ }
+ }
+ else
+ outputBuffer[ outNum++ ] = *input++;
+ }
+ outputBuffer[ outNum ] = '\0';
+ Q_strncpyz( output, outputBuffer, len );
+ G_Free( outputBuffer );
+ return doneAnything;
+}
+
+/*
+================
+ G_admin_print
+
+ This function facilitates the ADMP define. ADMP() is similar to CP except
+ that it prints the message to the server console if ent is not defined.
+================
+*/
+void G_admin_print( gentity_t *ent, char *m )
+{
+ if( ent )
+ trap_SendServerCommand( ent - level.gentities, va( "print \"%s\"", m ) );
+ else
+ {
+ char m2[ MAX_STRING_CHARS ];
+ if( !trap_Cvar_VariableIntegerValue( "com_ansiColor" ) )
+ {
+ G_DecolorString( m, m2 );
+ G_Printf( m2 );
+ }
+ else
+ G_Printf( m );
+ }
+}
+
+void G_admin_buffer_begin()
+{
+ g_bfb[ 0 ] = '\0';
+}
+
+void G_admin_buffer_end( gentity_t *ent )
+{
+ ADMP( g_bfb );
+}
+
+void G_admin_buffer_print( gentity_t *ent, char *m )
+{
+ // 1022 - strlen("print 64 \"\"") - 1
+ if( strlen( m ) + strlen( g_bfb ) >= 1009 )
+ {
+ ADMP( g_bfb );
+ g_bfb[ 0 ] = '\0';
+ }
+ Q_strcat( g_bfb, sizeof( g_bfb ), m );
+}
+
+
+void G_admin_cleanup()
+{
+ int i = 0;
+
+ for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ )
+ {
+ G_Free( g_admin_levels[ i ] );
+ g_admin_levels[ i ] = NULL;
+ }
+ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ )
+ {
+ G_Free( g_admin_admins[ i ] );
+ g_admin_admins[ i ] = NULL;
+ }
+ for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
+ {
+ G_Free( g_admin_bans[ i ] );
+ g_admin_bans[ i ] = NULL;
+ }
+ for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ )
+ {
+ G_Free( g_admin_commands[ i ] );
+ g_admin_commands[ i ] = NULL;
+ }
+}
+
+qboolean G_admin_L0(gentity_t *ent, int skiparg ){
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ] = {""};
+ char testname[ MAX_NAME_LENGTH ] = {""};
+ char err[ MAX_STRING_CHARS ];
+ qboolean numeric = qtrue;
+ int i;
+ int id = -1;
+ gentity_t *vic;
+
+ if( G_SayArgc() < 2 + skiparg )
+ {
+ ADMP( "^3!L0: ^7usage: !L0 [name|slot#|admin#]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, testname, sizeof( testname ) );
+ G_SanitiseString( testname, name, sizeof( name ) );
+ for( i = 0; i < sizeof( name ) && name[ i ] ; i++ )
+ {
+ if( name[ i ] < '0' || name[ i ] > '9' )
+ {
+ numeric = qfalse;
+ break;
+ }
+ }
+
+ if( numeric )
+ {
+ id = atoi( name );
+ }
+ else
+ {
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!L0: ^7%s\n", err ) );
+ return qfalse;
+ }
+ id = pids[ 0 ];
+ }
+
+ if (id >= 0 && id < level.maxclients)
+ {
+ vic = &g_entities[ id ];
+ if( !vic || !(vic->client) || vic->client->pers.connected != CON_CONNECTED )
+ {
+ ADMP( "^3!L0:^7 no one connected by that slot number\n" );
+ return qfalse;
+ }
+
+ if( G_admin_level( vic ) != 1 )
+ {
+ ADMP( "^3!L0:^7 intended victim is not level 1\n" );
+ return qfalse;
+ }
+ }
+ else if (id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS
+ && g_admin_admins[ id - MAX_CLIENTS ] )
+ {
+ if( g_admin_admins[ id - MAX_CLIENTS ]->level != 1 )
+ {
+ ADMP( "^3!L0:^7 intended victim is not level 1\n" );
+ return qfalse;
+ }
+ }
+ else
+ {
+ ADMP( "^3!L0:^7 no match. use !listplayers or !listadmins to "
+ "find an appropriate number to use instead of name.\n" );
+ return qfalse;
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "!setlevel %d 0;", id ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_L1(gentity_t *ent, int skiparg ){
+ int pids[ MAX_CLIENTS ];
+ char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
+ int minargc;
+
+ minargc = 2 + skiparg;
+
+ if( G_SayArgc() < minargc )
+ {
+ ADMP( "^3!L1: ^7usage: !L1 [name]\n" );
+ return qfalse;
+ }
+ G_SayArgv( 1 + skiparg, name, sizeof( name ) );
+ reason = G_SayConcatArgs( 2 + skiparg );
+ if( G_ClientNumbersFromString( name, pids ) != 1 )
+ {
+ G_MatchOnePlayer( pids, err, sizeof( err ) );
+ ADMP( va( "^3!L1: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( G_admin_level(&g_entities[ pids[ 0 ] ] )>0 )
+ {
+ ADMP( "^3!L1: ^7Sorry, but that person is already higher than level 0.\n" );
+ return qfalse;
+ }
+
+ trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d 1;", pids[ 0 ] ) );
+ return qtrue;
+}
\ No newline at end of file |