diff options
author | Mikko Tiusanen <ams@daug.net> | 2014-05-04 01:18:52 +0300 |
---|---|---|
committer | Mikko Tiusanen <ams@daug.net> | 2014-05-04 01:18:52 +0300 |
commit | 01beb9919b95479d8be040bec74abc5cc67a5e43 (patch) | |
tree | 65f0b79e793848491832756a4c3a32b23668fab3 /src/game/g_admin.c | |
parent | 191d731da136b7ee959a17e63111c9146219a768 (diff) |
Initial import.
Diffstat (limited to 'src/game/g_admin.c')
-rw-r--r-- | src/game/g_admin.c | 4218 |
1 files changed, 4218 insertions, 0 deletions
diff --git a/src/game/g_admin.c b/src/game/g_admin.c new file mode 100644 index 0000000..c134351 --- /dev/null +++ b/src/game/g_admin.c @@ -0,0 +1,4218 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ +/* +=========================================================================== +TREMULOUS EDGE MOD SRC FILE +=========================================================================== +*/ +#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", + "[^5ban#^5] (^5/mask^5) (^5duration^5) (^5reason^5)" + }, + + {"adminhelp", G_admin_adminhelp, qtrue, "adminhelp", + "display admin commands available to you or help on a specific command", + "(^5command^5)" + }, + + {"admintest", G_admin_admintest, qfalse, "admintest", + "display your current admin level", + "" + }, + + {"allowbuild", G_admin_denybuild, qfalse, "denybuild", + "restore a player's ability to build", + "[^5name|slot#^5]" + }, + + {"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", + "[^5name|slot#|IP(/mask)^5] (^5duration^5) (^5reason^5)" + }, + + {"builder", G_admin_builder, qtrue, "builder", + "show who built a structure", + "" + }, + + {"buildlog", G_admin_buildlog, qfalse, "buildlog", + "show buildable log", + "(^5name|slot#^5) (^5id^5)" + }, + + {"cancelvote", G_admin_endvote, qfalse, "cancelvote", + "cancel a vote taking place", + "(^5a|h^5)" + }, + + {"changemap", G_admin_changemap, qfalse, "changemap", + "load a map (and optionally force layout)", + "[^5mapname^5] (^5layout^5)" + }, + + {"cpa", G_admin_cp, qfalse, "cpa", + "display a brief Center Print Announcement message to users, optionally specifying team(s) to send to", + "(^5-A|H|S^5) [^5message^5]" + }, + + {"denybuild", G_admin_denybuild, qfalse, "denybuild", + "take away a player's ability to build", + "[^5name|slot#^5]" + }, + + {"flag", G_admin_flag, qfalse, "flag", + "add an admin flag to a player, prefix flag with '-' to disallow the flag. " + "console can use this command on admin levels by prefacing a '*' to the admin level value.", + "[^5name|slot#|admin#|*level#^5] (^5+^5|^5-^5)[^5flag^5]" + }, + + {"flaglist", G_admin_flaglist, qtrue, "flag", + "list the flags understood by this server", + "" + }, + + {"info", G_admin_info, qtrue, "info", + "display server information files", + "(^5subject^5)" + }, + + {"kick", G_admin_kick, qfalse, "kick", + "kick a player with an optional reason", + "[^5name|slot#^5] (^5reason^5)" + }, + + {"l0", G_admin_l1, qfalse, "l0", + "remove name protection from a player by setting them to admin level 0", + "[^5name|slot#|admin#^5]" + }, + + {"l1", G_admin_l1, qfalse, "l1", + "give a player name protection by setting them to admin level 1", + "[^5name|slot#^5]" + }, + + {"listadmins", G_admin_listadmins, qtrue, "listadmins", + "display a list of all server admins and their levels", + "(^5name^5) (^5start admin#^5)" + }, + + {"listlayouts", G_admin_listlayouts, qtrue, "listlayouts", + "display a list of all available layouts for a map", + "(^5mapname^5)" + }, + + {"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", + "[^5a|h^5]" + }, + + {"mute", G_admin_mute, qfalse, "mute", + "mute a player", + "[^5name|slot#^5]" + }, + + {"namelog", G_admin_namelog, qtrue, "namelog", + "display a list of names used by recently connected players", + "(^5name|IP(/mask)^5) (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^5)" + }, + + {"pause", G_admin_pause, qfalse, "pause", + "Pause (or unpause) the game.", + "" + }, + + {"putteam", G_admin_putteam, qfalse, "putteam", + "move a player to a specified team", + "[^5name|slot#^5] [^5h|a|s^5]" + }, + + {"readconfig", G_admin_readconfig, qfalse, "readconfig", + "reloads the admin config file and refreshes permission flags", + "" + }, + + {"register", G_admin_register, qfalse, "register", + "register your name to protect it from being used by others. " + "use 'register 0' to remove name protection.", + "(^5level^5)" + }, + + {"rename", G_admin_rename, qfalse, "rename", + "rename a player", + "[^5name|slot#^5] [^5new name^5]" + }, + + {"restart", G_admin_restart, qfalse, "restart", + "restart the current map (optionally using named layout or keeping/switching teams)", + "(^5layout^5) (^5keepteams|switchteams|keepteamslock|switchteamslock^5)" + }, + + {"revert", G_admin_revert, qfalse, "revert", + "revert buildables to a given time", + "[^5id^5]" + }, + + {"setlevel", G_admin_setlevel, qfalse, "setlevel", + "sets the admin level of a player", + "[^5name|slot#|admin#^5] [^5level^5]" + }, + + {"showbans", G_admin_showbans, qtrue, "showbans", + "display a (partial) list of active bans", + "(^5name|IP(/mask)^5) (^5start at ban#^5)" + }, + + {"slap", G_admin_slap, qtrue, "slap", + "Do damage to a player, and send them flying", + "[^5name|slot^5]" + }, + + {"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", + "[^5ban#^5]" + }, + + {"unflag", G_admin_flag, qfalse, "flag", + "clears an admin flag from a player. " + "console can use this command on admin levels by prefacing a '*' to the admin level value.", + "[^5name|slot#|admin#|*level#^5] (^5+^5|^5-^5)[^5flag^5]" + }, + + {"unlock", G_admin_lock, qfalse, "lock", + "unlock a locked team", + "[^5a|h^5]" + }, + + {"unmute", G_admin_mute, qfalse, "mute", + "unmute a muted player", + "[^5name|slot#^5]" + }, + + { + "warn", G_admin_warn, qfalse, "warn", + "warn a player to correct their current activity", + "[^5name|slot#^5] [^5reason^5]" + } + }; + +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; +} + +/*FUN +* If an admin is given, returns his/hers registered name. +* Otherwise retuns his net name. +*/ +const char *admin_name( gentity_t *ent ) { + const gclient_t *client; + if( ( ent != NULL ) && ( ( client = ent->client ) != NULL ) ) + { + if( client->pers.admin ) + return client->pers.admin->name; + else + return client->pers.netname; + } else + return "console"; +} + +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, "^5The name '^7console^5' 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, "^5The name '^7%s^5' 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, "^5The name '^7%s^5' 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 stale bans + if( G_ADMIN_BAN_STALE( b, 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, "^5Senior 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( "^5%s: ^5showing %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^5 %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 ); +} + +static int admin_find_admin( gentity_t *ent, char *name, const char *command, + gentity_t **client, g_admin_admin_t **admin ) +{ + char cleanName[ MAX_NAME_LENGTH ]; + char testName[ MAX_NAME_LENGTH ]; + char *p; + int id = -1; + int matches = 0; + int i; + g_admin_admin_t *a = NULL; + gentity_t *vic = NULL; + qboolean numeric = qtrue; + + if( client ) + *client = NULL; + if( admin ) + *admin = NULL; + if( !name || !name[ 0 ] ) + return -1; + + if( !command ) + command = "match"; + + for( p = name; ( *p ); p++ ) + { + if( !isdigit( *p ) ) + { + numeric = qfalse; + break; + } + } + + if( numeric ) + { + id = atoi( name ); + + if( id >= 0 && id < level.maxclients ) + { + vic = &g_entities[ id ]; + if( !vic || !vic->client || vic->client->pers.connected == CON_DISCONNECTED ) + { + ADMP( va( "^5%s: ^5no player connected in slot %d\n", command, id ) ); + return -1; + } + a = vic->client->pers.admin; + } + else if( id >= MAX_CLIENTS ) + { + for( i = MAX_CLIENTS, a = g_admin_admins; a ; i++, a = a->next ) + { + if( i == id ) + break; + } + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + + if( level.clients[ i ].pers.admin == a ) + { + vic = &g_entities[ i ]; + break; + } + } + } + + if( !vic && !a ) + { + for( i = 0, a = g_admin_admins; a ; i++, a = a->next); + ADMP( va( "^5%s: ^5%s not in range 0-%d or %d-%d\n", + command, name, + level.maxclients - 1, + MAX_CLIENTS, MAX_CLIENTS + i - 1 ) ); + return -1; + } + } + else + { + g_admin_admin_t *wa; + + G_SanitiseString( name, cleanName, sizeof( cleanName ) ); + for( wa = g_admin_admins, i = MAX_CLIENTS; wa && matches < 2; wa = wa->next, i++ ) + { + G_SanitiseString( wa->name, testName, sizeof( testName ) ); + if( strstr( testName, cleanName ) ) + { + id = i; + a = wa; + matches++; + } + } + + for( i = 0; i < level.maxclients && matches < 2; i++ ) + { + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + + if( matches && a && level.clients[ i ].pers.admin == a ) + { + vic = &g_entities[ i ]; + continue; + } + + G_SanitiseString( level.clients[ i ].pers.netname, + testName, sizeof( testName ) ); + if( strstr( testName, cleanName ) ) + { + id = i; + vic = &g_entities[ i ]; + a = vic->client->pers.admin; + matches++; + } + } + + if( matches == 0 ) + { + ADMP( va( "^5%s:^5 no match, use listplayers or listadmins to " + "find an appropriate number to use instead of name.\n", + command ) ); + return -1; + } + if( matches > 1 ) + { + ADMP( va( "^5%s:^5 more than one match, use the admin number instead:\n", + command ) ); + admin_listadmins( ent, 0, cleanName ); + return -1; + } + } + + if( client ) + *client = vic; + if( admin ) + *admin = a; + return id; +} + +#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 ) + { + if( G_ADMIN_BAN_EXPIRED( ban, 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( "^5%s: ^5permission 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( "^5%s: ^5permission 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( "^5readconfig: 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( "^5readconfig: ^5could 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++; + b->id = 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( "^5readconfig: ^5loaded %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( "^5time: ^5local 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 + gentity_t *vic = NULL; + g_admin_admin_t *a = NULL; + g_admin_level_t *l; + + if( trap_Argc() < 3 ) + { + ADMP( "^5setlevel: ^5usage: setlevel [name|slot#] [level]\n" ); + return qfalse; + } + + trap_Argv( 1, name, sizeof( name ) ); + trap_Argv( 2, lstr, sizeof( lstr ) ); + + if( !( l = G_admin_level( atoi( lstr ) ) ) ) + { + ADMP( "^5setlevel: ^5level is not defined\n" ); + return qfalse; + } + + if( ent && l->level > + ( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ) ) + { + ADMP( "^5setlevel: ^5you may not use setlevel to set a level higher " + "than your current level\n" ); + return qfalse; + } + + if( admin_find_admin( ent, name, "setlevel", &vic, &a ) < 0 ) + return qfalse; + + if( ent && !admin_higher_admin( ent->client->pers.admin, a ) ) + { + ADMP( "^5setlevel: ^5sorry, 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 \"^5setlevel: ^5%s^5 was given level %d admin rights by %s\n\"", + a->name, a->level, admin_name( ent ) ) ); + + admin_writeconfig(); + if( vic ) + { + G_admin_authlog( vic ); + G_admin_cmdlist( vic ); + } + return qtrue; +} + +qboolean G_admin_register( gentity_t *ent ) +{ + char arg[ 16 ]; + int oldLevel; + int newLevel = 1; + + if( !ent ) + return qfalse; + + if( ent->client->pers.admin ) + oldLevel = ent->client->pers.admin->level; + else + oldLevel = 0; + + if( trap_Argc( ) > 1 ) + { + trap_Argv( 1, arg, sizeof( arg ) ); + newLevel = atoi( arg ); + + if( newLevel < 0 || newLevel > 1 ) + { + ADMP( "^5register: ^5level can only be 0 or 1\n" ); + return qfalse; + } + + if( newLevel == 0 && oldLevel != 1 ) + { + ADMP( "^5register: ^5you may only remove name protection when level 1. " + "find an admin with setlevel\n"); + return qfalse; + } + } + + if( newLevel != 0 && G_IsNewbieName( ent->client->pers.netname ) ) + { + ADMP( va( "^5register: ^5You cannot register names similar to '%s^5' or 'UnnamedPlayer'\n", + g_newbieNamePrefix.string ) ); + return qfalse; + } + + if( oldLevel < 0 || oldLevel > 1 ) + newLevel = oldLevel; + + trap_SendConsoleCommand( EXEC_APPEND, + va( "setlevel %d %d;", ent - g_entities, newLevel ) ); + + AP( va( "print \"^5register: %s is now a %s nickname. Commands unlocked. ^2Congratulations!\n\"", + ( newLevel == 0 && ent->client->pers.admin ) ? + ent->client->pers.admin->name : ent->client->pers.netname, + newLevel == 0 ? "free" : "protected" ) ); + + return qtrue; +} + +qboolean G_admin_l1( gentity_t *ent ) +{ + char name[ MAX_NAME_LENGTH ]; + char cmd[ 8 ]; + gentity_t *vic = NULL; + g_admin_admin_t *a = NULL; + int id; + int level; + + trap_Argv( 0, cmd, sizeof( cmd ) ); + + if( trap_Argc() < 2 ) + { + ADMP( va( "^5%s: ^5usage: %s [name|slot#|admin#]\n", + cmd, cmd ) ); + return qfalse; + } + trap_Argv( 1, name, sizeof( name ) ); + + id = admin_find_admin( ent, name, cmd, &vic, &a ); + if( id < 0 ) + return qfalse; + + if( !Q_stricmp( cmd, "l1" ) ) + { + if( a && a->level != 0 ) + { + ADMP( va( "^5%s: ^5sorry, but your intended victim is not level 0\n", cmd ) ); + return qfalse; + } + level = 1; + } + else + { + if( !a || a->level != 1 ) + { + ADMP( va( "^5%s: ^5sorry, but your intended victim is not level 1\n", cmd ) ); + return qfalse; + } + level = 0; + } + + trap_SendConsoleCommand( EXEC_APPEND, + va( "setlevel %d %d;", id, level ) ); + + AP( va( "print \"^5%s: ^5name protection for %s^5 %s by %s\n\"", + cmd, + ( vic ) ? vic->client->pers.netname : a->name, + ( level ) ? "enabled" : "removed", + admin_name( ent ) ) ); + return qtrue; +} + +/* + 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( "^5setlevel: ^5no 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( "^5setlevel: ^5%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( "^5setlevel:^5 no match. use listplayers or listadmins to " + "find an appropriate number to use instead of name.\n" ); + return qfalse; + } + if( matches > 1 ) + { + ADMP( "^5setlevel:^5 more than one match. Use the admin number " + "instead:\n" ); + admin_listadmins( ent, 0, name ); + return qfalse; + } + } +*/ + +static void admin_create_ban( gentity_t *ent, + char *netname, + char *guid, + addr_t *ip, + int seconds, + char *reason ) +{ + g_admin_ban_t *b; + g_admin_ban_t *prev = NULL; + qtime_t qt; + int t; + int i; + const char *name; + char disconnect[ MAX_STRING_CHARS ]; + int expired = 0; + int id = 0; + + t = trap_RealTime( &qt ); + + for( b = g_admin_bans; b; b = b->next ) + { + if( b->id > id ) + id = b->id; + + if( G_ADMIN_BAN_EXPIRED( b, t ) && + !G_ADMIN_BAN_STALE( b, t ) ) + expired++; + } + + // free stale bans + b = g_admin_bans; + while( b ) + { + if ( G_ADMIN_BAN_EXPIRED( b, t ) && + ( expired >= MAX_ADMIN_EXPIRED_BANS || G_ADMIN_BAN_STALE( b, t ) ) ) + { + g_admin_ban_t *u = b; + + if( prev ) + { + prev->next = b->next; + b = prev; + } + else + { + g_admin_bans = b->next; + b = g_admin_bans; + } + + if( !G_ADMIN_BAN_STALE( u, t ) ) + expired--; + BG_Free( u ); + } + else + prev = b; + + if( b ) + b = b->next; + } + + for( b = g_admin_bans; b && b->next; b = b->next ); + + if( b ) + b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) ); + else + b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) ); + + b->id = id + 1; + 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 ), "%04i-%02i-%02i %02i:%02i:%02i", + qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, + qt.tm_hour, qt.tm_min, qt.tm_sec ); + + name = admin_name( ent ); + + Q_strncpyz( b->banner, name, sizeof( b->banner ) ); + if( !seconds ) + b->expires = 0; + else + b->expires = t + seconds; + if( !*reason ) + Q_strncpyz( b->reason, "^5banned 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( "^5disconnect \"%s\"", disconnect ) ); + + trap_DropClient( i, va( "^5has been kicked by ^7%s^5 - reason: ^7%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( "^5kick: ^5usage: 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( "^5kick: ^5%s", err ) ); + return qfalse; + } + vic = &g_entities[ pid ]; + if( !admin_higher( ent, vic ) ) + { + ADMP( "^5kick: ^5sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + if( vic->client->pers.localClient ) + { + ADMP( "^5kick: ^5disconnecting 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( "^5ban: usage: 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( "^5ban: ^5you 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( "^5ban: ^5you 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( "^5ban: ^5invalid 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( "^5ban: ^5no player found by that IP address\n" ); + return qfalse; + } + } + else if( !( match = G_NamelogFromString( ent, search ) ) || match->banned ) + { + ADMP( "^5ban: ^5no match\n" ); + return qfalse; + } + + if( ent && !admin_higher_guid( ent->client->pers.guid, match->guid ) ) + { + + ADMP( "^5ban: ^5sorry, 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( "^5ban: ^5disconnecting the host would end the game\n" ); + return qfalse; + } + + G_admin_duration( ( seconds ) ? seconds : -1, + duration, sizeof( duration ) ); + + AP( va( "print \"^5ban:^7 %s^5 has been banned by ^7%s^5 " + "duration: %s, reason: %s\n\"", + match->name[ match->nameOffset ], + admin_name( ent ), + duration, + ( *reason ) ? reason : "^7banned 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( "^5ban: ^5WARNING g_admin not set, not saving ban to a file\n" ); + else + admin_writeconfig(); + + return qtrue; +} + +qboolean G_admin_unban( gentity_t *ent ) +{ + int id; + int time = trap_RealTime( NULL ); + char bs[ 5 ]; + g_admin_ban_t *ban; + + if( trap_Argc() < 2 ) + { + ADMP( "^5unban: ^5usage: unban [ban#]\n" ); + return qfalse; + } + trap_Argv( 1, bs, sizeof( bs ) ); + id = atoi( bs ); + for( ban = g_admin_bans; ban && ban->id != id; ban = ban->next ); + if( !ban ) + { + ADMP( "^5unban: ^5invalid 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( "^5unban: ^5you 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 \"^5unban: ^5ban #%d for %s^5 has been removed by %s\n\"", + id, + ban->name, + admin_name( ent ) ) ); + ban->expires = time; + admin_writeconfig(); + return qtrue; +} + +qboolean G_admin_adjustban( gentity_t *ent ) +{ + int id; + 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 skiparg = 0; + + if( trap_Argc() < 3 ) + { + ADMP( "^5adjustban: ^5usage: adjustban [ban#] [/mask] [duration] [reason]" + "\n" ); + return qfalse; + } + trap_Argv( 1, bs, sizeof( bs ) ); + id = atoi( bs ); + for( ban = g_admin_bans; ban && ban->id != id; ban = ban->next ); + if( !ban ) + { + ADMP( "^5adjustban: ^5invalid 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( "^5adjustban: ^5you 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( "^5adjustban: ^5invalid 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( "^5adjustban: ^5new 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( "^5adjustban: ^5ban 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( "^5adjustban: ^5you 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 \"^5adjustban: ^5ban #%d for %s^5 has been updated by %s^5 " + "%s%s%s%s%s%s\n\"", + id, + ban->name, + admin_name( ent ), + ( 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( "^5putteam: ^5usage: putteam [name] [h|a|s]\n" ); + return qfalse; + } + + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) + { + ADMP( va( "^5putteam: ^5%s", err ) ); + return qfalse; + } + vic = &g_entities[ pid ]; + if( !admin_higher( ent, vic ) ) + { + ADMP( "^5putteam: ^5sorry, but your intended victim has a higher " + " admin level than you\n" ); + return qfalse; + } + teamnum = G_TeamFromString( team ); + if( teamnum == NUM_TEAMS ) + { + ADMP( va( "^5putteam: ^5unknown 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 \"^5putteam: ^5%s^5 put %s^5 on to the %s team\n\"", + admin_name( ent ), + 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( "^5changemap: ^5usage: 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( "^5changemap: ^5invalid 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( "^5changemap: ^5invalid layout name '%s'\n", layout ) ); + return qfalse; + } + } + admin_log( map ); + admin_log( layout ); + + trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) ); + level.restarted = qtrue; + G_MapLog_Result( 'M' ); + AP( va( "print \"^5changemap: ^5map '%s' started by %s^5 %s\n\"", map, + admin_name( ent ), + ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); + return qtrue; +} + +qboolean G_admin_cp( gentity_t *ent ) +{ + char message[ 64 ]; + char arg[ 8 ]; + int team = -1; + int i; + qboolean admin; + + if( trap_Argc( ) < 2 ) + { + ADMP( "^5cpa: ^5usage: cpa (-AHS) [message]\n" ); + return qfalse; + } + trap_Argv( 1, arg, sizeof( arg ) ); + if( arg[ 0 ] == '-' ) + { + switch( arg[ 1 ] ) + { + case 'a': case 'A': + team = TEAM_ALIENS; + break; + case 'h': case 'H': + team = TEAM_HUMANS; + break; + case 's': case 'S': + team = TEAM_NONE; + break; + default: + ADMP( "^5cpa: ^5team not recognized as -a -h or -s\n" ); + return qfalse; + } + if( trap_Argc( ) < 2 ) + { + ADMP( "^5cpa: ^5no message\n" ); + return qfalse; + } + G_DecolorString( ConcatArgs( 2 ), message, sizeof( message ) ); + } + else + G_DecolorString( ConcatArgs( 1 ), message, sizeof( message ) ); + + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected != CON_CONNECTED ) + continue; + + admin = qfalse; + if( team < 0 || level.clients[ i ].pers.teamSelection == team || + ( admin = G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) ) + { + if( !admin ) + trap_SendServerCommand( i, va( "cp \"^%s%s\"", + ( team < 0 ) ? "2" : "5", message ) ); + trap_SendServerCommand( i, va( "print \"%s^5CPA: ^5%s%s^5%s%s%s: %c%s\n\"", + ( admin ) ? "[ADMIN] " : "", + ( team >= 0 ) ? "(" : "", + admin_name( ent ), + ( team >= 0 ) ? ")" : "", + ( admin ) ? " to " : "", + ( admin ) ? BG_TeamName( team ) : "", + INDENT_MARKER, + message ) ); + } + } + + return qtrue; +} + +qboolean G_admin_warn( gentity_t *ent ) +{ + char reason[ 64 ]; + int pid; + char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; + gentity_t *vic; + + if( trap_Argc() < 3 ) + { + ADMP( va( "^5warn: ^5usage: warn [name|slot#] [reason]\n" ) ); + return qfalse; + } + trap_Argv( 1, name, sizeof( name ) ); + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) + { + ADMP( va( "^5warn: ^5%s\n", err ) ); + return qfalse; + } + if( !admin_higher( ent, &g_entities[ pid ] ) ) + { + ADMP( "^5warn: ^5sorry, but your intended victim has a higher admin level than you\n" ); + return qfalse; + } + vic = &g_entities[ pid ]; + + G_DecolorString( ConcatArgs( 2 ), reason, sizeof( reason ) ); + CPx( pid, va( "cp \"^1You have been warned by an administrator:\n^5%s\"", + reason ) ); + AP( va( "print \"^5warn: ^5%s^5 has been warned: '%s' by %s\n\"", + vic->client->pers.netname, + reason, + admin_name( ent ) ) ); + + 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( "^5%s: ^5usage: %s [name|slot#]\n", command, command ) ); + return qfalse; + } + trap_Argv( 1, name, sizeof( name ) ); + if( !( vic = G_NamelogFromString( ent, name ) ) ) + { + ADMP( va( "^5%s: ^5no match\n", command ) ); + return qfalse; + } + if( ent && !admin_higher_admin( ent->client->pers.admin, + G_admin_admin( vic->guid ) ) ) + { + ADMP( va( "^5%s: ^5sorry, but your intended victim has a higher admin" + " level than you\n", command ) ); + return qfalse; + } + if( vic->muted ) + { + if( !Q_stricmp( command, "mute" ) ) + { + ADMP( "^5mute: ^5player is already muted\n" ); + return qfalse; + } + vic->muted = qfalse; + if( vic->slot > -1 ) + CPx( vic->slot, "cp \"^1You have been unmuted\"" ); + AP( va( "print \"^5unmute: ^5%s^5 has been unmuted by %s\n\"", + vic->name[ vic->nameOffset ], + admin_name( ent ) ) ); + } + else + { + if( !Q_stricmp( command, "unmute" ) ) + { + ADMP( "^5unmute: ^5player 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 \"^5mute: ^5%s^5 has been muted by ^5%s\n\"", + vic->name[ vic->nameOffset ], + admin_name( ent ) ) ); + } + 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( "^5%s: ^5usage: %s [name|slot#]\n", command, command ) ); + return qfalse; + } + trap_Argv( 1, name, sizeof( name ) ); + if( !( vic = G_NamelogFromString( ent, name ) ) ) + { + ADMP( va( "^5%s: ^5no match\n", command ) ); + return qfalse; + } + if( ent && !admin_higher_admin( ent->client->pers.admin, + G_admin_admin( vic->guid ) ) ) + { + ADMP( va( "^5%s: ^5sorry, but your intended victim has a higher admin" + " level than you\n", command ) ); + return qfalse; + } + if( vic->denyBuild ) + { + if( !Q_stricmp( command, "denybuild" ) ) + { + ADMP( "^5denybuild: ^5player 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 \"^5allowbuild: ^5building rights for ^5%s^5 restored by %s\n\"", + vic->name[ vic->nameOffset ], + admin_name( ent ) ) ); + } + else + { + if( !Q_stricmp( command, "allowbuild" ) ) + { + ADMP( "^5allowbuild: ^5player 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 \"^5denybuild: ^5building rights for ^5%s^5 revoked by ^5%s\n\"", + vic->name[ vic->nameOffset ], + admin_name( ent ) ) ); + } + 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( "^5listlayouts:^5 %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[ 32 ]; + char muted, denied; + int colorlen; + char namecleaned[ 32 ]; + char name2cleaned[ 32 ]; + 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( "^5id/level/team/setlevel/rank/name/(alias) - [^5%d players connected]\n", + level.numConnectedClients ) ); + ADMBP( va( "-------------------------------------------------------------------\n" ) ); + 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_CYAN; + } + else + { + t = toupper( *( BG_TeamName( p->pers.teamSelection ) ) ); + if( p->pers.teamSelection == TEAM_HUMANS ) + c = COLOR_BLUE; + 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 | %-2i] ^%c%c^5 ^2%c^5 %*s^1%c%c^7 %s^5 %s%s%s \n", + i, + l ? l->level : 0, + c, + t, + + hint ? '*' : ' ', + admin_level_maxname + colorlen, + lname, + muted, + denied, + p->pers.netname, + ( registeredname ) ? "^5(^7" : "", + ( registeredname ) ? registeredname : "", + ( registeredname ) ? S_COLOR_WHITE "^5)" : "" ) ); + + + + } + 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_COLOR_CYAN "------------------------------" + S_COLOR_CYAN "\n id: | %ld" + S_COLOR_CYAN "\n Name: | %-*s" + S_COLOR_CYAN "\n IP: | %s%-15s" + S_COLOR_CYAN "\n Banner: | %s" + S_COLOR_CYAN "\n Reason: | %s" + S_COLOR_CYAN "\n Date: | %-8s" + S_COLOR_CYAN "\n Status: | %s%-*s^7", + + b->id, + MAX_NAME_LENGTH + colorlen1 - 1, + b->name, + ( strchr( b->ip.str, '/' ) ) ? S_COLOR_RED : S_COLOR_WHITE, + b->ip.str, + b->banner, + b->reason, + date, + d_color, + MAX_DURATION_LENGTH - 1, + duration + + + ); +} +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( "^5%-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( "^5%-12s", c->command ) ); + count++; + // show 6 commands per line + if( count % 6 == 0 ) + ADMBP( "\n" ); + } + if( count % 6 ) + ADMBP( "\n" ); + ADMBP( va( "^5adminhelp: ^5%i available commands\n", count ) ); + ADMBP( "run adminhelp [^5command^5] 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( "^5adminhelp: ^5help for '%s':\n", c->command ) ); + ADMBP( va( " ^5Description: ^5%s\n", c->desc ) ); + ADMBP( va( " ^5Syntax: ^5%s\n", c->command ) ); + ADMBP( va( " ^5Flag: ^5'%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( "^5adminhelp: ^5help for '%s':\n", admincmd->keyword ) ); + ADMBP( va( " ^5Description: ^5%s\n", admincmd->function ) ); + ADMBP( va( " ^5Syntax: ^5%s %s\n", admincmd->keyword, + admincmd->syntax ) ); + ADMBP( va( " ^5Flag: ^5'%s'\n", admincmd->flag ) ); + ADMBP_end(); + return qtrue; + } + denied = qtrue; + } + ADMBP( va( "^5adminhelp: ^5%s '%s'\n", + denied ? "you do not have permission to use" : "no help found for", + param ) ); + ADMBP_end( ); + return qfalse; + } +} + +qboolean G_admin_info( gentity_t *ent ) +{ + char buffer[ MAX_STRING_CHARS ]; + char output[ MAX_STRING_CHARS ]; + char subject[ 16 ]; + char filename[ MAX_OSPATH ]; + char *ptr; + fileHandle_t F; + int len; + int pos = 0; + int end = sizeof( output ) - 1; + int i; + + if( trap_Argc( ) > 1 ) + trap_Argv( 1, subject, sizeof( subject ) ); + else + Q_strncpyz( subject, "index", sizeof( subject ) ); + Com_sprintf( filename, sizeof( filename ), "info/info-%s.txt", subject ); + + len = trap_FS_FOpenFile( filename, &F, FS_READ ); + if( len < 0 ) + { + if( !strcmp( subject, "index" ) ) + G_Printf( "Unable to find the top page info file %s\n", filename ); + ADMP( va( "^5info: ^5info subject %s not found\n", subject ) ); + return qfalse; + } + trap_FS_Read( buffer, sizeof( buffer ), F ); + trap_FS_FCloseFile( F ); + + if( len >= sizeof( buffer ) ) + { + len = sizeof( buffer ) - 1; + G_Printf( "G_admin_info: %s exceeds max info file size (%d)\n", + filename, len ); + } + buffer[ len ] = '\0'; + + ptr = buffer; + while( *ptr ) + { + // parse cvars, format: ${cvar} + if( *ptr == '$' && *(ptr + 1) == '{' ) + { + char *cvar; + + ptr += 2; + cvar = ptr; + while( *ptr && *ptr != '}' ) + ptr++; + if( *ptr == '}' ) + { + char value[ MAX_CVAR_VALUE_STRING ]; + + *ptr = '\0'; + ptr++; + trap_Cvar_VariableStringBuffer( cvar, value, sizeof( value ) ); + for( i = 0; value[ i ] && pos < end; i++, pos++ ) + output[ pos ] = value [ i ]; + } + continue; + } + + if( ( isprint(*ptr) || *ptr == '\n' ) && pos < end ) + output[ pos++ ] = *ptr; + ptr++; + } + + if( ( pos == 0 || output[ pos - 1 ] != '\n' ) && pos < end ) + output[ pos++ ] = '\n'; + output[ pos ] = '\0'; + ADMP( output ); + return qtrue; +} + +qboolean G_admin_admintest( gentity_t *ent ) +{ + g_admin_level_t *l; + + if( !ent ) + { + ADMP( "^5admintest: ^5you are on the console.\n" ); + return qtrue; + } + + l = G_admin_level( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ); + + AP( va( "print \"^5admintest: ^5%s^5 is a level %d admin %s%s^5%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( "^5allready: ^5this 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 \"^5allready:^5 %s^5 says everyone is READY now\n\"", + admin_name( ent ) ) ); + 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( "^5%s: ^5invalid team '%s'\n", command, teamName ) ); + return qfalse; + } + msg = va( "print \"^5%s: ^5%s^5 decided that everyone voted %s\n\"", + command, admin_name( ent ), + cancel ? "No" : "Yes" ); + if( !level.voteTime[ team ] ) + { + ADMP( va( "^5%s: ^5no 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 \"^5spec999: ^5%s^5 moved %s^5 to spectators\n\"", + admin_name( ent ), + 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( "^5rename: ^5usage: 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( "^5rename: ^5%s", err ) ); + return qfalse; + } + victim = &g_entities[ pid ]; + if( !admin_higher( ent, victim ) ) + { + ADMP( "^5rename: ^5sorry, 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( "^5rename: ^5%s\n", err ) ); + return qfalse; + } + if( victim->client->pers.connected != CON_CONNECTED ) + { + ADMP( "^5rename: ^5sorry, 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 \"^5rename: ^5%s^5 has been renamed to %s^5 by %s\n\"", + victim->client->pers.netname, + newname, + admin_name( ent ) ) ); + 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( "^5restart: ^5layout '%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" ); + G_MapLog_Result( 'R' ); + + AP( va( "print \"^5restart: ^5map restarted by %s %s %s\n\"", + admin_name( ent ), + ( layout[ 0 ] ) ? va( "^5(forcing layout '%s^5')", layout ) : "", + ( teampref[ 0 ] ) ? va( "^5(with teams option: '%s^5')", teampref ) : "" ) ); + return qtrue; +} + +qboolean G_admin_nextmap( gentity_t *ent ) +{ + AP( va( "print \"^5nextmap: ^5%s^5 decided to load the next map\n\"", + admin_name( ent ) ) ); + level.lastWin = TEAM_NONE; + trap_SetConfigstring( CS_WINNER, "Evacuation" ); + LogExit( va( "nextmap was run by %s", + admin_name( ent ) ) ); + G_MapLog_Result( 'N' ); + 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( "^5%s: ^5usage: %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( "^5%s: ^5invalid team: '%s'\n", command, teamName ) ); + return qfalse; + } + + if( fail ) + { + ADMP( va( "^5%s: ^5the %s team is %s locked\n", + command, BG_TeamName( team ), lock ? "already" : "not currently" ) ); + + return qfalse; + } + + admin_log( BG_TeamName( team ) ); + AP( va( "print \"^5%s: ^5the %s team has been %slocked by %s\n\"", + command, BG_TeamName( team ), lock ? "" : "un", + admin_name( ent ) ) ); + + 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( "^5builder: ^5console 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( "^5builder: ^5structure 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( "^5builder: ^5%s built by %s^5%s\n", + BG_Buildable( log->modelindex )->humanName, + log->actor ? + log->actor->name[ log->actor->nameOffset ] : + "<world>", + logid ) ); + break; + } + } + if( i == level.numBuildLogs ) + ADMP( va( "^5builder: ^5%s not in build log, possibly a layout item\n", + BG_Buildable( traceEnt->s.modelindex )->humanName ) ); + } + else + ADMP( "^5builder: ^5no structure found under crosshair\n" ); + + return qtrue; +} + +qboolean G_admin_pause( gentity_t *ent ) +{ + if( !level.pausedTime ) + { + AP( va( "print \"^5pause: ^5%s^5 paused the game.\n\"", + admin_name( ent ) ) ); + 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( "^5pause: ^5Unpausing so soon assumed accidental and ignored.\n" ); + return qfalse; + } + + AP( va( "print \"^5pause: ^5%s^5 unpaused the game (Paused for %d sec) \n\"", + admin_name( ent ), + (int) ( (float) level.pausedTime / 1000.0f ) ) ); + trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); + + level.pausedTime = 0; + } + + return qtrue; +} + +static char *fates[] = +{ + "^5built^5", + "^5deconstructed^5", + "^5replaced^5", + "^5destroyed^5", + "^1TEAMKILLED^5", + "^5unpowered^5", + "removed" +}; + +qboolean G_admin_slap( gentity_t *ent ) +{ + int pids[ MAX_CLIENTS ]; + int pid; + char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; + int minargc; + gentity_t *vic; + vec3_t dir; + + minargc = 3; + if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) + minargc = 2; + + if( trap_Argc() < minargc ) + { + ADMP( "[^5name|slot^5]" ); + return qfalse; + } + + trap_Argv( 1, name, sizeof( name ) ); + reason = ConcatArgs( 2 ); + if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 ) + { + ADMP( va( "^5slap: ^5%s", err ) ); + return qfalse; + } + vic = &g_entities[ pid ]; + if( !admin_higher( ent, vic ) ) + { + ADMP( "^5slap: ^5sorry, but your intended victim has a higher admin" + " level than you\n" ); + return qfalse; + } + + if( vic->client->pers.teamSelection == TEAM_NONE || + vic->client->pers.classSelection == PCL_NONE ) + { + ADMP( "^5slap: ^5can't slap spectators\n" ); + return qfalse; + } + + // knockback in a random direction + dir[0] = crandom(); + dir[1] = crandom(); + dir[2] = random(); + G_Knockback( vic, dir, 100 ); + + trap_SendServerCommand( vic-g_entities, + va( "cp \"[yesmad] %s^1 is not amused! [yesmad]\n\"", + admin_name( ent )) + ); + AP( va( "print \"^5Slap: ^5%s^5 has been slapped by %s\n\"", + vic->client->pers.netname, + admin_name( ent ) ) ); + + vic->health -= 25; + vic->client->ps.stats[ STAT_HEALTH ] = vic->health; + vic->lastDamageTime = level.time; + if( vic->health <= 1 ) + { + vic->flags |= FL_NO_KNOCKBACK; + vic->enemy = &g_entities[ pids[ 0 ] ]; + vic->die( vic, ent, ent, 25, MOD_SLAP ); + } + else if( vic->pain ) + { + vic->pain( vic, &g_entities[ pids[ 0 ] ], 2 ); + } + + return qtrue; +} + + +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( "^5buildlog: ^5log 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( "^5buildlog: ^5invalid 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( "^5buildlog: ^5invalid build ID\n" ); + return qfalse; + } + + if( ent && ent->client->pers.teamSelection != TEAM_NONE ) + trap_SendServerCommand( -1, + va( "print \"^5buildlog: ^5%s^5 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^5%-3d %s ^5%s^5 %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 )->humanName, + fates[ log->fate ], + log->actor ? " by " : "", + log->actor ? + log->actor->name[ log->actor->nameOffset ] : + "" ) ); + } + ADMBP( va( "^5buildlog: ^5showing %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( "^5revert: ^5usage: 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( "^5revert: ^5invalid 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( "^5revert: ^5you can only revert direct player actions, " + "indicated by ^2* ^5in buildlog\n" ); + return qfalse; + } + + G_admin_duration( ( level.time - log->time ) / 1000, time, + sizeof( time ) ); + admin_log( arg ); + AP( va( "print \"^5revert: ^5%s^5 reverted %d %s over the past %s\n\"", + admin_name( ent ), + level.buildId - id, + ( level.buildId - id ) > 1 ? "changes" : "change", + time ) ); + G_BuildLogRevert( id ); + return qtrue; +} + +/* +================ + G_admin_flag_update + + Update a flag string + Resulting string is sorted +================ +*/ + +static int G_admin_flag_sort( const void *pa, const void *pb ) +{ + const char *a = pa; + const char *b = pb; + + if( *a == '-' || *a == '+' ) + a++; + if( *b == '-' || *b == '+' ) + b++; + + return strcmp( a, b ); +} + +const char *G_admin_flag_update( char *newflags, char *oldflags, int size, + const char *flag, qboolean add, qboolean permission ) +{ + char *token, *token_p; + char *key; + char flags[ MAX_ADMIN_FLAG_KEYS ][ MAX_ADMIN_FLAG_LEN ]; + qboolean found = qfalse; + int count = 0; + int i; + + if( !flag[ 0 ] ) + return "invalid admin flag"; + + token_p = oldflags; + while( *( token = COM_Parse( &token_p ) ) ) + { + key = token; + if( *key == '-' || *key == '+' ) + key++; + + if( !strcmp( key, flag ) ) + { + found = qtrue; + continue; + } + + if( count < MAX_ADMIN_FLAG_KEYS ) + { + Q_strncpyz( flags[ count ], token, sizeof( flags[ count ] ) ); + count++; + } + } + + if( add ) + { + if( count >= MAX_ADMIN_FLAG_KEYS ) + return "too many admin flags, flag not set"; + + Com_sprintf( flags[ count ], sizeof( flags[ count ] ), + "%c%s", + ( permission ) ? '+' : '-', + flag ); + count++; + } + else if( !found ) + { + return "flag was not present"; + } + + qsort( flags, count, sizeof( flags[ 0 ] ), G_admin_flag_sort ); + + // build new string + newflags[ 0 ] = '\0'; + for( i = 0; i < count; i++ ) + { + Q_strcat( newflags, size, + va( "%s%s", ( i ) ? " " : "", flags[ i ] ) ); + } + + return NULL; +} + +/* +================ + G_admin_flaglist + + List flags known to game +================ +*/ +typedef struct +{ + char *flag; + char *description; +} +g_admin_flag_t; + +static g_admin_flag_t g_admin_flags[] = { + { ADMF_ACTIVITY, "inactivity rules do not apply" }, + { ADMF_ADMINCHAT, "can see and use admin chat" }, + { ADMF_ALLFLAGS, "can use any command" }, + { ADMF_CAN_PERM_BAN, "can permanently ban players" }, + { ADMF_FORCETEAMCHANGE, "team balance rules do not apply" }, + { ADMF_INCOGNITO, "does not show as admin in !listplayers" }, + { ADMF_IMMUNITY, "cannot be vote kicked, vote muted, or banned" }, + { ADMF_IMMUTABLE, "admin commands cannot be used on them" }, + { ADMF_NOCENSORFLOOD, "can not be censored or flood protected" }, + { ADMF_NO_VOTE_LIMIT, "vote limitations do not apply" }, + { ADMF_SPEC_ALLCHAT, "can see team chat as spectator" }, + { ADMF_CVA, "can callvote" }, + { ADMF_UNACCOUNTABLE, "does not need to specify reason for kick/ban" } +}; +static int adminNumFlags = + sizeof( g_admin_flags ) / sizeof( g_admin_flags[ 0 ] ); + +qboolean G_admin_flaglist( gentity_t *ent ) +{ + qboolean shown[ MAX_ADMIN_FLAG_KEYS ]; + int count = 0; + int i, j; + + ADMBP_begin(); + + ADMBP( "^5Ability flags:\n" ); + for( i = 0; i < adminNumFlags; i++ ) + { + ADMBP( va( " ^5%-20s ^5%s\n", + g_admin_flags[ i ].flag, + g_admin_flags[ i ].description ) ); + } + + ADMBP( "^5Command flags:\n" ); + memset( shown, 0, sizeof( shown ) ); + for( i = 0; i < adminNumCmds; i++ ) + { + if( i < MAX_ADMIN_FLAG_KEYS && shown[ i ] ) + continue; + ADMBP( va( " ^5%-20s^5", g_admin_cmds[ i ].flag ) ); + for( j = i; j < adminNumCmds; j++ ) + { + if( !strcmp( g_admin_cmds[ j ].flag, g_admin_cmds[ i ].flag ) ) + { + ADMBP( va( " %s", g_admin_cmds[ j ].keyword ) ); + if( j < MAX_ADMIN_FLAG_KEYS ) + shown[ j ] = qtrue; + } + } + ADMBP( "\n" ); + count++; + } + + ADMBP( va( "^5flaglist: ^5listed %d ability and %d command flags\n", + adminNumFlags, count ) ); + ADMBP_end(); + + return qtrue; +} + +/* +================ + G_admin_flag + + Set flag permissions for a player or admin level +================ +*/ +qboolean G_admin_flag( gentity_t *ent ) +{ + g_admin_admin_t *admin = NULL; + g_admin_level_t *level = NULL; + gentity_t *vic = NULL; + char command[ MAX_ADMIN_CMD_LEN ]; + char name[ MAX_NAME_LENGTH ]; + char adminname[ MAX_NAME_LENGTH ]; + char flagbuf[ MAX_ADMIN_FLAG_LEN ]; + char *flag; + qboolean add = qtrue; + qboolean perm = qtrue; + const char *action = "allowed"; + const char *result; + char *flagPtr; + int flagSize; + int id; + + trap_Argv( 0, command, sizeof( command ) ); + + if( trap_Argc() < 2 ) + { + ADMP( va( "^5%s: ^5usage: %s [^5name|slot#|admin#|*level#^5] (^5+^5|^5-^5)[^5flag^5]\n", + command, command ) ); + return qfalse; + } + + trap_Argv( 1, name, sizeof( name ) ); + if( name[ 0 ] == '*' ) + { + if( ent ) + { + ADMP( va( "^5%s: only console can change admin level flags\n", + command ) ); + return qfalse; + } + + id = atoi( name + 1 ); + level = G_admin_level( id ); + if( !level ) + { + ADMP( va( "^5%s: admin level %d does not exist\n", + command, id ) ); + return qfalse; + } + + Com_sprintf( adminname, sizeof( adminname ), + "admin level %d", level->level ); + } + else + { + if( admin_find_admin( ent, name, command, &vic, &admin ) < 0 ) + return qfalse; + + if( !admin || admin->level == 0 ) + { + ADMP( va( "^5%s:^5 your intended victim is not an admin\n", + command ) ); + return qfalse; + } + + if ( ent && + !admin_higher_admin( ent->client->pers.admin, admin ) ) + { + ADMP( va( "^5%s:^5 your intended victim has a higher admin level than you\n", + command ) ); + return qfalse; + } + + Q_strncpyz( adminname, admin->name, sizeof( adminname )); + } + + if( trap_Argc() < 3 ) + { + if( !level ) + { + level = G_admin_level( admin->level ); + ADMP( va( "^5%s:^5 flags for %s^5 are '^5%s^5'\n", + command, + admin->name, admin->flags ) ); + } + if( level ) + { + ADMP( va( "^5%s:^5 admin level %d flags are '%s'\n", + command, + level->level, level->flags ) ); + } + return qtrue; + } + + trap_Argv( 2, flagbuf, sizeof( flagbuf ) ); + flag = flagbuf; + if( flag[ 0 ] == '-' || flag[ 0 ] == '+' ) + { + perm = ( flag[ 0 ] == '+' ); + flag++; + if( !perm ) + action = "denied"; + } + + if( !Q_stricmp( command, "unflag" ) ) + { + add = qfalse; + action = "cleared"; + } + + if( ent && ent->client->pers.admin == admin ) + { + ADMP( va( "^5%s:^5 you may not change your own flags (use rcon)\n", + command ) ); + return qfalse; + } + + if( !G_admin_permission( ent, flag ) ) + { + ADMP( va( "^5%s:^5 you may only change flags that you also have\n", + command ) ); + return qfalse; + } + + if( level ) + { + flagPtr = level->flags; + flagSize = sizeof( level->flags ); + } + else + { + flagPtr = admin->flags; + flagSize = sizeof( admin->flags ); + } + + result = G_admin_flag_update( flagPtr, flagPtr, flagSize, + flag, add, perm ); + if( result ) + { + ADMP( va( "^5%s: ^5an error occured %s flag '^5%s^5' for %s^5, %s\n", + command, ( add ) ? "setting" : "clearing", + flag, adminname, result ) ); + return qfalse; + } + + if( !G_admin_permission( ent, ADMF_ADMINCHAT ) ) + { + ADMP( va( "^5%s: ^5flag '%s' %s for %s\n", + command, + flag, action, adminname ) ); + } + G_AdminMessage( ent, va( "admin flag '%s' %s for %s", + flag, action, + adminname ) ); + admin_writeconfig( ); + if( vic ) + G_admin_authlog( vic ); + 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( ); +} |