summaryrefslogtreecommitdiff
path: root/src/game/g_admin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/g_admin.c')
-rw-r--r--src/game/g_admin.c3277
1 files changed, 3277 insertions, 0 deletions
diff --git a/src/game/g_admin.c b/src/game/g_admin.c
new file mode 100644
index 0000000..e753f19
--- /dev/null
+++ b/src/game/g_admin.c
@@ -0,0 +1,3277 @@
+/*
+===========================================================================
+Copyright (C) 2000-2009 Darklegion Development
+
+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, qfalse, "ban",
+ "change the duration or reason of a ban. duration is specified as "
+ "numbers followed by units 'w' (weeks), 'd' (days), 'h' (hours) or "
+ "'m' (minutes), or seconds if no units are specified. if the duration is"
+ " preceded by a + or -, the ban duration will be extended or shortened by"
+ " the specified amount",
+ "[^3ban#^7] (^5/mask^7) (^5duration^7) (^5reason^7)"
+ },
+
+ {"adminhelp", G_admin_adminhelp, qtrue, "adminhelp",
+ "display admin commands available to you or help on a specific command",
+ "(^5command^7)"
+ },
+
+ {"admintest", G_admin_admintest, qfalse, "admintest",
+ "display your current admin level",
+ ""
+ },
+
+ {"allowbuild", G_admin_denybuild, qfalse, "denybuild",
+ "restore a player's ability to build",
+ "[^3name|slot#^7]"
+ },
+
+ {"allready", G_admin_allready, qfalse, "allready",
+ "makes everyone ready in intermission",
+ ""
+ },
+
+ {"ban", G_admin_ban, qfalse, "ban",
+ "ban a player by IP and GUID with an optional expiration time and reason."
+ " duration 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(/mask)^7] (^5duration^7) (^5reason^7)"
+ },
+
+ {"builder", G_admin_builder, qtrue, "builder",
+ "show who built a structure",
+ ""
+ },
+
+ {"buildlog", G_admin_buildlog, qfalse, "buildlog",
+ "show buildable log",
+ "(^5name|slot#^7) (^5id^7)"
+ },
+
+ {"cancelvote", G_admin_endvote, qfalse, "cancelvote",
+ "cancel a vote taking place",
+ "(^5a|h^7)"
+ },
+
+ {"changemap", G_admin_changemap, qfalse, "changemap",
+ "load a map (and optionally force layout)",
+ "[^3mapname^7] (^5layout^7)"
+ },
+
+ {"denybuild", G_admin_denybuild, qfalse, "denybuild",
+ "take away a player's ability to build",
+ "[^3name|slot#^7]"
+ },
+
+ {"kick", G_admin_kick, qfalse, "kick",
+ "kick a player with an optional reason",
+ "[^3name|slot#^7] (^5reason^7)"
+ },
+
+ {"listadmins", G_admin_listadmins, qtrue, "listadmins",
+ "display a list of all server admins and their levels",
+ "(^5name^7) (^5start admin#^7)"
+ },
+
+ {"listlayouts", G_admin_listlayouts, qtrue, "listlayouts",
+ "display a list of all available layouts for a map",
+ "(^5mapname^7)"
+ },
+
+ {"listplayers", G_admin_listplayers, qtrue, "listplayers",
+ "display a list of players, their client numbers and their levels",
+ ""
+ },
+
+ {"lock", G_admin_lock, qfalse, "lock",
+ "lock a team to prevent anyone from joining it",
+ "[^3a|h^7]"
+ },
+
+ {"mute", G_admin_mute, qfalse, "mute",
+ "mute a player",
+ "[^3name|slot#^7]"
+ },
+
+ {"namelog", G_admin_namelog, qtrue, "namelog",
+ "display a list of names used by recently connected players",
+ "(^5name|IP(/mask)^7) (start namelog#)"
+ },
+
+ {"nextmap", G_admin_nextmap, qfalse, "nextmap",
+ "go to the next map in the cycle",
+ ""
+ },
+
+ {"passvote", G_admin_endvote, qfalse, "passvote",
+ "pass a vote currently taking place",
+ "(^5a|h^7)"
+ },
+
+ {"pause", G_admin_pause, qfalse, "pause",
+ "Pause (or unpause) the game.",
+ ""
+ },
+
+ {"putteam", G_admin_putteam, qfalse, "putteam",
+ "move a player to a specified team",
+ "[^3name|slot#^7] [^3h|a|s^7]"
+ },
+
+ {"readconfig", G_admin_readconfig, qfalse, "readconfig",
+ "reloads the admin config file and refreshes permission flags",
+ ""
+ },
+
+ {"rename", G_admin_rename, qfalse, "rename",
+ "rename a player",
+ "[^3name|slot#^7] [^3new name^7]"
+ },
+
+ {"restart", G_admin_restart, qfalse, "restart",
+ "restart the current map (optionally using named layout or keeping/switching teams)",
+ "(^5layout^7) (^5keepteams|switchteams|keepteamslock|switchteamslock^7)"
+ },
+
+ {"revert", G_admin_revert, qfalse, "revert",
+ "revert buildables to a given time",
+ "[^3id^7]"
+ },
+
+ {"setlevel", G_admin_setlevel, qfalse, "setlevel",
+ "sets the admin level of a player",
+ "[^3name|slot#|admin#^7] [^3level^7]"
+ },
+
+ {"showbans", G_admin_showbans, qtrue, "showbans",
+ "display a (partial) list of active bans",
+ "(^5name|IP(/mask)^7) (^5start at ban#^7)"
+ },
+
+ {"spec999", G_admin_spec999, qfalse, "spec999",
+ "move 999 pingers to the spectator team",
+ ""},
+
+ {"time", G_admin_time, qtrue, "time",
+ "show the current local server time",
+ ""},
+
+ {"unban", G_admin_unban, qfalse, "ban",
+ "unbans a player specified by the slot as seen in showbans",
+ "[^3ban#^7]"
+ },
+
+ {"unlock", G_admin_lock, qfalse, "lock",
+ "unlock a locked team",
+ "[^3a|h^7]"
+ },
+
+ {"unmute", G_admin_mute, qfalse, "mute",
+ "unmute a muted player",
+ "[^3name|slot#^7]"
+ }
+ };
+
+static size_t adminNumCmds = sizeof( g_admin_cmds ) / sizeof( g_admin_cmds[ 0 ] );
+
+static int admin_level_maxname = 0;
+g_admin_level_t *g_admin_levels = NULL;
+g_admin_admin_t *g_admin_admins = NULL;
+g_admin_ban_t *g_admin_bans = NULL;
+g_admin_command_t *g_admin_commands = NULL;
+
+void G_admin_register_cmds( void )
+{
+ int i;
+
+ for( i = 0; i < adminNumCmds; i++ )
+ trap_AddCommand( g_admin_cmds[ i ].keyword );
+}
+
+void G_admin_unregister_cmds( void )
+{
+ int i;
+
+ for( i = 0; i < adminNumCmds; i++ )
+ trap_RemoveCommand( g_admin_cmds[ i ].keyword );
+}
+
+void G_admin_cmdlist( gentity_t *ent )
+{
+ int i;
+ char out[ MAX_STRING_CHARS ] = "";
+ int len, outlen;
+
+ outlen = 0;
+
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( !G_admin_permission( ent, g_admin_cmds[ i ].flag ) )
+ continue;
+
+ len = strlen( g_admin_cmds[ i ].keyword ) + 1;
+ if( len + outlen >= sizeof( out ) - 1 )
+ {
+ trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) );
+ outlen = 0;
+ }
+
+ strcpy( out + outlen, va( " %s", g_admin_cmds[ i ].keyword ) );
+ outlen += len;
+ }
+
+ trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) );
+}
+
+// match a certain flag within these flags
+static qboolean admin_permission( char *flags, const char *flag, qboolean *perm )
+{
+ char *token, *token_p = flags;
+ qboolean allflags = qfalse;
+ qboolean p = qfalse;
+ *perm = qfalse;
+ while( *( token = COM_Parse( &token_p ) ) )
+ {
+ *perm = qtrue;
+ if( *token == '-' || *token == '+' )
+ *perm = *token++ == '+';
+ if( !strcmp( token, flag ) )
+ return qtrue;
+ if( !strcmp( token, ADMF_ALLFLAGS ) )
+ {
+ allflags = qtrue;
+ p = *perm;
+ }
+ }
+ if( allflags )
+ *perm = p;
+ return allflags;
+}
+
+g_admin_cmd_t *G_admin_cmd( const char *cmd )
+{
+ return bsearch( cmd, g_admin_cmds, adminNumCmds, sizeof( g_admin_cmd_t ),
+ cmdcmp );
+}
+
+g_admin_level_t *G_admin_level( const int l )
+{
+ g_admin_level_t *level;
+
+ for( level = g_admin_levels; level; level = level->next )
+ {
+ if( level->level == l )
+ return level;
+ }
+
+ return NULL;
+}
+
+g_admin_admin_t *G_admin_admin( const char *guid )
+{
+ g_admin_admin_t *admin;
+
+ for( admin = g_admin_admins; admin; admin = admin->next )
+ {
+ if( !Q_stricmp( admin->guid, guid ) )
+ return admin;
+ }
+
+ return NULL;
+}
+
+g_admin_command_t *G_admin_command( const char *cmd )
+{
+ g_admin_command_t *c;
+
+ for( c = g_admin_commands; c; c = c->next )
+ {
+ if( !Q_stricmp( c->command, cmd ) )
+ return c;
+ }
+
+ return NULL;
+}
+
+qboolean G_admin_permission( gentity_t *ent, const char *flag )
+{
+ qboolean perm;
+ g_admin_admin_t *a;
+ g_admin_level_t *l;
+
+ // console always wins
+ if( !ent )
+ return qtrue;
+
+ if( ( a = ent->client->pers.admin ) )
+ {
+ if( admin_permission( a->flags, flag, &perm ) )
+ return perm;
+
+ l = G_admin_level( a->level );
+ }
+ else
+ l = G_admin_level( 0 );
+
+ if( l )
+ return admin_permission( l->flags, flag, &perm ) && perm;
+
+ return qfalse;
+}
+
+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 ] = {""};
+ g_admin_admin_t *admin;
+ int alphaCount = 0;
+
+ G_SanitiseString( name, name2, sizeof( name2 ) );
+
+ if( !strcmp( name2, "unnamedplayer" ) )
+ return qtrue;
+
+ if( !strcmp( name2, "console" ) )
+ {
+ if( err && len > 0 )
+ Q_strncpyz( err, "The name 'console' is not allowed.", len );
+ return qfalse;
+ }
+
+ G_DecolorString( name, testName, sizeof( testName ) );
+ if( isdigit( testName[ 0 ] ) )
+ {
+ if( err && len > 0 )
+ Q_strncpyz( err, "Names cannot begin with numbers", len );
+ return qfalse;
+ }
+
+ for( i = 0; testName[ i ]; i++)
+ {
+ if( isalpha( testName[ i ] ) )
+ alphaCount++;
+ }
+
+ if( alphaCount == 0 )
+ {
+ if( err && len > 0 )
+ Q_strncpyz( err, "Names must contain letters", len );
+ return qfalse;
+ }
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ client = &level.clients[ i ];
+ if( client->pers.connected == CON_DISCONNECTED )
+ 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( !strcmp( name2, testName ) )
+ {
+ if( err && len > 0 )
+ Com_sprintf( err, len, "The name '%s^7' is already in use", name );
+ return qfalse;
+ }
+ }
+
+ for( admin = g_admin_admins; admin; admin = admin->next )
+ {
+ if( admin->level < 1 )
+ continue;
+ G_SanitiseString( admin->name, testName, sizeof( testName ) );
+ if( !strcmp( name2, testName ) && ent->client->pers.admin != admin )
+ {
+ if( err && len > 0 )
+ Com_sprintf( err, len, "The name '%s^7' belongs to an admin, "
+ "please use another name", name );
+ return qfalse;
+ }
+ }
+ return qtrue;
+}
+
+static qboolean admin_higher_admin( g_admin_admin_t *a, g_admin_admin_t *b )
+{
+ qboolean perm;
+
+ if( !b )
+ return qtrue;
+
+ if( admin_permission( b->flags, ADMF_IMMUTABLE, &perm ) )
+ return !perm;
+
+ return b->level <= ( a ? a->level : 0 );
+}
+
+static qboolean admin_higher_guid( char *admin_guid, char *victim_guid )
+{
+ return admin_higher_admin( G_admin_admin( admin_guid ),
+ G_admin_admin( victim_guid ) );
+}
+
+static qboolean admin_higher( gentity_t *admin, gentity_t *victim )
+{
+
+ // console always wins
+ if( !admin )
+ return qtrue;
+
+ return admin_higher_admin( admin->client->pers.admin,
+ victim->client->pers.admin );
+}
+
+static void admin_writeconfig_string( char *s, fileHandle_t f )
+{
+ if( s[ 0 ] )
+ trap_FS_Write( s, strlen( s ), 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\n", v );
+ trap_FS_Write( buf, strlen( buf ), f );
+}
+
+static void admin_writeconfig( void )
+{
+ fileHandle_t f;
+ int t;
+ g_admin_admin_t *a;
+ g_admin_level_t *l;
+ g_admin_ban_t *b;
+ g_admin_command_t *c;
+
+ 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 );
+ if( trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE ) < 0 )
+ {
+ G_Printf( "admin_writeconfig: could not open g_admin file \"%s\"\n",
+ g_admin.string );
+ return;
+ }
+ for( l = g_admin_levels; l; l = l->next )
+ {
+ trap_FS_Write( "[level]\n", 8, f );
+ trap_FS_Write( "level = ", 10, f );
+ admin_writeconfig_int( l->level, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( l->name, f );
+ trap_FS_Write( "flags = ", 10, f );
+ admin_writeconfig_string( l->flags, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( a = g_admin_admins; a; a = a->next )
+ {
+ // don't write level 0 users
+ if( a->level == 0 )
+ continue;
+
+ trap_FS_Write( "[admin]\n", 8, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( a->name, f );
+ trap_FS_Write( "guid = ", 10, f );
+ admin_writeconfig_string( a->guid, f );
+ trap_FS_Write( "level = ", 10, f );
+ admin_writeconfig_int( a->level, f );
+ trap_FS_Write( "flags = ", 10, f );
+ admin_writeconfig_string( a->flags, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( b = g_admin_bans; b; b = b->next )
+ {
+ // don't write expired bans
+ // if expires is 0, then it's a perm ban
+ if( b->expires != 0 && b->expires <= t )
+ continue;
+
+ trap_FS_Write( "[ban]\n", 6, f );
+ trap_FS_Write( "name = ", 10, f );
+ admin_writeconfig_string( b->name, f );
+ trap_FS_Write( "guid = ", 10, f );
+ admin_writeconfig_string( b->guid, f );
+ trap_FS_Write( "ip = ", 10, f );
+ admin_writeconfig_string( b->ip.str, f );
+ trap_FS_Write( "reason = ", 10, f );
+ admin_writeconfig_string( b->reason, f );
+ trap_FS_Write( "made = ", 10, f );
+ admin_writeconfig_string( b->made, f );
+ trap_FS_Write( "expires = ", 10, f );
+ admin_writeconfig_int( b->expires, f );
+ trap_FS_Write( "banner = ", 10, f );
+ admin_writeconfig_string( b->banner, f );
+ trap_FS_Write( "\n", 1, f );
+ }
+ for( c = g_admin_commands; c; c = c->next )
+ {
+ trap_FS_Write( "[command]\n", 10, f );
+ trap_FS_Write( "command = ", 10, f );
+ admin_writeconfig_string( c->command, f );
+ trap_FS_Write( "exec = ", 10, f );
+ admin_writeconfig_string( c->exec, f );
+ trap_FS_Write( "desc = ", 10, f );
+ admin_writeconfig_string( c->desc, f );
+ trap_FS_Write( "flag = ", 10, f );
+ admin_writeconfig_string( c->flag, 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, "=");
+ s[ 0 ] = '\0';
+ t = COM_ParseExt( cnf, qfalse );
+ if( strcmp( t, "=" ) )
+ {
+ COM_ParseWarning( "expected '=' before \"%s\"", t );
+ Q_strncpyz( s, t, size );
+ }
+ while( 1 )
+ {
+ t = COM_ParseExt( cnf, qfalse );
+ if( !*t )
+ break;
+ if( strlen( t ) + strlen( s ) >= size )
+ break;
+ if( *s )
+ Q_strcat( s, size, " " );
+ Q_strcat( s, size, t );
+ }
+}
+
+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
+ {
+ COM_ParseWarning( "expected '=' before \"%s\"", t );
+ }
+ *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 level = 0;
+
+ l = g_admin_levels = BG_Alloc( sizeof( g_admin_level_t ) );
+ l->level = level++;
+ Q_strncpyz( l->name, "^4Unknown Player", sizeof( l->name ) );
+ Q_strncpyz( l->flags,
+ "listplayers admintest adminhelp time",
+ sizeof( l->flags ) );
+
+ l = l->next = BG_Alloc( sizeof( g_admin_level_t ) );
+ l->level = level++;
+ Q_strncpyz( l->name, "^5Server Regular", sizeof( l->name ) );
+ Q_strncpyz( l->flags,
+ "listplayers admintest adminhelp time",
+ sizeof( l->flags ) );
+
+ l = l->next = BG_Alloc( sizeof( g_admin_level_t ) );
+ l->level = level++;
+ Q_strncpyz( l->name, "^6Team Manager", sizeof( l->name ) );
+ Q_strncpyz( l->flags,
+ "listplayers admintest adminhelp time putteam spec999",
+ sizeof( l->flags ) );
+
+ l = l->next = BG_Alloc( sizeof( g_admin_level_t ) );
+ l->level = level++;
+ Q_strncpyz( l->name, "^2Junior Admin", sizeof( l->name ) );
+ Q_strncpyz( l->flags,
+ "listplayers admintest adminhelp time putteam spec999 kick mute ADMINCHAT",
+ sizeof( l->flags ) );
+
+ l = l->next = BG_Alloc( sizeof( g_admin_level_t ) );
+ l->level = level++;
+ Q_strncpyz( l->name, "^3Senior Admin", sizeof( l->name ) );
+ Q_strncpyz( l->flags,
+ "listplayers admintest adminhelp time putteam spec999 kick mute showbans ban "
+ "namelog ADMINCHAT",
+ sizeof( l->flags ) );
+
+ l = l->next = BG_Alloc( sizeof( g_admin_level_t ) );
+ l->level = level++;
+ Q_strncpyz( l->name, "^1Server Operator", sizeof( l->name ) );
+ Q_strncpyz( l->flags,
+ "ALLFLAGS -IMMUTABLE -INCOGNITO",
+ sizeof( l->flags ) );
+ admin_level_maxname = 15;
+}
+
+void G_admin_authlog( gentity_t *ent )
+{
+ char aflags[ MAX_ADMIN_FLAGS * 2 ];
+ g_admin_level_t *level;
+ int levelNum = 0;
+
+ if( !ent )
+ return;
+
+ if( ent->client->pers.admin )
+ levelNum = ent->client->pers.admin->level;
+
+ level = G_admin_level( levelNum );
+
+ Com_sprintf( aflags, sizeof( aflags ), "%s %s",
+ ent->client->pers.admin->flags,
+ ( level ) ? level->flags : "" );
+
+ G_LogPrintf( "AdminAuth: %i \"%s" S_COLOR_WHITE "\" \"%s" S_COLOR_WHITE
+ "\" [%d] (%s): %s\n",
+ ent - g_entities, ent->client->pers.netname,
+ ent->client->pers.admin->name, ent->client->pers.admin->level,
+ ent->client->pers.guid, aflags );
+}
+
+static char adminLog[ MAX_STRING_CHARS ];
+static int adminLogLen;
+static void admin_log_start( gentity_t *admin, const char *cmd )
+{
+ const char *name = admin ? admin->client->pers.netname : "console";
+
+ adminLogLen = Q_snprintf( adminLog, sizeof( adminLog ),
+ "%d \"%s" S_COLOR_WHITE "\" \"%s" S_COLOR_WHITE "\" [%d] (%s): %s",
+ admin ? admin->s.clientNum : -1,
+ name,
+ admin && admin->client->pers.admin ? admin->client->pers.admin->name : name,
+ admin && admin->client->pers.admin ? admin->client->pers.admin->level : 0,
+ admin ? admin->client->pers.guid : "",
+ cmd );
+}
+
+static void admin_log( const char *str )
+{
+ if( adminLog[ 0 ] )
+ adminLogLen += Q_snprintf( adminLog + adminLogLen,
+ sizeof( adminLog ) - adminLogLen, ": %s", str );
+}
+
+static void admin_log_abort( void )
+{
+ adminLog[ 0 ] = '\0';
+ adminLogLen = 0;
+}
+
+static void admin_log_end( const qboolean ok )
+{
+ if( adminLog[ 0 ] )
+ G_LogPrintf( "AdminExec: %s: %s\n", ok ? "ok" : "fail", adminLog );
+ admin_log_abort( );
+}
+
+struct llist
+{
+ struct llist *next;
+};
+static int admin_search( gentity_t *ent,
+ const char *cmd,
+ const char *noun,
+ qboolean ( *match )( void *, const void * ),
+ void ( *out )( void *, char * ),
+ const void *list,
+ const void *arg, /* this will be used as char* later */
+ int start,
+ const int offset,
+ const int limit )
+{
+ int i;
+ int count = 0;
+ int found = 0;
+ int total;
+ int next = 0, end = 0;
+ char str[ MAX_STRING_CHARS ];
+ struct llist *l = (struct llist *)list;
+
+ for( total = 0; l; total++, l = l->next ) ;
+ if( start < 0 )
+ start += total;
+ else
+ start -= offset;
+ if( start < 0 || start > total )
+ start = 0;
+
+ ADMBP_begin();
+ for( i = 0, l = (struct llist *)list; l; i++, l = l->next )
+ {
+ if( match( l, arg ) )
+ {
+ if( i >= start && ( limit < 1 || count < limit ) )
+ {
+ out( l, str );
+ ADMBP( va( "%-3d %s\n", i + offset, str ) );
+ count++;
+ end = i;
+ }
+ else if( count == limit )
+ {
+ if( next == 0 )
+ next = i;
+ }
+
+ found++;
+ }
+ }
+
+ if( limit > 0 )
+ {
+ ADMBP( va( "^3%s: ^7showing %d of %d %s %d-%d%s%s.",
+ cmd, count, found, noun, start + offset, end + offset,
+ *(char *)arg ? " matching " : "", (char *)arg ) );
+ if( next )
+ ADMBP( va( " use '%s%s%s %d' to see more", cmd,
+ *(char *)arg ? " " : "",
+ (char *)arg,
+ next + offset ) );
+ }
+ ADMBP( "\n" );
+ ADMBP_end();
+ return next + offset;
+}
+
+static qboolean admin_match( void *admin, const void *match )
+{
+ char n1[ MAX_NAME_LENGTH ], n2[ MAX_NAME_LENGTH ];
+ G_SanitiseString( (char *)match, n2, sizeof( n2 ) );
+ if( !n2[ 0 ] )
+ return qtrue;
+ G_SanitiseString( ( (g_admin_admin_t *)admin )->name, n1, sizeof( n1 ) );
+ return strstr( n1, n2 ) ? qtrue : qfalse;
+}
+static void admin_out( void *admin, char *str )
+{
+ g_admin_admin_t *a = (g_admin_admin_t *)admin;
+ g_admin_level_t *l = G_admin_level( a->level );
+ int lncol = 0, i;
+ for( i = 0; l && l->name[ i ]; i++ )
+ {
+ if( Q_IsColorString( l->name + i ) )
+ lncol += 2;
+ }
+ Com_sprintf( str, MAX_STRING_CHARS, "%-6d %*s^7 %s",
+ a->level, admin_level_maxname + lncol - 1, l ? l->name : "(null)",
+ a->name );
+}
+static int admin_listadmins( gentity_t *ent, int start, char *search )
+{
+ return admin_search( ent, "listadmins", "admins", admin_match, admin_out,
+ g_admin_admins, search, start, MAX_CLIENTS, MAX_ADMIN_LISTITEMS );
+}
+
+#define MAX_DURATION_LENGTH 13
+void G_admin_duration( int secs, char *duration, int dursize )
+{
+ // sizeof("12.5 minutes") == 13
+ 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 );
+}
+
+static void G_admin_ban_message(
+ gentity_t *ent,
+ g_admin_ban_t *ban,
+ char *creason,
+ int clen,
+ char *areason,
+ int alen )
+{
+ if( creason )
+ {
+ char duration[ MAX_DURATION_LENGTH ];
+ G_admin_duration( ban->expires - trap_RealTime( NULL ), duration,
+ sizeof( duration ) );
+ // part of this might get cut off on the connect screen
+ Com_sprintf( creason, clen,
+ "You have been banned by %s" S_COLOR_WHITE " duration: %s"
+ " reason: %s",
+ ban->banner,
+ duration,
+ ban->reason );
+ }
+
+ if( areason && ent )
+ {
+ // we just want the ban number
+ int n = 1;
+ g_admin_ban_t *b = g_admin_bans;
+ for( ; b && b != ban; b = b->next, n++ )
+ ;
+ Com_sprintf( areason, alen,
+ S_COLOR_YELLOW "Banned player %s" S_COLOR_YELLOW
+ " tried to connect from %s (ban #%d)",
+ ent->client->pers.netname[ 0 ] ? ent->client->pers.netname : ban->name,
+ ent->client->pers.ip.str,
+ n );
+ }
+}
+
+static qboolean G_admin_ban_matches( g_admin_ban_t *ban, gentity_t *ent )
+{
+ return !Q_stricmp( ban->guid, ent->client->pers.guid ) ||
+ ( !G_admin_permission( ent, ADMF_IMMUNITY ) &&
+ G_AddressCompare( &ban->ip, &ent->client->pers.ip ) );
+}
+
+static g_admin_ban_t *G_admin_match_ban( gentity_t *ent )
+{
+ int t;
+ g_admin_ban_t *ban;
+
+ t = trap_RealTime( NULL );
+ if( ent->client->pers.localClient )
+ return NULL;
+
+ for( ban = g_admin_bans; ban; ban = ban->next )
+ {
+ // 0 is for perm ban
+ if( ban->expires != 0 && ban->expires <= t )
+ continue;
+
+ if( G_admin_ban_matches( ban, ent ) )
+ return ban;
+ }
+
+ return NULL;
+}
+
+qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen )
+{
+ g_admin_ban_t *ban;
+ char warningMessage[ MAX_STRING_CHARS ];
+
+ if( ent->client->pers.localClient )
+ return qfalse;
+
+ if( ( ban = G_admin_match_ban( ent ) ) )
+ {
+ G_admin_ban_message( ent, ban, reason, rlen,
+ warningMessage, sizeof( warningMessage ) );
+
+ // don't spam admins
+ if( ban->warnCount++ < 5 )
+ G_AdminMessage( NULL, warningMessage );
+ // and don't fill the console
+ else if( ban->warnCount < 10 )
+ trap_Print( va( "%s%s\n", warningMessage,
+ ban->warnCount + 1 == 10 ?
+ S_COLOR_WHITE " - future messages for this ban will be suppressed" :
+ "" ) );
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+qboolean G_admin_cmd_check( gentity_t *ent )
+{
+ char command[ MAX_ADMIN_CMD_LEN ];
+ g_admin_cmd_t *admincmd;
+ g_admin_command_t *c;
+ qboolean success;
+
+ command[ 0 ] = '\0';
+ trap_Argv( 0, command, sizeof( command ) );
+ if( !command[ 0 ] )
+ return qfalse;
+
+ Q_strlwr( command );
+ admin_log_start( ent, command );
+
+ if( ( c = G_admin_command( command ) ) )
+ {
+ admin_log( ConcatArgsPrintable( 1 ) );
+ if( ( success = G_admin_permission( ent, c->flag ) ) )
+ {
+ if( G_FloodLimited( ent ) )
+ return qtrue;
+ trap_SendConsoleCommand( EXEC_APPEND, c->exec );
+ }
+ else
+ {
+ ADMP( va( "^3%s: ^7permission denied\n", c->command ) );
+ }
+ admin_log_end( success );
+ return qtrue;
+ }
+
+ if( ( admincmd = G_admin_cmd( command ) ) )
+ {
+ if( ( success = G_admin_permission( ent, admincmd->flag ) ) )
+ {
+ if( G_FloodLimited( ent ) )
+ return qtrue;
+ if( admincmd->silent )
+ admin_log_abort( );
+ if( !( success = admincmd->handler( ent ) ) )
+ admin_log( ConcatArgsPrintable( 1 ) );
+ }
+ else
+ {
+ ADMP( va( "^3%s: ^7permission denied\n", admincmd->keyword ) );
+ admin_log( ConcatArgsPrintable( 1 ) );
+ }
+ admin_log_end( success );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static void llsort( struct llist **head, int compar( const void *, const void * ) )
+{
+ struct llist *a, *b, *t, *l;
+ int i, c = 1, ns, as, bs;
+
+ if( !*head )
+ return;
+
+ do
+ {
+ a = *head, l = *head = NULL;
+ for( ns = 0; a; ns++, a = b )
+ {
+ b = a;
+ for( i = as = 0; i < c; i++ )
+ {
+ as++;
+ if( !( b = b->next ) )
+ break;
+ }
+ for( bs = c; ( b && bs ) || as; l = t )
+ {
+ if( as && ( !bs || !b || compar( a, b ) <= 0 ) )
+ t = a, a = a->next, as--;
+ else
+ t = b, b = b->next, bs--;
+ if( l )
+ l->next = t;
+ else
+ *head = t;
+ }
+ }
+ l->next = NULL;
+ c *= 2;
+ } while( ns > 1 );
+}
+
+static int cmplevel( const void *a, const void *b )
+{
+ return ((g_admin_level_t *)b)->level - ((g_admin_level_t *)a)->level;
+}
+
+qboolean G_admin_readconfig( gentity_t *ent )
+{
+ 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;
+ int i;
+ char ip[ 44 ];
+
+ G_admin_cleanup();
+
+ if( !g_admin.string[ 0 ] )
+ {
+ ADMP( "^3readconfig: g_admin is not set, not loading configuration "
+ "from a file\n" );
+ return qfalse;
+ }
+
+ len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ );
+ if( len < 0 )
+ {
+ G_Printf( "^3readconfig: ^7could not open admin config file %s\n",
+ g_admin.string );
+ admin_default_levels();
+ return qfalse;
+ }
+ cnf = BG_Alloc( len + 1 );
+ cnf2 = cnf;
+ trap_FS_Read( cnf, len, f );
+ *( cnf + len ) = '\0';
+ trap_FS_FCloseFile( f );
+
+ admin_level_maxname = 0;
+
+ level_open = admin_open = ban_open = command_open = qfalse;
+ COM_BeginParseSession( g_admin.string );
+ while( 1 )
+ {
+ t = COM_Parse( &cnf );
+ if( !*t )
+ break;
+
+ if( !Q_stricmp( t, "[level]" ) )
+ {
+ if( l )
+ l = l->next = BG_Alloc( sizeof( g_admin_level_t ) );
+ else
+ l = g_admin_levels = BG_Alloc( sizeof( g_admin_level_t ) );
+ level_open = qtrue;
+ admin_open = ban_open = command_open = qfalse;
+ lc++;
+ }
+ else if( !Q_stricmp( t, "[admin]" ) )
+ {
+ if( a )
+ a = a->next = BG_Alloc( sizeof( g_admin_admin_t ) );
+ else
+ a = g_admin_admins = BG_Alloc( sizeof( g_admin_admin_t ) );
+ admin_open = qtrue;
+ level_open = ban_open = command_open = qfalse;
+ ac++;
+ }
+ else if( !Q_stricmp( t, "[ban]" ) )
+ {
+ if( b )
+ b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) );
+ else
+ b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) );
+ ban_open = qtrue;
+ level_open = admin_open = command_open = qfalse;
+ bc++;
+ }
+ else if( !Q_stricmp( t, "[command]" ) )
+ {
+ if( c )
+ c = c->next = BG_Alloc( sizeof( g_admin_command_t ) );
+ else
+ c = g_admin_commands = BG_Alloc( sizeof( g_admin_command_t ) );
+ command_open = qtrue;
+ level_open = admin_open = ban_open = qfalse;
+ cc++;
+ }
+ else 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 ) );
+ // max printable name length for formatting
+ len = Q_PrintStrlen( l->name );
+ if( len > admin_level_maxname )
+ admin_level_maxname = len;
+ }
+ else if( !Q_stricmp( t, "flags" ) )
+ {
+ admin_readconfig_string( &cnf, l->flags, sizeof( l->flags ) );
+ }
+ else
+ {
+ COM_ParseError( "[level] unrecognized token \"%s\"", t );
+ }
+ }
+ 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
+ {
+ COM_ParseError( "[admin] unrecognized token \"%s\"", t );
+ }
+
+ }
+ 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, ip, sizeof( ip ) );
+ G_AddressParse( ip, &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
+ {
+ COM_ParseError( "[ban] unrecognized token \"%s\"", t );
+ }
+ }
+ 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, "flag" ) )
+ {
+ admin_readconfig_string( &cnf, c->flag, sizeof( c->flag ) );
+ }
+ else
+ {
+ COM_ParseError( "[command] unrecognized token \"%s\"", t );
+ }
+ }
+ else
+ {
+ COM_ParseError( "unexpected token \"%s\"", t );
+ }
+ }
+ BG_Free( cnf2 );
+ ADMP( va( "^3readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n",
+ lc, ac, bc, cc ) );
+ if( lc == 0 )
+ admin_default_levels();
+ else
+ {
+ llsort( (struct llist **)&g_admin_levels, cmplevel );
+ llsort( (struct llist **)&g_admin_admins, cmplevel );
+ }
+
+ // restore admin mapping
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected != CON_DISCONNECTED )
+ {
+ level.clients[ i ].pers.admin =
+ G_admin_admin( level.clients[ i ].pers.guid );
+ if( level.clients[ i ].pers.admin )
+ G_admin_authlog( &g_entities[ i ] );
+ G_admin_cmdlist( &g_entities[ i ] );
+ }
+ }
+
+ return qtrue;
+}
+
+qboolean G_admin_time( gentity_t *ent )
+{
+ qtime_t qt;
+
+ trap_RealTime( &qt );
+ ADMP( va( "^3time: ^7local time is %02i:%02i:%02i\n",
+ qt.tm_hour, qt.tm_min, qt.tm_sec ) );
+ return qtrue;
+}
+
+// this should be in one of the headers, but it is only used here for now
+namelog_t *G_NamelogFromString( gentity_t *ent, char *s );
+
+/*
+for consistency, we should be able to target a disconnected player with setlevel
+but we can't use namelog and remain consistent, so the solution would be to make
+everyone a real level 0 admin so they can be targeted until the next level
+but that seems kind of stupid
+*/
+qboolean G_admin_setlevel( gentity_t *ent )
+{
+ char name[ MAX_NAME_LENGTH ] = {""};
+ char lstr[ 12 ]; // 11 is max strlen() for 32-bit (signed) int
+ char testname[ MAX_NAME_LENGTH ] = {""};
+ int i;
+ gentity_t *vic = NULL;
+ g_admin_admin_t *a = NULL;
+ g_admin_level_t *l = NULL;
+ int na;
+
+ if( trap_Argc() < 3 )
+ {
+ ADMP( "^3setlevel: ^7usage: setlevel [name|slot#] [level]\n" );
+ return qfalse;
+ }
+
+ trap_Argv( 1, testname, sizeof( testname ) );
+ trap_Argv( 2, lstr, sizeof( lstr ) );
+
+ if( !( l = G_admin_level( atoi( lstr ) ) ) )
+ {
+ ADMP( "^3setlevel: ^7level is not defined\n" );
+ return qfalse;
+ }
+
+ if( ent && l->level >
+ ( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ) )
+ {
+ ADMP( "^3setlevel: ^7you may not use setlevel to set a level higher "
+ "than your current level\n" );
+ return qfalse;
+ }
+
+ for( na = 0, a = g_admin_admins; a; na++, a = a->next );
+
+ for( i = 0; testname[ i ] && isdigit( testname[ i ] ); i++ );
+ if( !testname[ i ] )
+ {
+ int id = atoi( testname );
+ if( id < MAX_CLIENTS )
+ {
+ vic = &g_entities[ id ];
+ if( !vic || !vic->client || vic->client->pers.connected == CON_DISCONNECTED )
+ {
+ ADMP( va( "^3setlevel: ^7no player connected in slot %d\n", id ) );
+ return qfalse;
+ }
+ }
+ else if( id < na + MAX_CLIENTS )
+ for( i = 0, a = g_admin_admins; i < id - MAX_CLIENTS; i++, a = a->next );
+ else
+ {
+ ADMP( va( "^3setlevel: ^7%s not in range 1-%d\n",
+ testname, na + MAX_CLIENTS - 1 ) );
+ return qfalse;
+ }
+ }
+ else
+ G_SanitiseString( testname, name, sizeof( name ) );
+
+ if( vic )
+ a = vic->client->pers.admin;
+ else if( !a )
+ {
+ g_admin_admin_t *wa;
+ int matches = 0;
+
+ for( wa = g_admin_admins; wa && matches < 2; wa = wa->next )
+ {
+ G_SanitiseString( wa->name, testname, sizeof( testname ) );
+ if( strstr( testname, name ) )
+ {
+ a = wa;
+ matches++;
+ }
+ }
+
+ for( i = 0; i < level.maxclients && matches < 2; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+
+ if( matches && level.clients[ i ].pers.admin &&
+ level.clients[ i ].pers.admin == a )
+ {
+ vic = &g_entities[ i ];
+ continue;
+ }
+
+ G_SanitiseString( level.clients[ i ].pers.netname, testname,
+ sizeof( testname ) );
+ if( strstr( testname, name ) )
+ {
+ vic = &g_entities[ i ];
+ a = vic->client->pers.admin;
+ matches++;
+ }
+ }
+
+ if( matches == 0 )
+ {
+ ADMP( "^3setlevel:^7 no match. use listplayers or listadmins to "
+ "find an appropriate number to use instead of name.\n" );
+ return qfalse;
+ }
+ if( matches > 1 )
+ {
+ ADMP( "^3setlevel:^7 more than one match. Use the admin number "
+ "instead:\n" );
+ admin_listadmins( ent, 0, name );
+ return qfalse;
+ }
+ }
+
+ if( ent && !admin_higher_admin( ent->client->pers.admin, a ) )
+ {
+ ADMP( "^3setlevel: ^7sorry, but your intended victim has a higher"
+ " admin level than you\n" );
+ return qfalse;
+ }
+
+ if( !a && vic )
+ {
+ for( a = g_admin_admins; a && a->next; a = a->next );
+ if( a )
+ a = a->next = BG_Alloc( sizeof( g_admin_admin_t ) );
+ else
+ a = g_admin_admins = BG_Alloc( sizeof( g_admin_admin_t ) );
+ vic->client->pers.admin = a;
+ Q_strncpyz( a->guid, vic->client->pers.guid, sizeof( a->guid ) );
+ }
+
+ a->level = l->level;
+ if( vic )
+ Q_strncpyz( a->name, vic->client->pers.netname, sizeof( a->name ) );
+
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", a->level, a->guid,
+ a->name ) );
+
+ AP( va(
+ "print \"^3setlevel: ^7%s^7 was given level %d admin rights by %s\n\"",
+ a->name, a->level, ( ent ) ? ent->client->pers.netname : "console" ) );
+
+ admin_writeconfig();
+ if( vic )
+ {
+ G_admin_authlog( vic );
+ G_admin_cmdlist( vic );
+ }
+ return qtrue;
+}
+
+static void admin_create_ban( gentity_t *ent,
+ char *netname,
+ char *guid,
+ addr_t *ip,
+ int seconds,
+ char *reason )
+{
+ g_admin_ban_t *b = NULL;
+ qtime_t qt;
+ int t;
+ int i;
+ char *name;
+ char disconnect[ MAX_STRING_CHARS ];
+
+ t = trap_RealTime( &qt );
+
+ for( b = g_admin_bans; b; b = b->next )
+ {
+ if( !b->next )
+ break;
+ }
+
+ if( b )
+ {
+ if( !b->next )
+ b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) );
+ }
+ else
+ b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) );
+
+ Q_strncpyz( b->name, netname, sizeof( b->name ) );
+ Q_strncpyz( b->guid, guid, sizeof( b->guid ) );
+ memcpy( &b->ip, ip, sizeof( b->ip ) );
+
+ Com_sprintf( b->made, sizeof( b->made ), "%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 );
+
+ if( ent && ent->client->pers.admin )
+ name = ent->client->pers.admin->name;
+ else if( ent )
+ name = ent->client->pers.netname;
+ else
+ name = "console";
+
+ Q_strncpyz( b->banner, name, 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 ) );
+
+ G_admin_ban_message( NULL, b, disconnect, sizeof( disconnect ), NULL, 0 );
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+ if( G_admin_ban_matches( b, &g_entities[ i ] ) )
+ {
+ trap_SendServerCommand( i, va( "disconnect \"%s\"", disconnect ) );
+
+ trap_DropClient( i, va( "has been kicked by %s^7. reason: %s",
+ b->banner, b->reason ) );
+ }
+ }
+}
+
+int G_admin_parse_time( const char *time )
+{
+ int seconds = 0, num = 0;
+ if( !*time )
+ return -1;
+ while( *time )
+ {
+ if( !isdigit( *time ) )
+ return -1;
+ while( isdigit( *time ) )
+ num = num * 10 + *time++ - '0';
+
+ if( !*time )
+ break;
+ switch( *time++ )
+ {
+ 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;
+ return seconds;
+}
+
+qboolean G_admin_kick( gentity_t *ent )
+{
+ int pid;
+ char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];
+ int minargc;
+ gentity_t *vic;
+
+ minargc = 3;
+ if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ minargc = 2;
+
+ if( trap_Argc() < minargc )
+ {
+ ADMP( "^3kick: ^7usage: kick [name] [reason]\n" );
+ return qfalse;
+ }
+ trap_Argv( 1, name, sizeof( name ) );
+ reason = ConcatArgs( 2 );
+ if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 )
+ {
+ ADMP( va( "^3kick: ^7%s", err ) );
+ return qfalse;
+ }
+ vic = &g_entities[ pid ];
+ if( !admin_higher( ent, vic ) )
+ {
+ ADMP( "^3kick: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ if( vic->client->pers.localClient )
+ {
+ ADMP( "^3kick: ^7disconnecting the host would end the game\n" );
+ return qfalse;
+ }
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\"",
+ pid,
+ vic->client->pers.guid,
+ vic->client->pers.netname,
+ reason ) );
+ admin_create_ban( ent,
+ vic->client->pers.netname,
+ vic->client->pers.guid,
+ &vic->client->pers.ip,
+ MAX( 1, G_admin_parse_time( g_adminTempBan.string ) ),
+ ( *reason ) ? reason : "kicked by admin" );
+ admin_writeconfig();
+
+ return qtrue;
+}
+
+qboolean G_admin_ban( gentity_t *ent )
+{
+ int seconds;
+ char search[ MAX_NAME_LENGTH ];
+ char secs[ MAX_TOKEN_CHARS ];
+ char *reason;
+ char duration[ MAX_DURATION_LENGTH ];
+ int i;
+ addr_t ip;
+ qboolean ipmatch = qfalse;
+ namelog_t *match = NULL;
+
+ if( trap_Argc() < 2 )
+ {
+ ADMP( "^3ban: ^7usage: ban [name|slot|IP(/mask)] [duration] [reason]\n" );
+ return qfalse;
+ }
+ trap_Argv( 1, search, sizeof( search ) );
+ trap_Argv( 2, secs, sizeof( secs ) );
+
+ seconds = G_admin_parse_time( secs );
+ if( seconds <= 0 )
+ {
+ seconds = 0;
+ reason = ConcatArgs( 2 );
+ }
+ else
+ {
+ reason = ConcatArgs( 3 );
+ }
+ if( !*reason && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )
+ {
+ ADMP( "^3ban: ^7you must specify a reason\n" );
+ return qfalse;
+ }
+ if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )
+ {
+ int maximum = MAX( 1, G_admin_parse_time( g_adminMaxBan.string ) );
+ if( seconds == 0 || seconds > maximum )
+ {
+ ADMP( "^3ban: ^7you may not issue permanent bans\n" );
+ seconds = maximum;
+ }
+ }
+
+ if( G_AddressParse( search, &ip ) )
+ {
+ int max = ip.type == IPv4 ? 32 : 128;
+ int min = ent ? max / 2 : 1;
+
+ if( ip.mask < min || ip.mask > max )
+ {
+ ADMP( va( "^3ban: ^7invalid netmask (%d is not one of %d-%d)\n",
+ ip.mask, min, max ) );
+ return qfalse;
+ }
+ ipmatch = qtrue;
+
+ for( match = level.namelogs; match; match = match->next )
+ {
+ // skip players in the namelog who have already been banned
+ if( match->banned )
+ continue;
+
+ for( i = 0; i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ]; i++ )
+ {
+ if( G_AddressCompare( &ip, &match->ip[ i ] ) )
+ break;
+ }
+ if( i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ] )
+ break;
+ }
+
+ if( !match )
+ {
+ ADMP( "^3ban: ^7no player found by that IP address\n" );
+ return qfalse;
+ }
+ }
+ else if( !( match = G_NamelogFromString( ent, search ) ) || match->banned )
+ {
+ ADMP( "^3ban: ^7no match\n" );
+ return qfalse;
+ }
+
+ if( ent && !admin_higher_guid( ent->client->pers.guid, match->guid ) )
+ {
+
+ ADMP( "^3ban: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n" );
+ return qfalse;
+ }
+ if( match->slot > -1 && level.clients[ match->slot ].pers.localClient )
+ {
+ ADMP( "^3ban: ^7disconnecting the host would end the game\n" );
+ return qfalse;
+ }
+
+ G_admin_duration( ( seconds ) ? seconds : -1,
+ duration, sizeof( duration ) );
+
+ AP( va( "print \"^3ban:^7 %s^7 has been banned by %s^7 "
+ "duration: %s, reason: %s\n\"",
+ match->name[ match->nameOffset ],
+ ( ent ) ? ent->client->pers.netname : "console",
+ duration,
+ ( *reason ) ? reason : "banned by admin" ) );
+
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\"",
+ seconds, match->guid, match->name[ match->nameOffset ], reason ) );
+ if( ipmatch )
+ {
+ admin_create_ban( ent,
+ match->slot == -1 ?
+ match->name[ match->nameOffset ] :
+ level.clients[ match->slot ].pers.netname,
+ match->guid,
+ &ip,
+ seconds, reason );
+ admin_log( va( "[%s]", ip.str ) );
+ }
+ else
+ {
+ // ban all IP addresses used by this player
+ for( i = 0; i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ]; i++ )
+ {
+ admin_create_ban( ent,
+ match->slot == -1 ?
+ match->name[ match->nameOffset ] :
+ level.clients[ match->slot ].pers.netname,
+ match->guid,
+ &match->ip[ i ],
+ seconds, reason );
+ admin_log( va( "[%s]", match->ip[ i ].str ) );
+ }
+ }
+
+ match->banned = qtrue;
+
+ if( !g_admin.string[ 0 ] )
+ ADMP( "^3ban: ^7WARNING g_admin not set, not saving ban to a file\n" );
+ else
+ admin_writeconfig();
+
+ return qtrue;
+}
+
+qboolean G_admin_unban( gentity_t *ent )
+{
+ int bnum;
+ int time = trap_RealTime( NULL );
+ char bs[ 5 ];
+ int i;
+ g_admin_ban_t *ban, *p;
+
+ if( trap_Argc() < 2 )
+ {
+ ADMP( "^3unban: ^7usage: unban [ban#]\n" );
+ return qfalse;
+ }
+ trap_Argv( 1, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ for( ban = p = g_admin_bans, i = 1; ban && i < bnum;
+ p = ban, ban = ban->next, i++ );
+ if( i != bnum || !ban )
+ {
+ ADMP( "^3unban: ^7invalid ban#\n" );
+ return qfalse;
+ }
+ if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ ( ban->expires == 0 || ( ban->expires - time > MAX( 1,
+ G_admin_parse_time( g_adminMaxBan.string ) ) ) ) )
+ {
+ ADMP( "^3unban: ^7you cannot remove permanent bans\n" );
+ return qfalse;
+ }
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\": [%s]",
+ ban->expires ? ban->expires - time : 0, ban->guid, ban->name, ban->reason,
+ ban->ip.str ) );
+ AP( va( "print \"^3unban: ^7ban #%d for %s^7 has been removed by %s\n\"",
+ bnum,
+ ban->name,
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ if( p == ban )
+ g_admin_bans = ban->next;
+ else
+ p->next = ban->next;
+ BG_Free( ban );
+ admin_writeconfig();
+ return qtrue;
+}
+
+qboolean G_admin_adjustban( gentity_t *ent )
+{
+ int bnum;
+ int length, maximum;
+ int expires;
+ int time = trap_RealTime( NULL );
+ char duration[ MAX_DURATION_LENGTH ] = {""};
+ char *reason;
+ char bs[ 5 ];
+ char secs[ MAX_TOKEN_CHARS ];
+ char mode = '\0';
+ g_admin_ban_t *ban;
+ int mask = 0;
+ int i;
+ int skiparg = 0;
+
+ if( trap_Argc() < 3 )
+ {
+ ADMP( "^3adjustban: ^7usage: adjustban [ban#] [/mask] [duration] [reason]"
+ "\n" );
+ return qfalse;
+ }
+ trap_Argv( 1, bs, sizeof( bs ) );
+ bnum = atoi( bs );
+ for( ban = g_admin_bans, i = 1; ban && i < bnum; ban = ban->next, i++ );
+ if( i != bnum || !ban )
+ {
+ ADMP( "^3adjustban: ^7invalid ban#\n" );
+ return qfalse;
+ }
+ maximum = MAX( 1, G_admin_parse_time( g_adminMaxBan.string ) );
+ if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ ( ban->expires == 0 || ban->expires - time > maximum ) )
+ {
+ ADMP( "^3adjustban: ^7you cannot modify permanent bans\n" );
+ return qfalse;
+ }
+ trap_Argv( 2, secs, sizeof( secs ) );
+ if( secs[ 0 ] == '/' )
+ {
+ int max = ban->ip.type == IPv6 ? 128 : 32;
+ int min = ent ? max / 2 : 1;
+ mask = atoi( secs + 1 );
+ if( mask < min || mask > max )
+ {
+ ADMP( va( "^3adjustban: ^7invalid netmask (%d is not one of %d-%d)\n",
+ mask, min, max ) );
+ return qfalse;
+ }
+ trap_Argv( 3 + skiparg++, secs, sizeof( secs ) );
+ }
+ if( secs[ 0 ] == '+' || secs[ 0 ] == '-' )
+ mode = secs[ 0 ];
+ length = G_admin_parse_time( &secs[ mode ? 1 : 0 ] );
+ if( length < 0 )
+ skiparg--;
+ else
+ {
+ if( length )
+ {
+ if( ban->expires == 0 && mode )
+ {
+ ADMP( "^3adjustban: ^7new duration must be explicit\n" );
+ return qfalse;
+ }
+ if( mode == '+' )
+ expires = ban->expires + length;
+ else if( mode == '-' )
+ expires = ban->expires - length;
+ else
+ expires = time + length;
+ if( expires <= time )
+ {
+ ADMP( "^3adjustban: ^7ban duration must be positive\n" );
+ return qfalse;
+ }
+ }
+ else
+ length = expires = 0;
+ if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) &&
+ ( length == 0 || length > maximum ) )
+ {
+ ADMP( "^3adjustban: ^7you may not issue permanent bans\n" );
+ expires = time + maximum;
+ }
+
+ ban->expires = expires;
+ G_admin_duration( ( expires ) ? expires - time : -1, duration,
+ sizeof( duration ) );
+ }
+ if( mask )
+ {
+ char *p = strchr( ban->ip.str, '/' );
+ if( !p )
+ p = ban->ip.str + strlen( ban->ip.str );
+ if( mask == ( ban->ip.type == IPv6 ? 128 : 32 ) )
+ *p = '\0';
+ else
+ Com_sprintf( p, sizeof( ban->ip.str ) - ( p - ban->ip.str ), "/%d", mask );
+ ban->ip.mask = mask;
+ }
+ reason = ConcatArgs( 3 + skiparg );
+ if( *reason )
+ Q_strncpyz( ban->reason, reason, sizeof( ban->reason ) );
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\": [%s]",
+ ban->expires ? ban->expires - time : 0, ban->guid, ban->name, ban->reason,
+ ban->ip.str ) );
+ AP( va( "print \"^3adjustban: ^7ban #%d for %s^7 has been updated by %s^7 "
+ "%s%s%s%s%s%s\n\"",
+ bnum,
+ ban->name,
+ ( ent ) ? ent->client->pers.netname : "console",
+ ( mask ) ?
+ va( "netmask: /%d%s", mask,
+ ( length >= 0 || *reason ) ? ", " : "" ) : "",
+ ( length >= 0 ) ? "duration: " : "",
+ duration,
+ ( length >= 0 && *reason ) ? ", " : "",
+ ( *reason ) ? "reason: " : "",
+ reason ) );
+ if( ent )
+ Q_strncpyz( ban->banner, ent->client->pers.netname, sizeof( ban->banner ) );
+ admin_writeconfig();
+ return qtrue;
+}
+
+qboolean G_admin_putteam( gentity_t *ent )
+{
+ int pid;
+ char name[ MAX_NAME_LENGTH ], team[ sizeof( "spectators" ) ],
+ err[ MAX_STRING_CHARS ];
+ gentity_t *vic;
+ team_t teamnum = TEAM_NONE;
+
+ trap_Argv( 1, name, sizeof( name ) );
+ trap_Argv( 2, team, sizeof( team ) );
+ if( trap_Argc() < 3 )
+ {
+ ADMP( "^3putteam: ^7usage: putteam [name] [h|a|s]\n" );
+ return qfalse;
+ }
+
+ if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 )
+ {
+ ADMP( va( "^3putteam: ^7%s", err ) );
+ return qfalse;
+ }
+ vic = &g_entities[ pid ];
+ if( !admin_higher( ent, vic ) )
+ {
+ ADMP( "^3putteam: ^7sorry, but your intended victim has a higher "
+ " admin level than you\n" );
+ return qfalse;
+ }
+ teamnum = G_TeamFromString( team );
+ if( teamnum == NUM_TEAMS )
+ {
+ ADMP( va( "^3putteam: ^7unknown team %s\n", team ) );
+ return qfalse;
+ }
+ if( vic->client->pers.teamSelection == teamnum )
+ return qfalse;
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", pid, vic->client->pers.guid,
+ vic->client->pers.netname ) );
+ G_ChangeTeam( vic, teamnum );
+
+ AP( va( "print \"^3putteam: ^7%s^7 put %s^7 on to the %s team\n\"",
+ ( ent ) ? ent->client->pers.netname : "console",
+ vic->client->pers.netname, BG_TeamName( teamnum ) ) );
+ return qtrue;
+}
+
+qboolean G_admin_changemap( gentity_t *ent )
+{
+ char map[ MAX_QPATH ];
+ char layout[ MAX_QPATH ] = { "" };
+
+ if( trap_Argc( ) < 2 )
+ {
+ ADMP( "^3changemap: ^7usage: changemap [map] (layout)\n" );
+ return qfalse;
+ }
+
+ trap_Argv( 1, map, sizeof( map ) );
+
+ if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) )
+ {
+ ADMP( va( "^3changemap: ^7invalid map name '%s'\n", map ) );
+ return qfalse;
+ }
+
+ if( trap_Argc( ) > 2 )
+ {
+ trap_Argv( 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( "^3changemap: ^7invalid layout name '%s'\n", layout ) );
+ return qfalse;
+ }
+ }
+ admin_log( map );
+ admin_log( layout );
+
+ trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) );
+ level.restarted = qtrue;
+ AP( va( "print \"^3changemap: ^7map '%s' started by %s^7 %s\n\"", map,
+ ( ent ) ? ent->client->pers.netname : "console",
+ ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) );
+ return qtrue;
+}
+
+qboolean G_admin_mute( gentity_t *ent )
+{
+ char name[ MAX_NAME_LENGTH ];
+ char command[ MAX_ADMIN_CMD_LEN ];
+ namelog_t *vic;
+
+ trap_Argv( 0, command, sizeof( command ) );
+ if( trap_Argc() < 2 )
+ {
+ ADMP( va( "^3%s: ^7usage: %s [name|slot#]\n", command, command ) );
+ return qfalse;
+ }
+ trap_Argv( 1, name, sizeof( name ) );
+ if( !( vic = G_NamelogFromString( ent, name ) ) )
+ {
+ ADMP( va( "^3%s: ^7no match\n", command ) );
+ return qfalse;
+ }
+ if( ent && !admin_higher_admin( ent->client->pers.admin,
+ G_admin_admin( vic->guid ) ) )
+ {
+ ADMP( va( "^3%s: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n", command ) );
+ return qfalse;
+ }
+ if( vic->muted )
+ {
+ if( !Q_stricmp( command, "mute" ) )
+ {
+ ADMP( "^3mute: ^7player is already muted\n" );
+ return qfalse;
+ }
+ vic->muted = qfalse;
+ if( vic->slot > -1 )
+ CPx( vic->slot, "cp \"^1You have been unmuted\"" );
+ AP( va( "print \"^3unmute: ^7%s^7 has been unmuted by %s\n\"",
+ vic->name[ vic->nameOffset ],
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ }
+ else
+ {
+ if( !Q_stricmp( command, "unmute" ) )
+ {
+ ADMP( "^3unmute: ^7player is not currently muted\n" );
+ return qfalse;
+ }
+ vic->muted = qtrue;
+ if( vic->slot > -1 )
+ CPx( vic->slot, "cp \"^1You've been muted\"" );
+ AP( va( "print \"^3mute: ^7%s^7 has been muted by ^7%s\n\"",
+ vic->name[ vic->nameOffset ],
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ }
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", vic->slot, vic->guid,
+ vic->name[ vic->nameOffset ] ) );
+ return qtrue;
+}
+
+qboolean G_admin_denybuild( gentity_t *ent )
+{
+ char name[ MAX_NAME_LENGTH ];
+ char command[ MAX_ADMIN_CMD_LEN ];
+ namelog_t *vic;
+
+ trap_Argv( 0, command, sizeof( command ) );
+ if( trap_Argc() < 2 )
+ {
+ ADMP( va( "^3%s: ^7usage: %s [name|slot#]\n", command, command ) );
+ return qfalse;
+ }
+ trap_Argv( 1, name, sizeof( name ) );
+ if( !( vic = G_NamelogFromString( ent, name ) ) )
+ {
+ ADMP( va( "^3%s: ^7no match\n", command ) );
+ return qfalse;
+ }
+ if( ent && !admin_higher_admin( ent->client->pers.admin,
+ G_admin_admin( vic->guid ) ) )
+ {
+ ADMP( va( "^3%s: ^7sorry, but your intended victim has a higher admin"
+ " level than you\n", command ) );
+ return qfalse;
+ }
+ if( vic->denyBuild )
+ {
+ if( !Q_stricmp( command, "denybuild" ) )
+ {
+ ADMP( "^3denybuild: ^7player already has no building rights\n" );
+ return qfalse;
+ }
+ vic->denyBuild = qfalse;
+ if( vic->slot > -1 )
+ CPx( vic->slot, "cp \"^1You've regained your building rights\"" );
+ AP( va(
+ "print \"^3allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"",
+ vic->name[ vic->nameOffset ],
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ }
+ else
+ {
+ if( !Q_stricmp( command, "allowbuild" ) )
+ {
+ ADMP( "^3allowbuild: ^7player already has building rights\n" );
+ return qfalse;
+ }
+ vic->denyBuild = qtrue;
+ if( vic->slot > -1 )
+ {
+ level.clients[ vic->slot ].ps.stats[ STAT_BUILDABLE ] = BA_NONE;
+ CPx( vic->slot, "cp \"^1You've lost your building rights\"" );
+ }
+ AP( va(
+ "print \"^3denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"",
+ vic->name[ vic->nameOffset ],
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ }
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", vic->slot, vic->guid,
+ vic->name[ vic->nameOffset ] ) );
+ return qtrue;
+}
+
+qboolean G_admin_listadmins( gentity_t *ent )
+{
+ int i;
+ char search[ MAX_NAME_LENGTH ] = {""};
+ char s[ MAX_NAME_LENGTH ] = {""};
+ int start = MAX_CLIENTS;
+
+ if( trap_Argc() == 3 )
+ {
+ trap_Argv( 2, s, sizeof( s ) );
+ start = atoi( s );
+ }
+ if( trap_Argc() > 1 )
+ {
+ trap_Argv( 1, s, sizeof( s ) );
+ i = 0;
+ if( trap_Argc() == 2 )
+ {
+ i = s[ 0 ] == '-';
+ for( ; isdigit( s[ i ] ); i++ );
+ }
+ if( i && !s[ i ] )
+ start = atoi( s );
+ else
+ G_SanitiseString( s, search, sizeof( search ) );
+ }
+
+ admin_listadmins( ent, start, search );
+ return qtrue;
+}
+
+qboolean G_admin_listlayouts( gentity_t *ent )
+{
+ char list[ MAX_CVAR_VALUE_STRING ];
+ char map[ MAX_QPATH ];
+ int count = 0;
+ char *s;
+ char layout[ MAX_QPATH ] = { "" };
+ int i = 0;
+
+ if( trap_Argc( ) == 2 )
+ trap_Argv( 1, map, sizeof( map ) );
+ else
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+
+ count = G_LayoutList( map, list, sizeof( list ) );
+ ADMBP_begin( );
+ ADMBP( va( "^3listlayouts:^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 i, j;
+ gclient_t *p;
+ char c, t; // color and team letter
+ char *registeredname;
+ char lname[ MAX_NAME_LENGTH ];
+ char muted, denied;
+ int colorlen;
+ char namecleaned[ MAX_NAME_LENGTH ];
+ char name2cleaned[ MAX_NAME_LENGTH ];
+ g_admin_level_t *l;
+ g_admin_level_t *d = G_admin_level( 0 );
+ qboolean hint;
+ qboolean canset = G_admin_permission( ent, "setlevel" );
+
+ ADMBP_begin();
+ ADMBP( va( "^3listplayers: ^7%d players connected:\n",
+ level.numConnectedClients ) );
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ p = &level.clients[ i ];
+ if( p->pers.connected == CON_DISCONNECTED )
+ continue;
+ if( p->pers.connected == CON_CONNECTING )
+ {
+ t = 'C';
+ c = COLOR_YELLOW;
+ }
+ else
+ {
+ t = toupper( *( BG_TeamName( p->pers.teamSelection ) ) );
+ if( p->pers.teamSelection == TEAM_HUMANS )
+ c = COLOR_CYAN;
+ else if( p->pers.teamSelection == TEAM_ALIENS )
+ c = COLOR_RED;
+ else
+ c = COLOR_WHITE;
+ }
+
+ muted = p->pers.namelog->muted ? 'M' : ' ';
+ denied = p->pers.namelog->denyBuild ? 'B' : ' ';
+
+ l = d;
+ registeredname = NULL;
+ hint = canset;
+ if( p->pers.admin )
+ {
+ if( hint )
+ hint = admin_higher( ent, &g_entities[ i ] );
+ if( hint || !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) )
+ {
+ l = G_admin_level( p->pers.admin->level );
+ G_SanitiseString( p->pers.netname, namecleaned,
+ sizeof( namecleaned ) );
+ G_SanitiseString( p->pers.admin->name,
+ name2cleaned, sizeof( name2cleaned ) );
+ if( Q_stricmp( namecleaned, name2cleaned ) )
+ registeredname = p->pers.admin->name;
+ }
+ }
+
+ if( l )
+ Q_strncpyz( lname, l->name, sizeof( lname ) );
+
+ for( colorlen = j = 0; lname[ j ]; j++ )
+ {
+ if( Q_IsColorString( &lname[ j ] ) )
+ colorlen += 2;
+ }
+
+ ADMBP( va( "%2i ^%c%c^7 %-2i^2%c^7 %*s^7 ^1%c%c^7 %s^7 %s%s%s\n",
+ i,
+ c,
+ t,
+ l ? l->level : 0,
+ hint ? '*' : ' ',
+ admin_level_maxname + colorlen,
+ lname,
+ muted,
+ denied,
+ p->pers.netname,
+ ( registeredname ) ? "(a.k.a. " : "",
+ ( registeredname ) ? registeredname : "",
+ ( registeredname ) ? S_COLOR_WHITE ")" : "" ) );
+
+ }
+ ADMBP_end();
+ return qtrue;
+}
+
+static qboolean ban_matchip( void *ban, const void *ip )
+{
+ return G_AddressCompare( &((g_admin_ban_t *)ban)->ip, (addr_t *)ip ) ||
+ G_AddressCompare( (addr_t *)ip, &((g_admin_ban_t *)ban)->ip );
+}
+static qboolean ban_matchname( void *ban, const void *name )
+{
+ char match[ MAX_NAME_LENGTH ];
+
+ G_SanitiseString( ( (g_admin_ban_t *)ban )->name, match, sizeof( match ) );
+ return strstr( match, (const char *)name ) != NULL;
+}
+static void ban_out( void *ban, char *str )
+{
+ int i;
+ int colorlen1 = 0;
+ char duration[ MAX_DURATION_LENGTH ];
+ char *d_color = S_COLOR_WHITE;
+ char date[ 11 ];
+ g_admin_ban_t *b = ( g_admin_ban_t * )ban;
+ int t = trap_RealTime( NULL );
+ char *made = b->made;
+
+ for( i = 0; b->name[ i ]; i++ )
+ {
+ if( Q_IsColorString( &b->name[ i ] ) )
+ colorlen1 += 2;
+ }
+
+ // only print out the the date part of made
+ date[ 0 ] = '\0';
+ for( i = 0; *made && *made != ' ' && i < sizeof( date ) - 1; i++ )
+ date[ i ] = *made++;
+ date[ i ] = 0;
+
+ if( !b->expires || b->expires - t > 0 )
+ G_admin_duration( b->expires ? b->expires - t : - 1,
+ duration, sizeof( duration ) );
+ else
+ {
+ Q_strncpyz( duration, "expired", sizeof( duration ) );
+ d_color = S_COLOR_CYAN;
+ }
+
+ Com_sprintf( str, MAX_STRING_CHARS, "%-*s %s%-15s " S_COLOR_WHITE "%-8s %s"
+ "\n \\__ %s%-*s " S_COLOR_WHITE "%s",
+ MAX_NAME_LENGTH + colorlen1 - 1, b->name,
+ ( strchr( b->ip.str, '/' ) ) ? S_COLOR_RED : S_COLOR_WHITE,
+ b->ip.str,
+ date,
+ b->banner,
+ d_color,
+ MAX_DURATION_LENGTH - 1,
+ duration,
+ b->reason );
+}
+qboolean G_admin_showbans( gentity_t *ent )
+{
+ int i;
+ int start = 1;
+ char filter[ MAX_NAME_LENGTH ] = {""};
+ char name_match[ MAX_NAME_LENGTH ] = {""};
+ qboolean ipmatch = qfalse;
+ addr_t ip;
+
+ if( trap_Argc() == 3 )
+ {
+ trap_Argv( 2, filter, sizeof( filter ) );
+ start = atoi( filter );
+ }
+ if( trap_Argc() > 1 )
+ {
+ trap_Argv( 1, filter, sizeof( filter ) );
+ i = 0;
+ if( trap_Argc() == 2 )
+ for( i = filter[ 0 ] == '-'; isdigit( filter[ i ] ); i++ );
+ if( !filter[ i ] )
+ start = atoi( filter );
+ else if( !( ipmatch = G_AddressParse( filter, &ip ) ) )
+ G_SanitiseString( filter, name_match, sizeof( name_match ) );
+ }
+
+ admin_search( ent, "showbans", "bans",
+ ipmatch ? ban_matchip : ban_matchname,
+ ban_out, g_admin_bans,
+ ipmatch ? (void * )&ip : (void *)name_match,
+ start, 1, MAX_ADMIN_SHOWBANS );
+ return qtrue;
+}
+
+qboolean G_admin_adminhelp( gentity_t *ent )
+{
+ g_admin_command_t *c;
+ if( trap_Argc() < 2 )
+ {
+ int i;
+ int count = 0;
+
+ ADMBP_begin();
+ for( i = 0; i < adminNumCmds; i++ )
+ {
+ if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) )
+ {
+ ADMBP( va( "^3%-12s", g_admin_cmds[ i ].keyword ) );
+ count++;
+ // show 6 commands per line
+ if( count % 6 == 0 )
+ ADMBP( "\n" );
+ }
+ }
+ for( c = g_admin_commands; c; c = c->next )
+ {
+ if( !G_admin_permission( ent, c->flag ) )
+ continue;
+ ADMBP( va( "^3%-12s", c->command ) );
+ count++;
+ // show 6 commands per line
+ if( count % 6 == 0 )
+ ADMBP( "\n" );
+ }
+ if( count % 6 )
+ ADMBP( "\n" );
+ ADMBP( va( "^3adminhelp: ^7%i available commands\n", count ) );
+ ADMBP( "run adminhelp [^3command^7] for adminhelp with a specific command.\n" );
+ ADMBP_end();
+
+ return qtrue;
+ }
+ else
+ {
+ //!adminhelp param
+ char param[ MAX_ADMIN_CMD_LEN ];
+ g_admin_cmd_t *admincmd;
+ qboolean denied = qfalse;
+
+ trap_Argv( 1, param, sizeof( param ) );
+ ADMBP_begin();
+ if( ( c = G_admin_command( param ) ) )
+ {
+ if( G_admin_permission( ent, c->flag ) )
+ {
+ ADMBP( va( "^3adminhelp: ^7help for '%s':\n", c->command ) );
+ ADMBP( va( " ^3Description: ^7%s\n", c->desc ) );
+ ADMBP( va( " ^3Syntax: ^7%s\n", c->command ) );
+ ADMBP( va( " ^3Flag: ^7'%s'\n", c->flag ) );
+ ADMBP_end( );
+ return qtrue;
+ }
+ denied = qtrue;
+ }
+ if( ( admincmd = G_admin_cmd( param ) ) )
+ {
+ if( G_admin_permission( ent, admincmd->flag ) )
+ {
+ ADMBP( va( "^3adminhelp: ^7help for '%s':\n", admincmd->keyword ) );
+ ADMBP( va( " ^3Description: ^7%s\n", admincmd->function ) );
+ ADMBP( va( " ^3Syntax: ^7%s %s\n", admincmd->keyword,
+ admincmd->syntax ) );
+ ADMBP( va( " ^3Flag: ^7'%s'\n", admincmd->flag ) );
+ ADMBP_end();
+ return qtrue;
+ }
+ denied = qtrue;
+ }
+ ADMBP( va( "^3adminhelp: ^7%s '%s'\n",
+ denied ? "you do not have permission to use" : "no help found for",
+ param ) );
+ ADMBP_end( );
+ return qfalse;
+ }
+}
+
+qboolean G_admin_admintest( gentity_t *ent )
+{
+ g_admin_level_t *l;
+
+ if( !ent )
+ {
+ ADMP( "^3admintest: ^7you are on the console.\n" );
+ return qtrue;
+ }
+
+ l = G_admin_level( ent->client->pers.admin ? ent->client->pers.admin->level : 0 );
+
+ AP( va( "print \"^3admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"",
+ ent->client->pers.netname,
+ l ? l->level : 0,
+ l ? "(" : "",
+ l ? l->name : "",
+ l ? ")" : "" ) );
+ return qtrue;
+}
+
+qboolean G_admin_allready( gentity_t *ent )
+{
+ int i = 0;
+ gclient_t *cl;
+
+ if( !level.intermissiontime )
+ {
+ ADMP( "^3allready: ^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 == TEAM_NONE )
+ continue;
+
+ cl->readyToExit = qtrue;
+ }
+ AP( va( "print \"^3allready:^7 %s^7 says everyone is READY now\n\"",
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ return qtrue;
+}
+
+qboolean G_admin_endvote( gentity_t *ent )
+{
+ char teamName[ sizeof( "spectators" ) ] = {"s"};
+ char command[ MAX_ADMIN_CMD_LEN ];
+ team_t team;
+ qboolean cancel;
+ char *msg;
+
+ trap_Argv( 0, command, sizeof( command ) );
+ cancel = !Q_stricmp( command, "cancelvote" );
+ if( trap_Argc() == 2 )
+ trap_Argv( 1, teamName, sizeof( teamName ) );
+ team = G_TeamFromString( teamName );
+ if( team == NUM_TEAMS )
+ {
+ ADMP( va( "^3%s: ^7invalid team '%s'\n", command, teamName ) );
+ return qfalse;
+ }
+ msg = va( "print \"^3%s: ^7%s^7 decided that everyone voted %s\n\"",
+ command, ( ent ) ? ent->client->pers.netname : "console",
+ cancel ? "No" : "Yes" );
+ if( !level.voteTime[ team ] )
+ {
+ ADMP( va( "^3%s: ^7no vote in progress\n", command ) );
+ return qfalse;
+ }
+ admin_log( BG_TeamName( team ) );
+ level.voteNo[ team ] = cancel ? level.numVotingClients[ team ] : 0;
+ level.voteYes[ team ] = cancel ? 0 : level.numVotingClients[ team ];
+ G_CheckVote( team );
+ if( team == TEAM_NONE )
+ AP( msg );
+ else
+ G_TeamCommand( team, msg );
+ return qtrue;
+}
+
+qboolean G_admin_spec999( gentity_t *ent )
+{
+ 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 == TEAM_NONE )
+ continue;
+ if( vic->client->ps.ping == 999 )
+ {
+ G_ChangeTeam( vic, TEAM_NONE );
+ AP( va( "print \"^3spec999: ^7%s^7 moved %s^7 to spectators\n\"",
+ ( ent ) ? ent->client->pers.netname : "console",
+ vic->client->pers.netname ) );
+ }
+ }
+ return qtrue;
+}
+
+qboolean G_admin_rename( gentity_t *ent )
+{
+ int pid;
+ char name[ MAX_NAME_LENGTH ];
+ char newname[ MAX_NAME_LENGTH ];
+ char err[ MAX_STRING_CHARS ];
+ char userinfo[ MAX_INFO_STRING ];
+ gentity_t *victim = NULL;
+
+ if( trap_Argc() < 3 )
+ {
+ ADMP( "^3rename: ^7usage: rename [name] [newname]\n" );
+ return qfalse;
+ }
+ trap_Argv( 1, name, sizeof( name ) );
+ Q_strncpyz( newname, ConcatArgs( 2 ), sizeof( newname ) );
+ if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 )
+ {
+ ADMP( va( "^3rename: ^7%s", err ) );
+ return qfalse;
+ }
+ victim = &g_entities[ pid ];
+ if( !admin_higher( ent, victim ) )
+ {
+ ADMP( "^3rename: ^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 ) ) )
+ {
+ ADMP( va( "^3rename: ^7%s\n", err ) );
+ return qfalse;
+ }
+ if( victim->client->pers.connected != CON_CONNECTED )
+ {
+ ADMP( "^3rename: ^7sorry, but your intended victim is still connecting\n" );
+ return qfalse;
+ }
+ admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", pid,
+ victim->client->pers.guid, victim->client->pers.netname ) );
+ admin_log( newname );
+ trap_GetUserinfo( pid, userinfo, sizeof( userinfo ) );
+ AP( va( "print \"^3rename: ^7%s^7 has been renamed to %s^7 by %s\n\"",
+ victim->client->pers.netname,
+ newname,
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ Info_SetValueForKey( userinfo, "name", newname );
+ trap_SetUserinfo( pid, userinfo );
+ ClientUserinfoChanged( pid, qtrue );
+ return qtrue;
+}
+
+qboolean G_admin_restart( gentity_t *ent )
+{
+ char layout[ MAX_CVAR_VALUE_STRING ] = { "" };
+ char teampref[ MAX_STRING_CHARS ] = { "" };
+ int i;
+ gclient_t *cl;
+
+ if( trap_Argc( ) > 1 )
+ {
+ char map[ MAX_QPATH ];
+
+ trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );
+ trap_Argv( 1, layout, sizeof( layout ) );
+
+ // Figure out which argument is which
+ 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( "^3restart: ^7layout '%s' does not exist\n", layout ) );
+ return qfalse;
+ }
+ }
+ else
+ {
+ layout[ 0 ] = '\0';
+ trap_Argv( 1, teampref, sizeof( teampref ) );
+ }
+ }
+
+ if( trap_Argc( ) > 2 )
+ trap_Argv( 2, teampref, sizeof( teampref ) );
+
+ admin_log( layout );
+ admin_log( teampref );
+
+ if( !Q_stricmpn( teampref, "keepteams", 9 ) )
+ {
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->pers.teamSelection == TEAM_NONE )
+ continue;
+
+ cl->sess.restartTeam = cl->pers.teamSelection;
+ }
+ }
+ else if( !Q_stricmpn( teampref, "switchteams", 11 ) )
+ {
+ for( i = 0; i < g_maxclients.integer; i++ )
+ {
+ cl = level.clients + i;
+
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ if( cl->pers.teamSelection == TEAM_HUMANS )
+ cl->sess.restartTeam = TEAM_ALIENS;
+ else if(cl->pers.teamSelection == TEAM_ALIENS )
+ cl->sess.restartTeam = TEAM_HUMANS;
+ }
+ }
+
+ if( !Q_stricmp( teampref, "switchteamslock" ) ||
+ !Q_stricmp( teampref, "keepteamslock" ) )
+ trap_Cvar_Set( "g_lockTeamsAtStart", "1" );
+
+ trap_SendConsoleCommand( EXEC_APPEND, "map_restart" );
+
+ AP( va( "print \"^3restart: ^7map restarted by %s %s %s\n\"",
+ ( ent ) ? ent->client->pers.netname : "console",
+ ( layout[ 0 ] ) ? va( "^7(forcing layout '%s^7')", layout ) : "",
+ ( teampref[ 0 ] ) ? va( "^7(with teams option: '%s^7')", teampref ) : "" ) );
+ return qtrue;
+}
+
+qboolean G_admin_nextmap( gentity_t *ent )
+{
+ AP( va( "print \"^3nextmap: ^7%s^7 decided to load the next map\n\"",
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ level.lastWin = TEAM_NONE;
+ trap_SetConfigstring( CS_WINNER, "Evacuation" );
+ LogExit( va( "nextmap was run by %s",
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ return qtrue;
+}
+
+static qboolean namelog_matchip( void *namelog, const void *ip )
+{
+ int i;
+ namelog_t *n = (namelog_t *)namelog;
+ for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ )
+ {
+ if( G_AddressCompare( &n->ip[ i ], (addr_t *)ip ) ||
+ G_AddressCompare( (addr_t *)ip, &n->ip[ i ] ) )
+ return qtrue;
+ }
+ return qfalse;
+}
+static qboolean namelog_matchname( void *namelog, const void *name )
+{
+ char match[ MAX_NAME_LENGTH ];
+ int i;
+ namelog_t *n = (namelog_t *)namelog;
+ for( i = 0; i < MAX_NAMELOG_NAMES && n->name[ i ][ 0 ]; i++ )
+ {
+ G_SanitiseString( n->name[ i ], match, sizeof( match ) );
+ if( strstr( match, (const char *)name ) )
+ return qtrue;
+ }
+ return qfalse;
+}
+static void namelog_out( void *namelog, char *str )
+{
+ namelog_t *n = (namelog_t *)namelog;
+ char *p = str;
+ int l, l2 = MAX_STRING_CHARS, i;
+ const char *scolor;
+
+ if( n->slot > -1 )
+ {
+ scolor = S_COLOR_YELLOW;
+ l = Q_snprintf( p, l2, "%s%-2d", scolor, n->slot );
+ p += l;
+ l2 -= l;
+ }
+ else
+ {
+ *p++ = '-';
+ *p++ = ' ';
+ *p = '\0';
+ l2 -= 2;
+ scolor = S_COLOR_WHITE;
+ }
+
+ for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ )
+ {
+ l = Q_snprintf( p, l2, " %s", n->ip[ i ].str );
+ p += l;
+ l2 -= l;
+ }
+
+ for( i = 0; i < MAX_NAMELOG_NAMES && n->name[ i ][ 0 ]; i++ )
+ {
+ l = Q_snprintf( p, l2, " '" S_COLOR_WHITE "%s%s'%s", n->name[ i ], scolor,
+ i == n->nameOffset ? "*" : "" );
+ p += l;
+ l2 -= l;
+ }
+}
+qboolean G_admin_namelog( gentity_t *ent )
+{
+ char search[ MAX_NAME_LENGTH ] = {""};
+ char s2[ MAX_NAME_LENGTH ] = {""};
+ addr_t ip;
+ qboolean ipmatch = qfalse;
+ int start = MAX_CLIENTS, i;
+
+ if( trap_Argc() == 3 )
+ {
+ trap_Argv( 2, search, sizeof( search ) );
+ start = atoi( search );
+ }
+ if( trap_Argc() > 1 )
+ {
+ trap_Argv( 1, search, sizeof( search ) );
+ i = 0;
+ if( trap_Argc() == 2 )
+ for( i = search[ 0 ] == '-'; isdigit( search[ i ] ); i++ );
+ if( !search[ i ] )
+ start = atoi( search );
+ else if( !( ipmatch = G_AddressParse( search, &ip ) ) )
+ G_SanitiseString( search, s2, sizeof( s2 ) );
+ }
+
+ admin_search( ent, "namelog", "recent players",
+ ipmatch ? namelog_matchip : namelog_matchname, namelog_out, level.namelogs,
+ ipmatch ? (void *)&ip : s2, start, MAX_CLIENTS, MAX_ADMIN_LISTITEMS );
+ return qtrue;
+}
+
+/*
+==================
+G_NamelogFromString
+
+This is similar to G_ClientNumberFromString but for namelog instead
+Returns NULL if not exactly 1 match
+==================
+*/
+namelog_t *G_NamelogFromString( gentity_t *ent, char *s )
+{
+ namelog_t *p, *m = NULL;
+ int i, found = 0;
+ char n2[ MAX_NAME_LENGTH ] = {""};
+ char s2[ MAX_NAME_LENGTH ] = {""};
+
+ if( !s[ 0 ] )
+ return NULL;
+
+ // if a number is provided, it is a clientnum or namelog id
+ for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ );
+ if( !s[ i ] )
+ {
+ i = atoi( s );
+
+ if( i >= 0 && i < level.maxclients )
+ {
+ if( level.clients[ i ].pers.connected != CON_DISCONNECTED )
+ return level.clients[ i ].pers.namelog;
+ }
+ else if( i >= MAX_CLIENTS )
+ {
+ for( p = level.namelogs; p; p = p->next )
+ {
+ if( p->id == i )
+ break;
+ }
+ if( p )
+ return p;
+ }
+
+ return NULL;
+ }
+
+ // check for a name match
+ G_SanitiseString( s, s2, sizeof( s2 ) );
+
+ for( p = level.namelogs; p; p = p->next )
+ {
+ for( i = 0; i < MAX_NAMELOG_NAMES && p->name[ i ][ 0 ]; i++ )
+ {
+ G_SanitiseString( p->name[ i ], n2, sizeof( n2 ) );
+
+ // if this is an exact match to a current player
+ if( i == p->nameOffset && p->slot > -1 && !strcmp( s2, n2 ) )
+ return p;
+
+ if( strstr( n2, s2 ) )
+ m = p;
+ }
+
+ if( m == p )
+ found++;
+ }
+
+ if( found == 1 )
+ return m;
+
+ if( found > 1 )
+ admin_search( ent, "namelog", "recent players", namelog_matchname,
+ namelog_out, level.namelogs, s2, 0, MAX_CLIENTS, -1 );
+
+ return NULL;
+}
+
+qboolean G_admin_lock( gentity_t *ent )
+{
+ char command[ MAX_ADMIN_CMD_LEN ];
+ char teamName[ sizeof( "aliens" ) ];
+ team_t team;
+ qboolean lock, fail = qfalse;
+
+ trap_Argv( 0, command, sizeof( command ) );
+ if( trap_Argc() < 2 )
+ {
+ ADMP( va( "^3%s: ^7usage: %s [a|h]\n", command, command ) );
+ return qfalse;
+ }
+ lock = !Q_stricmp( command, "lock" );
+ trap_Argv( 1, teamName, sizeof( teamName ) );
+ team = G_TeamFromString( teamName );
+
+ if( team == TEAM_ALIENS )
+ {
+ if( level.alienTeamLocked == lock )
+ fail = qtrue;
+ else
+ level.alienTeamLocked = lock;
+ }
+ else if( team == TEAM_HUMANS )
+ {
+ if( level.humanTeamLocked == lock )
+ fail = qtrue;
+ else
+ level.humanTeamLocked = lock;
+ }
+ else
+ {
+ ADMP( va( "^3%s: ^7invalid team: '%s'\n", command, teamName ) );
+ return qfalse;
+ }
+
+ if( fail )
+ {
+ ADMP( va( "^3%s: ^7the %s team is %s locked\n",
+ command, BG_TeamName( team ), lock ? "already" : "not currently" ) );
+
+ return qfalse;
+ }
+
+ admin_log( BG_TeamName( team ) );
+ AP( va( "print \"^3%s: ^7the %s team has been %slocked by %s\n\"",
+ command, BG_TeamName( team ), lock ? "" : "un",
+ ent ? ent->client->pers.netname : "console" ) );
+
+ return qtrue;
+}
+
+qboolean G_admin_builder( gentity_t *ent )
+{
+ vec3_t forward, right, up;
+ vec3_t start, end, dist;
+ trace_t tr;
+ gentity_t *traceEnt;
+ buildLog_t *log;
+ int i;
+
+ if( !ent )
+ {
+ ADMP( "^3builder: ^7console can't aim.\n" );
+ return qfalse;
+ }
+
+ AngleVectors( ent->client->ps.viewangles, forward, right, up );
+ if( ent->client->pers.teamSelection != TEAM_NONE &&
+ ent->client->sess.spectatorState == SPECTATOR_NOT )
+ CalcMuzzlePoint( ent, forward, right, up, start );
+ else
+ VectorCopy( ent->client->ps.origin, start );
+ VectorMA( start, 1000, forward, end );
+
+ trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID );
+ traceEnt = &g_entities[ tr.entityNum ];
+ if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) )
+ {
+ if( !G_admin_permission( ent, "buildlog" ) &&
+ ent->client->pers.teamSelection != TEAM_NONE &&
+ ent->client->pers.teamSelection != traceEnt->buildableTeam )
+ {
+ ADMP( "^3builder: ^7structure not owned by your team\n" );
+ return qfalse;
+ }
+
+ for( i = 0 ; i < level.numBuildLogs; i++ )
+ {
+ log = &level.buildLog[ ( level.buildId - i - 1 ) % MAX_BUILDLOG ];
+ if( log->fate != BF_CONSTRUCT || traceEnt->s.modelindex != log->modelindex )
+ continue;
+
+ VectorSubtract( traceEnt->s.pos.trBase, log->origin, dist );
+ if( VectorLengthSquared( dist ) < 2.0f )
+ {
+ char logid[ 20 ] = {""};
+
+ if( G_admin_permission( ent, "buildlog" ) )
+ Com_sprintf( logid, sizeof( logid ), ", buildlog #%d",
+ MAX_CLIENTS + level.buildId - i - 1 );
+ ADMP( va( "^3builder: ^7%s built by %s^7%s\n",
+ BG_Buildable( log->modelindex, NULL )->humanName,
+ log->actor ?
+ log->actor->name[ log->actor->nameOffset ] :
+ "<world>",
+ logid ) );
+ break;
+ }
+ }
+ if( i == level.numBuildLogs )
+ ADMP( va( "^3builder: ^7%s not in build log, possibly a layout item\n",
+ BG_Buildable( traceEnt->s.modelindex, NULL )->humanName ) );
+ }
+ else
+ ADMP( "^3builder: ^7no structure found under crosshair\n" );
+
+ return qtrue;
+}
+
+qboolean G_admin_pause( gentity_t *ent )
+{
+ if( !level.pausedTime )
+ {
+ AP( va( "print \"^3pause: ^7%s^7 paused the game.\n\"",
+ ( ent ) ? ent->client->pers.netname : "console" ) );
+ level.pausedTime = 1;
+ trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" );
+ }
+ else
+ {
+ // Prevent accidental pause->unpause race conditions by two admins
+ if( level.pausedTime < 1000 )
+ {
+ ADMP( "^3pause: ^7Unpausing so soon assumed accidental and ignored.\n" );
+ return qfalse;
+ }
+
+ AP( va( "print \"^3pause: ^7%s^7 unpaused the game (Paused for %d sec) \n\"",
+ ( ent ) ? ent->client->pers.netname : "console",
+ (int) ( (float) level.pausedTime / 1000.0f ) ) );
+ trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" );
+
+ level.pausedTime = 0;
+ }
+
+ return qtrue;
+}
+
+static char *fates[] =
+{
+ "^2built^7",
+ "^3deconstructed^7",
+ "^7replaced^7",
+ "^3destroyed^7",
+ "^1TEAMKILLED^7",
+ "^7unpowered^7",
+ "removed"
+};
+qboolean G_admin_buildlog( gentity_t *ent )
+{
+ char search[ MAX_NAME_LENGTH ] = {""};
+ char s[ MAX_NAME_LENGTH ] = {""};
+ char n[ MAX_NAME_LENGTH ];
+ char stamp[ 8 ];
+ int id = -1;
+ int printed = 0;
+ int time;
+ int start = MAX_CLIENTS + level.buildId - level.numBuildLogs;
+ int i = 0, j;
+ buildLog_t *log;
+
+ if( !level.buildId )
+ {
+ ADMP( "^3buildlog: ^7log is empty\n" );
+ return qtrue;
+ }
+
+ if( trap_Argc() == 3 )
+ {
+ trap_Argv( 2, search, sizeof( search ) );
+ start = atoi( search );
+ }
+ if( trap_Argc() > 1 )
+ {
+ trap_Argv( 1, search, sizeof( search ) );
+ for( i = search[ 0 ] == '-'; isdigit( search[ i ] ); i++ );
+ if( i && !search[ i ] )
+ {
+ id = atoi( search );
+ if( trap_Argc() == 2 && ( id < 0 || id >= MAX_CLIENTS ) )
+ {
+ start = id;
+ id = -1;
+ }
+ else if( id < 0 || id >= MAX_CLIENTS ||
+ level.clients[ id ].pers.connected != CON_CONNECTED )
+ {
+ ADMP( "^3buildlog: ^7invalid client id\n" );
+ return qfalse;
+ }
+ }
+ else
+ G_SanitiseString( search, s, sizeof( s ) );
+ }
+ else
+ start = MAX( -MAX_ADMIN_LISTITEMS, -level.buildId );
+
+ if( start < 0 )
+ start = MAX( level.buildId - level.numBuildLogs, start + level.buildId );
+ else
+ start -= MAX_CLIENTS;
+ if( start < level.buildId - level.numBuildLogs || start >= level.buildId )
+ {
+ ADMP( "^3buildlog: ^7invalid build ID\n" );
+ return qfalse;
+ }
+
+ if( ent && ent->client->pers.teamSelection != TEAM_NONE )
+ trap_SendServerCommand( -1,
+ va( "print \"^3buildlog: ^7%s^7 requested a log of recent building activity\n\"",
+ ent->client->pers.netname ) );
+
+ ADMBP_begin();
+ for( i = start; i < level.buildId && printed < MAX_ADMIN_LISTITEMS; i++ )
+ {
+ log = &level.buildLog[ i % MAX_BUILDLOG ];
+ if( id >= 0 && id < MAX_CLIENTS )
+ {
+ if( log->actor != level.clients[ id ].pers.namelog )
+ continue;
+ }
+ else if( s[ 0 ] )
+ {
+ if( !log->actor )
+ continue;
+ for( j = 0; j < MAX_NAMELOG_NAMES && log->actor->name[ j ][ 0 ]; j++ )
+ {
+ G_SanitiseString( log->actor->name[ j ], n, sizeof( n ) );
+ if( strstr( n, s ) )
+ break;
+ }
+ if( j >= MAX_NAMELOG_NAMES || !log->actor->name[ j ][ 0 ] )
+ continue;
+ }
+ printed++;
+ time = ( log->time - level.startTime ) / 1000;
+ Com_sprintf( stamp, sizeof( stamp ), "%3d:%02d", time / 60, time % 60 );
+ ADMBP( va( "^2%c^7%-3d %s ^7%s^7 %s%s%s\n",
+ log->actor && log->fate != BF_REPLACE && log->fate != BF_UNPOWER ?
+ '*' : ' ',
+ i + MAX_CLIENTS,
+ log->actor && ( log->fate == BF_REPLACE || log->fate == BF_UNPOWER ) ?
+ " \\_" : stamp,
+ BG_Buildable( log->modelindex, NULL )->humanName,
+ fates[ log->fate ],
+ log->actor ? " by " : "",
+ log->actor ?
+ log->actor->name[ log->actor->nameOffset ] :
+ "" ) );
+ }
+ ADMBP( va( "^3buildlog: ^7showing %d build logs %d - %d of %d - %d. %s\n",
+ printed, start + MAX_CLIENTS, i + MAX_CLIENTS - 1,
+ level.buildId + MAX_CLIENTS - level.numBuildLogs,
+ level.buildId + MAX_CLIENTS - 1,
+ i < level.buildId ? va( "run 'buildlog %s%s%d' to see more",
+ search, search[ 0 ] ? " " : "", i + MAX_CLIENTS ) : "" ) );
+ ADMBP_end();
+ return qtrue;
+}
+
+qboolean G_admin_revert( gentity_t *ent )
+{
+ char arg[ MAX_TOKEN_CHARS ];
+ char time[ MAX_DURATION_LENGTH ];
+ int id;
+ buildLog_t *log;
+
+ if( trap_Argc() != 2 )
+ {
+ ADMP( "^3revert: ^7usage: revert [id]\n" );
+ return qfalse;
+ }
+
+ trap_Argv( 1, arg, sizeof( arg ) );
+ id = atoi( arg ) - MAX_CLIENTS;
+ if( id < level.buildId - level.numBuildLogs || id >= level.buildId )
+ {
+ ADMP( "^3revert: ^7invalid id\n" );
+ return qfalse;
+ }
+
+ log = &level.buildLog[ id % MAX_BUILDLOG ];
+ if( !log->actor || log->fate == BF_REPLACE || log->fate == BF_UNPOWER )
+ {
+ // fixme: then why list them with an id # in build log ? - rez
+ ADMP( "^3revert: ^7you can only revert direct player actions, "
+ "indicated by ^2* ^7in buildlog\n" );
+ return qfalse;
+ }
+
+ G_admin_duration( ( level.time - log->time ) / 1000, time,
+ sizeof( time ) );
+ admin_log( arg );
+ AP( va( "print \"^3revert: ^7%s^7 reverted %d %s over the past %s\n\"",
+ ent ? ent->client->pers.netname : "console",
+ level.buildId - id,
+ ( level.buildId - id ) > 1 ? "changes" : "change",
+ time ) );
+ G_BuildLogRevert( id );
+ return qtrue;
+}
+
+/*
+================
+ 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, sizeof( m2 ) );
+ trap_Print( m2 );
+ }
+ else
+ trap_Print( m );
+ }
+}
+
+void G_admin_buffer_begin( void )
+{
+ 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( void )
+{
+ g_admin_level_t *l;
+ g_admin_admin_t *a;
+ g_admin_ban_t *b;
+ g_admin_command_t *c;
+ void *n;
+
+ for( l = g_admin_levels; l; l = n )
+ {
+ n = l->next;
+ BG_Free( l );
+ }
+ g_admin_levels = NULL;
+ for( a = g_admin_admins; a; a = n )
+ {
+ n = a->next;
+ BG_Free( a );
+ }
+ g_admin_admins = NULL;
+ for( b = g_admin_bans; b; b = n )
+ {
+ n = b->next;
+ BG_Free( b );
+ }
+ g_admin_bans = NULL;
+ for( c = g_admin_commands; c; c = n )
+ {
+ n = c->next;
+ BG_Free( c );
+ }
+ g_admin_commands = NULL;
+ BG_DefragmentMemory( );
+}