jkxr/Projects/Android/jni/OpenJK/codemp/server/sv_ccmds.cpp

2016 lines
49 KiB
C++

/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2005 - 2015, ioquake3 contributors
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#include "server.h"
#include "qcommon/stringed_ingame.h"
#include "server/sv_gameapi.h"
#include "qcommon/game_version.h"
/*
===============================================================================
OPERATOR CONSOLE ONLY COMMANDS
These commands can only be entered from stdin or by a remote operator datagram
===============================================================================
*/
const char *SV_GetStringEdString(char *refSection, char *refName)
{
//Well, it would've been lovely doing it the above way, but it would mean mixing
//languages for the client depending on what the server is. So we'll mark this as
//a stringed reference with @@@ and send the refname to the client, and when it goes
//to print it will get scanned for the stringed reference indication and dealt with
//properly.
static char text[1024]={0};
Com_sprintf(text, sizeof(text), "@@@%s", refName);
return text;
}
/*
==================
SV_GetPlayerByHandle
Returns the player with player id or name from Cmd_Argv(1)
==================
*/
static client_t *SV_GetPlayerByHandle( void ) {
client_t *cl;
int i;
char *s;
char cleanName[64];
// make sure server is running
if ( !com_sv_running->integer ) {
return NULL;
}
if ( Cmd_Argc() < 2 ) {
Com_Printf( "No player specified.\n" );
return NULL;
}
s = Cmd_Argv(1);
// Check whether this is a numeric player handle
for(i = 0; s[i] >= '0' && s[i] <= '9'; i++);
if(!s[i])
{
int plid = atoi(s);
// Check for numeric playerid match
if(plid >= 0 && plid < sv_maxclients->integer)
{
cl = &svs.clients[plid];
if(cl->state)
return cl;
}
}
// check for a name match
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
if ( !Q_stricmp( cl->name, s ) ) {
return cl;
}
Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
Q_StripColor( cleanName );
//Q_CleanStr( cleanName );
if ( !Q_stricmp( cleanName, s ) ) {
return cl;
}
}
Com_Printf( "Player %s is not on the server\n", s );
return NULL;
}
/*
==================
SV_GetPlayerByNum
Returns the player with idnum from Cmd_Argv(1)
==================
*/
static client_t *SV_GetPlayerByNum( void ) {
client_t *cl;
int i;
int idnum;
char *s;
// make sure server is running
if ( !com_sv_running->integer ) {
return NULL;
}
if ( Cmd_Argc() < 2 ) {
Com_Printf( "No player specified.\n" );
return NULL;
}
s = Cmd_Argv(1);
for (i = 0; s[i]; i++) {
if (s[i] < '0' || s[i] > '9') {
Com_Printf( "Bad slot number: %s\n", s);
return NULL;
}
}
idnum = atoi( s );
if ( idnum < 0 || idnum >= sv_maxclients->integer ) {
Com_Printf( "Bad client slot: %i\n", idnum );
return NULL;
}
cl = &svs.clients[idnum];
if ( !cl->state ) {
Com_Printf( "Client %i is not active\n", idnum );
return NULL;
}
return cl;
}
//=========================================================
/*
==================
SV_Map_f
Restart the server on a different map
==================
*/
static void SV_Map_f( void ) {
char *cmd = NULL, *map = NULL;
qboolean killBots=qfalse, cheat=qfalse;
char expanded[MAX_QPATH] = {0}, mapname[MAX_QPATH] = {0};
map = Cmd_Argv(1);
if ( !map )
return;
// make sure the level exists before trying to change, so that
// a typo at the server console won't end the game
if (strchr (map, '\\') ) {
Com_Printf ("Can't have mapnames with a \\\n");
return;
}
Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map);
if ( FS_ReadFile (expanded, NULL) == -1 ) {
Com_Printf ("Can't find map %s\n", expanded);
return;
}
// force latched values to get set
Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH );
cmd = Cmd_Argv(0);
if ( !Q_stricmpn( cmd, "devmap", 6 ) ) {
cheat = qtrue;
killBots = qtrue;
} else {
cheat = qfalse;
killBots = qfalse;
}
// save the map name here cause on a map restart we reload the jampconfig.cfg
// and thus nuke the arguments of the map command
Q_strncpyz(mapname, map, sizeof(mapname));
ForceReload_e eForceReload = eForceReload_NOTHING; // default for normal load
// if ( !Q_stricmp( cmd, "devmapbsp") ) { // not relevant in MP codebase
// eForceReload = eForceReload_BSP;
// }
// else
if ( !Q_stricmp( cmd, "devmapmdl") ) {
eForceReload = eForceReload_MODELS;
}
else
if ( !Q_stricmp( cmd, "devmapall") ) {
eForceReload = eForceReload_ALL;
}
// start up the map
SV_SpawnServer( mapname, killBots, eForceReload );
// set the cheat value
// if the level was started with "map <levelname>", then
// cheats will not be allowed. If started with "devmap <levelname>"
// then cheats will be allowed
Cvar_Set( "sv_cheats", cheat ? "1" : "0" );
}
/*
================
SV_MapRestart_f
Completely restarts a level, but doesn't send a new gamestate to the clients.
This allows fair starts with variable load times.
================
*/
static void SV_MapRestart_f( void ) {
int i;
client_t *client;
char *denied;
qboolean isBot;
int delay;
// make sure we aren't restarting twice in the same frame
if ( com_frameTime == sv.serverId ) {
return;
}
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( sv.restartTime ) {
return;
}
if (Cmd_Argc() > 1 ) {
delay = atoi( Cmd_Argv(1) );
}
else {
delay = 5;
}
if( delay ) {
sv.restartTime = sv.time + delay * 1000;
SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) );
return;
}
// check for changes in variables that can't just be restarted
// check for maxclients change
if ( sv_maxclients->modified || sv_gametype->modified ) {
char mapname[MAX_QPATH];
Com_Printf( "variable change -- restarting.\n" );
// restart the map the slow way
Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) );
SV_SpawnServer( mapname, qfalse, eForceReload_NOTHING );
return;
}
SV_StopAutoRecordDemos();
// toggle the server bit so clients can detect that a
// map_restart has happened
svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
// generate a new serverid
// TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart
sv.serverId = com_frameTime;
Cvar_Set( "sv_serverid", va("%i", sv.serverId ) );
time( &sv.realMapTimeStarted );
sv.demosPruned = qfalse;
// if a map_restart occurs while a client is changing maps, we need
// to give them the correct time so that when they finish loading
// they don't violate the backwards time check in cl_cgame.c
for (i=0 ; i<sv_maxclients->integer ; i++) {
if (svs.clients[i].state == CS_PRIMED) {
svs.clients[i].oldServerTime = sv.restartTime;
}
}
// reset all the vm data in place without changing memory allocation
// note that we do NOT set sv.state = SS_LOADING, so configstrings that
// had been changed from their default values will generate broadcast updates
sv.state = SS_LOADING;
sv.restarting = qtrue;
SV_RestartGame();
// run a few frames to allow everything to settle
for ( i = 0 ;i < 3 ; i++ ) {
GVM_RunFrame( sv.time );
sv.time += 100;
svs.time += 100;
}
sv.state = SS_GAME;
sv.restarting = qfalse;
// connect and begin all the clients
for (i=0 ; i<sv_maxclients->integer ; i++) {
client = &svs.clients[i];
// send the new gamestate to all connected clients
if ( client->state < CS_CONNECTED) {
continue;
}
if ( client->netchan.remoteAddress.type == NA_BOT ) {
isBot = qtrue;
} else {
isBot = qfalse;
}
// add the map_restart command
SV_AddServerCommand( client, "map_restart\n" );
// connect the client again, without the firstTime flag
denied = GVM_ClientConnect( i, qfalse, isBot );
if ( denied ) {
// this generally shouldn't happen, because the client
// was connected before the level change
SV_DropClient( client, denied );
Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i );
continue;
}
if(client->state == CS_ACTIVE)
SV_ClientEnterWorld(client, &client->lastUsercmd);
else
{
// If we don't reset client->lastUsercmd and are restarting during map load,
// the client will hang because we'll use the last Usercmd from the previous map,
// which is wrong obviously.
SV_ClientEnterWorld(client, NULL);
}
}
// run another frame to allow things to look at all the players
GVM_RunFrame( sv.time );
sv.time += 100;
svs.time += 100;
SV_BeginAutoRecordDemos();
}
//===============================================================
/*
==================
SV_KickBlankPlayers
==================
*/
static void SV_KickBlankPlayers( void ) {
client_t *cl;
int i;
char cleanName[64];
// make sure server is running
if ( !com_sv_running->integer ) {
return;
}
// check for a name match
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
continue;
}
if ( !Q_stricmp( cl->name, "" ) ) {
SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
continue;
}
Q_strncpyz( cleanName, cl->name, sizeof(cleanName) );
Q_StripColor( cleanName );
//Q_CleanStr( cleanName );
if ( !Q_stricmp( cleanName, "" ) ) {
SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
}
/*
==================
SV_Kick_f
Kick a user off of the server
==================
*/
static void SV_Kick_f( void ) {
client_t *cl;
int i;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
Com_Printf ("Usage: kick <player name>\nkick all = kick everyone\nkick allbots = kick all bots\n");
return;
}
if (!Q_stricmp(Cmd_Argv(1), "Padawan"))
{ //if you try to kick the default name, also try to kick ""
SV_KickBlankPlayers();
}
cl = SV_GetPlayerByHandle();
if ( !cl ) {
if ( !Q_stricmp(Cmd_Argv(1), "all") ) {
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
continue;
}
SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
else if ( !Q_stricmp(Cmd_Argv(1), "allbots") ) {
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type != NA_BOT ) {
continue;
}
SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
return;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
Com_Printf("Cannot kick host player\n");
return;
}
SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
/*
==================
SV_KickBots_f
Kick all bots off of the server
==================
*/
static void SV_KickBots_f( void ) {
client_t *cl;
int i;
// make sure server is running
if( !com_sv_running->integer ) {
Com_Printf("Server is not running.\n");
return;
}
for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) {
if( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type != NA_BOT ) {
continue;
}
SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
/*
==================
SV_KickAll_f
Kick all users off of the server
==================
*/
static void SV_KickAll_f( void ) {
client_t *cl;
int i;
// make sure server is running
if( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) {
if( !cl->state ) {
continue;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
continue;
}
SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
}
/*
==================
SV_KickNum_f
Kick a user off of the server
==================
*/
static void SV_KickNum_f( void ) {
client_t *cl;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
Com_Printf ("Usage: %s <client number>\n", Cmd_Argv(0));
return;
}
cl = SV_GetPlayerByNum();
if ( !cl ) {
return;
}
if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) {
Com_Printf("Cannot kick host player\n");
return;
}
SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" );
cl->lastPacketTime = svs.time; // in case there is a funny zombie
}
/*
==================
SV_RehashBans_f
Load saved bans from file.
==================
*/
static void SV_RehashBans_f( void )
{
int index, filelen;
fileHandle_t readfrom;
char *textbuf, *curpos, *maskpos, *newlinepos, *endpos;
char filepath[MAX_QPATH];
// make sure server is running
if ( !com_sv_running->integer ) {
return;
}
serverBansCount = 0;
if ( !sv_banFile->string || !*sv_banFile->string )
return;
Com_sprintf( filepath, sizeof( filepath ), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string );
if ( (filelen = FS_SV_FOpenFileRead( filepath, &readfrom )) >= 0 )
{
if ( filelen < 2 )
{
// Don't bother if file is too short.
FS_FCloseFile( readfrom );
return;
}
curpos = textbuf = (char *)Z_Malloc( filelen, TAG_TEMP_WORKSPACE );
filelen = FS_Read( textbuf, filelen, readfrom );
FS_FCloseFile( readfrom );
endpos = textbuf + filelen;
for ( index = 0; index < SERVER_MAXBANS && curpos + 2 < endpos; index++ )
{
// find the end of the address string
for ( maskpos = curpos + 2; maskpos < endpos && *maskpos != ' '; maskpos++ );
if ( maskpos + 1 >= endpos )
break;
*maskpos = '\0';
maskpos++;
// find the end of the subnet specifier
for ( newlinepos = maskpos; newlinepos < endpos && *newlinepos != '\n'; newlinepos++ );
if ( newlinepos >= endpos )
break;
*newlinepos = '\0';
if ( NET_StringToAdr( curpos + 2, &serverBans[index].ip ) )
{
serverBans[index].isexception = (qboolean)(curpos[0] != '0');
serverBans[index].subnet = atoi( maskpos );
if ( serverBans[index].ip.type == NA_IP &&
(serverBans[index].subnet < 1 || serverBans[index].subnet > 32) )
{
serverBans[index].subnet = 32;
}
}
curpos = newlinepos + 1;
}
serverBansCount = index;
Z_Free( textbuf );
}
}
/*
==================
SV_WriteBans
Save bans to file.
==================
*/
static void SV_WriteBans( void )
{
int index;
fileHandle_t writeto;
char filepath[MAX_QPATH];
if ( !sv_banFile->string || !*sv_banFile->string )
return;
Com_sprintf( filepath, sizeof( filepath ), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string );
if ( (writeto = FS_SV_FOpenFileWrite( filepath )) )
{
char writebuf[128];
serverBan_t *curban;
for ( index = 0; index < serverBansCount; index++ )
{
curban = &serverBans[index];
Com_sprintf( writebuf, sizeof( writebuf ), "%d %s %d\n",
curban->isexception, NET_AdrToString( curban->ip ), curban->subnet );
FS_Write( writebuf, strlen( writebuf ), writeto );
}
FS_FCloseFile( writeto );
}
}
/*
==================
SV_DelBanEntryFromList
Remove a ban or an exception from the list.
==================
*/
static qboolean SV_DelBanEntryFromList( int index ) {
if ( index == serverBansCount - 1 )
serverBansCount--;
else if ( index < (int)ARRAY_LEN( serverBans ) - 1 )
{
memmove( serverBans + index, serverBans + index + 1, (serverBansCount - index - 1) * sizeof( *serverBans ) );
serverBansCount--;
}
else
return qtrue;
return qfalse;
}
/*
==================
SV_ParseCIDRNotation
Parse a CIDR notation type string and return a netadr_t and suffix by reference
==================
*/
static qboolean SV_ParseCIDRNotation( netadr_t *dest, int *mask, char *adrstr )
{
char *suffix;
suffix = strchr( adrstr, '/' );
if ( suffix )
{
*suffix = '\0';
suffix++;
}
if ( !NET_StringToAdr( adrstr, dest ) )
return qtrue;
if ( suffix )
{
*mask = atoi( suffix );
if ( dest->type == NA_IP )
{
if ( *mask < 1 || *mask > 32 )
*mask = 32;
}
else
*mask = 32;
}
//else if ( dest->type == NA_IP )
// *mask = 32;
else
*mask = 32;
return qfalse;
}
/*
==================
SV_AddBanToList
Ban a user from being able to play on this server based on his ip address.
==================
*/
static void SV_AddBanToList( qboolean isexception )
{
char *banstring;
char addy2[NET_ADDRSTRMAXLEN];
netadr_t ip;
int index, argc, mask;
serverBan_t *curban;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
argc = Cmd_Argc();
if ( argc < 2 || argc > 3 )
{
Com_Printf( "Usage: %s (ip[/subnet] | clientnum [subnet])\n", Cmd_Argv( 0 ) );
return;
}
if ( serverBansCount >= (int)ARRAY_LEN( serverBans ) )
{
Com_Printf( "Error: Maximum number of bans/exceptions exceeded.\n" );
return;
}
banstring = Cmd_Argv( 1 );
if ( strchr( banstring, '.' ) /*|| strchr( banstring, ':' )*/ )
{
// This is an ip address, not a client num.
if ( SV_ParseCIDRNotation( &ip, &mask, banstring ) )
{
Com_Printf( "Error: Invalid address %s\n", banstring );
return;
}
}
else
{
client_t *cl;
// client num.
cl = SV_GetPlayerByNum();
if ( !cl )
{
Com_Printf( "Error: Playernum %s does not exist.\n", Cmd_Argv( 1 ) );
return;
}
ip = cl->netchan.remoteAddress;
if ( argc == 3 )
{
mask = atoi( Cmd_Argv( 2 ) );
if ( ip.type == NA_IP )
{
if ( mask < 1 || mask > 32 )
mask = 32;
}
else
mask = 32;
}
else
mask = 32;
}
if ( ip.type != NA_IP )
{
Com_Printf( "Error: Can ban players connected via the internet only.\n" );
return;
}
// first check whether a conflicting ban exists that would supersede the new one.
for ( index = 0; index < serverBansCount; index++ )
{
curban = &serverBans[index];
if ( curban->subnet <= mask )
{
if ( (curban->isexception || !isexception) && NET_CompareBaseAdrMask( curban->ip, ip, curban->subnet ) )
{
Q_strncpyz( addy2, NET_AdrToString( ip ), sizeof( addy2 ) );
Com_Printf( "Error: %s %s/%d supersedes %s %s/%d\n", curban->isexception ? "Exception" : "Ban",
NET_AdrToString( curban->ip ), curban->subnet,
isexception ? "exception" : "ban", addy2, mask );
return;
}
}
if ( curban->subnet >= mask )
{
if ( !curban->isexception && isexception && NET_CompareBaseAdrMask( curban->ip, ip, mask ) )
{
Q_strncpyz( addy2, NET_AdrToString( curban->ip ), sizeof( addy2 ) );
Com_Printf( "Error: %s %s/%d supersedes already existing %s %s/%d\n", isexception ? "Exception" : "Ban",
NET_AdrToString( ip ), mask,
curban->isexception ? "exception" : "ban", addy2, curban->subnet );
return;
}
}
}
// now delete bans that are superseded by the new one
index = 0;
while ( index < serverBansCount )
{
curban = &serverBans[index];
if ( curban->subnet > mask && (!curban->isexception || isexception) && NET_CompareBaseAdrMask( curban->ip, ip, mask ) )
SV_DelBanEntryFromList( index );
else
index++;
}
serverBans[serverBansCount].ip = ip;
serverBans[serverBansCount].subnet = mask;
serverBans[serverBansCount].isexception = isexception;
serverBansCount++;
SV_WriteBans();
Com_Printf( "Added %s: %s/%d\n", isexception ? "ban exception" : "ban",
NET_AdrToString( ip ), mask );
}
/*
==================
SV_DelBanFromList
Remove a ban or an exception from the list.
==================
*/
static void SV_DelBanFromList( qboolean isexception )
{
int index, count = 0, todel, mask;
netadr_t ip;
char *banstring;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 )
{
Com_Printf( "Usage: %s (ip[/subnet] | num)\n", Cmd_Argv( 0 ) );
return;
}
banstring = Cmd_Argv( 1 );
if ( strchr( banstring, '.' ) || strchr( banstring, ':' ) )
{
serverBan_t *curban;
if ( SV_ParseCIDRNotation( &ip, &mask, banstring ) )
{
Com_Printf( "Error: Invalid address %s\n", banstring );
return;
}
index = 0;
while ( index < serverBansCount )
{
curban = &serverBans[index];
if ( curban->isexception == isexception &&
curban->subnet >= mask &&
NET_CompareBaseAdrMask( curban->ip, ip, mask ) )
{
Com_Printf( "Deleting %s %s/%d\n",
isexception ? "exception" : "ban",
NET_AdrToString( curban->ip ), curban->subnet );
SV_DelBanEntryFromList( index );
}
else
index++;
}
}
else
{
todel = atoi( Cmd_Argv( 1 ) );
if ( todel < 1 || todel > serverBansCount )
{
Com_Printf( "Error: Invalid ban number given\n" );
return;
}
for ( index = 0; index < serverBansCount; index++ )
{
if ( serverBans[index].isexception == isexception )
{
count++;
if ( count == todel )
{
Com_Printf( "Deleting %s %s/%d\n",
isexception ? "exception" : "ban",
NET_AdrToString( serverBans[index].ip ), serverBans[index].subnet );
SV_DelBanEntryFromList( index );
break;
}
}
}
}
SV_WriteBans();
}
/*
==================
SV_ListBans_f
List all bans and exceptions on console
==================
*/
static void SV_ListBans_f( void )
{
int index, count;
serverBan_t *ban;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
// List all bans
for ( index = count = 0; index < serverBansCount; index++ )
{
ban = &serverBans[index];
if ( !ban->isexception )
{
count++;
Com_Printf( "Ban #%d: %s/%d\n", count,
NET_AdrToString( ban->ip ), ban->subnet );
}
}
// List all exceptions
for ( index = count = 0; index < serverBansCount; index++ )
{
ban = &serverBans[index];
if ( ban->isexception )
{
count++;
Com_Printf( "Except #%d: %s/%d\n", count,
NET_AdrToString( ban->ip ), ban->subnet );
}
}
}
/*
==================
SV_FlushBans_f
Delete all bans and exceptions.
==================
*/
static void SV_FlushBans_f( void )
{
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
serverBansCount = 0;
// empty the ban file.
SV_WriteBans();
Com_Printf( "All bans and exceptions have been deleted.\n" );
}
static void SV_BanAddr_f( void )
{
SV_AddBanToList( qfalse );
}
static void SV_ExceptAddr_f( void )
{
SV_AddBanToList( qtrue );
}
static void SV_BanDel_f( void )
{
SV_DelBanFromList( qfalse );
}
static void SV_ExceptDel_f( void )
{
SV_DelBanFromList( qtrue );
}
static const char *SV_CalcUptime( void ) {
static char buf[MAX_STRING_CHARS / 4] = { '\0' };
char tmp[64] = { '\0' };
time_t currTime;
time( &currTime );
int secs = difftime( currTime, svs.startTime );
int mins = secs / 60;
int hours = mins / 60;
int days = hours / 24;
secs %= 60;
mins %= 60;
hours %= 24;
//days %= 365;
buf[0] = '\0';
if ( days > 0 ) {
Com_sprintf( tmp, sizeof(tmp), "%i days ", days );
Q_strcat( buf, sizeof(buf), tmp );
}
Com_sprintf( tmp, sizeof(tmp), "%ih%im%is", hours, mins, secs );
Q_strcat( buf, sizeof(buf), tmp );
return buf;
}
/*
================
SV_Status_f
================
*/
static void SV_Status_f( void )
{
int i, humans, bots;
client_t *cl;
playerState_t *ps;
const char *s;
int ping;
char state[32];
qboolean avoidTruncation = qfalse;
// make sure server is running
if ( !com_sv_running->integer )
{
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() > 1 )
{
if (!Q_stricmp("notrunc", Cmd_Argv(1)))
{
avoidTruncation = qtrue;
}
}
humans = bots = 0;
for ( i = 0 ; i < sv_maxclients->integer ; i++ ) {
if ( svs.clients[i].state >= CS_CONNECTED ) {
if ( svs.clients[i].netchan.remoteAddress.type != NA_BOT ) {
humans++;
}
else {
bots++;
}
}
}
#if defined(_WIN32)
#define STATUS_OS "Windows"
#elif defined(__linux__)
#define STATUS_OS "Linux"
#elif defined(MACOS_X)
#define STATUS_OS "OSX"
#else
#define STATUS_OS "Unknown"
#endif
const char *ded_table[] =
{
"listen",
"lan dedicated",
"public dedicated",
};
char hostname[MAX_HOSTNAMELENGTH] = { 0 };
Q_strncpyz( hostname, sv_hostname->string, sizeof(hostname) );
Q_StripColor( hostname );
Com_Printf( "hostname: %s^7\n", hostname );
Com_Printf( "version : %s %i\n", VERSION_STRING_DOTTED, PROTOCOL_VERSION );
Com_Printf( "game : %s\n", FS_GetCurrentGameDir() );
Com_Printf( "udp/ip : %s:%i os(%s) type(%s)\n", Cvar_VariableString( "net_ip" ), Cvar_VariableIntegerValue( "net_port" ), STATUS_OS, ded_table[com_dedicated->integer] );
Com_Printf( "map : %s gametype(%i)\n", sv_mapname->string, sv_gametype->integer );
Com_Printf( "players : %i humans, %i bots (%i max)\n", humans, bots, sv_maxclients->integer - sv_privateClients->integer );
Com_Printf( "uptime : %s\n", SV_CalcUptime() );
Com_Printf ("cl score ping name address rate \n");
Com_Printf ("-- ----- ---- --------------- --------------------------------------- -----\n");
for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++)
{
if ( !cl->state )
continue;
if ( cl->state == CS_CONNECTED )
Q_strncpyz( state, "CON ", sizeof( state ) );
else if ( cl->state == CS_ZOMBIE )
Q_strncpyz( state, "ZMB ", sizeof( state ) );
else {
ping = cl->ping < 9999 ? cl->ping : 9999;
Com_sprintf( state, sizeof(state), "%4i", ping );
}
ps = SV_GameClientNum( i );
s = NET_AdrToString( cl->netchan.remoteAddress );
if (!avoidTruncation)
{
Com_Printf ("%2i %5i %s %-15.15s ^7%39s %5i\n",
i,
ps->persistant[PERS_SCORE],
state,
cl->name,
s,
cl->rate
);
}
else
{
Com_Printf ("%2i %5i %s %s ^7%39s %5i\n",
i,
ps->persistant[PERS_SCORE],
state,
cl->name,
s,
cl->rate
);
}
}
Com_Printf ("\n");
}
char *SV_ExpandNewlines( char *in );
#define SVSAY_PREFIX "Server^7\x19: "
/*
==================
SV_ConSay_f
==================
*/
static void SV_ConSay_f(void) {
char text[MAX_SAY_TEXT] = {0};
if( !com_dedicated->integer ) {
Com_Printf( "Server is not dedicated.\n" );
return;
}
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc () < 2 ) {
return;
}
Cmd_ArgsBuffer( text, sizeof(text) );
Com_Printf ("broadcast: chat \"" SVSAY_PREFIX "%s\\n\"\n", SV_ExpandNewlines((char *)text) );
SV_SendServerCommand(NULL, "chat \"" SVSAY_PREFIX "%s\"\n", text);
}
#define SVTELL_PREFIX "\x19[Server^7\x19]\x19: "
/*
==================
SV_ConTell_f
==================
*/
static void SV_ConTell_f(void) {
char text[MAX_SAY_TEXT] = {0};
client_t *cl;
if( !com_dedicated->integer ) {
Com_Printf( "Server is not dedicated.\n" );
return;
}
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc () < 3 ) {
Com_Printf ("Usage: svtell <client number> <text>\n");
return;
}
cl = SV_GetPlayerByNum();
if ( !cl ) {
return;
}
Cmd_ArgsFromBuffer( 2, text, sizeof(text) );
Com_Printf ("tell: svtell to %s" S_COLOR_WHITE ": %s\n", cl->name, SV_ExpandNewlines((char *)text) );
SV_SendServerCommand(cl, "chat \"" SVTELL_PREFIX S_COLOR_MAGENTA "%s" S_COLOR_WHITE "\"\n", text);
}
const char *forceToggleNamePrints[NUM_FORCE_POWERS] = {
"HEAL",
"JUMP",
"SPEED",
"PUSH",
"PULL",
"MINDTRICK",
"GRIP",
"LIGHTNING",
"DARK RAGE",
"PROTECT",
"ABSORB",
"TEAM HEAL",
"TEAM REPLENISH",
"DRAIN",
"SEEING",
"SABER OFFENSE",
"SABER DEFENSE",
"SABER THROW",
};
static void SV_ForceToggle_f( void ) {
int bits = Cvar_VariableIntegerValue("g_forcePowerDisable");
int i, val;
char *s;
// make sure server is running
if( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
for ( i = 0; i<NUM_FORCE_POWERS; i++ ) {
if ( (bits & (1 << i)) ) Com_Printf( "%2d [X] %s\n", i, forceToggleNamePrints[i] );
else Com_Printf( "%2d [ ] %s\n", i, forceToggleNamePrints[i] );
}
Com_Printf( "Example usage: forcetoggle 3(toggles PUSH)\n" );
return;
}
s = Cmd_Argv(1);
if( Q_isanumber( s ) ) {
val = atoi(s);
if( val >= 0 && val < NUM_FORCE_POWERS) {
bits ^= (1 << val);
Cvar_SetValue("g_forcePowerDisable", bits);
Com_Printf( "%s %s^7\n", forceToggleNamePrints[val], (bits & (1<<val)) ? "^2Enabled" : "^1Disabled" );
}
else {
for ( i = 0; i<NUM_FORCE_POWERS; i++ ) {
if ( (bits & (1 << i)) ) Com_Printf( "%2d [X] %s\n", i, forceToggleNamePrints[i] );
else Com_Printf( "%2d [ ] %s\n", i, forceToggleNamePrints[i] );
}
Com_Printf ("Specified a power that does not exist.\nExample usage: forcetoggle 3\n(toggles PUSH)\n");
}
}
else {
for ( i = 0; i<NUM_FORCE_POWERS; i++ ) {
if ( (bits & (1 << i)) ) Com_Printf( "%2d [X] %s\n", i, forceToggleNamePrints[i] );
else Com_Printf( "%2d [ ] %s\n", i, forceToggleNamePrints[i] );
}
Com_Printf ("Specified a power that does not exist.\nExample usage: forcetoggle 3\n(toggles PUSH)\n");
}
}
const char *weaponToggleNamePrints[WP_NUM_WEAPONS] = {
"NO WEAPON",
"STUN BATON",
"MELEE",
"SABER",
"BRYAR PISTOL",
"BLASTER",
"DISRUPTOR",
"BOWCASTER",
"REPEATER",
"DEMP2",
"FLECHETTE",
"ROCKET LAUNCHER",
"THERMAL",
"TRIP MINE",
"DET PACK",
"CONCUSSION",
"BRYAR OLD",
"EMPLACED GUN",
"TURRET"
};
static void SV_WeaponToggle_f( void ) {
int bits = 0;
int i, val;
char *s;
const char *cvarStr = NULL;
if ( sv_gametype->integer == GT_DUEL || sv_gametype->integer == GT_POWERDUEL ) {
cvarStr = "g_duelWeaponDisable";
bits = Cvar_VariableIntegerValue( "g_duelWeaponDisable" );
}
else {
cvarStr = "g_weaponDisable";
bits = Cvar_VariableIntegerValue( "g_weaponDisable" );
}
// make sure server is running
if( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
for ( i = 0; i<WP_NUM_WEAPONS; i++ ) {
if ( (bits & (1 << i)) ) Com_Printf( "%2d [X] %s\n", i, weaponToggleNamePrints[i] );
else Com_Printf( "%2d [ ] %s\n", i, weaponToggleNamePrints[i] );
}
Com_Printf ("Example usage: weapontoggle 3(toggles SABER)\n");
return;
}
s = Cmd_Argv(1);
if( Q_isanumber( s ) ) {
val = atoi(s);
if( val >= 0 && val < WP_NUM_WEAPONS) {
bits ^= (1 << val);
Cvar_SetValue(cvarStr, bits);
Com_Printf( "%s %s^7\n", weaponToggleNamePrints[val], (bits & (1 << val)) ? "^2Enabled" : "^1Disabled" );
}
else {
for ( i = 0; i<WP_NUM_WEAPONS; i++ ) {
if ( (bits & (1 << i)) ) Com_Printf( "%2d [X] %s\n", i, weaponToggleNamePrints[i] );
else Com_Printf( "%2d [ ] %s\n", i, weaponToggleNamePrints[i] );
}
Com_Printf ("Specified a weapon that does not exist.\nExample usage: weapontoggle 3\n(toggles SABER)\n");
}
}
else {
for ( i = 0; i<WP_NUM_WEAPONS; i++ ) {
if ( (bits & (1 << i)) ) Com_Printf( "%2d [X] %s\n", i, weaponToggleNamePrints[i] );
else Com_Printf( "%2d [ ] %s\n", i, weaponToggleNamePrints[i] );
}
Com_Printf ("Specified a weapon that does not exist.\nExample usage: weapontoggle 3\n(toggles SABER)\n");
}
}
/*
==================
SV_Heartbeat_f
Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer
==================
*/
void SV_Heartbeat_f( void ) {
svs.nextHeartbeatTime = -9999999;
}
/*
===========
SV_Serverinfo_f
Examine the serverinfo string
===========
*/
static void SV_Serverinfo_f( void ) {
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
Com_Printf ("Server info settings:\n");
Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) );
}
/*
===========
SV_Systeminfo_f
Examine or change the serverinfo string
===========
*/
static void SV_Systeminfo_f( void ) {
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
Com_Printf ("System info settings:\n");
Info_Print ( Cvar_InfoString_Big( CVAR_SYSTEMINFO ) );
}
/*
===========
SV_DumpUser_f
Examine all a users info strings FIXME: move to game
===========
*/
static void SV_DumpUser_f( void ) {
client_t *cl;
// make sure server is running
if ( !com_sv_running->integer ) {
Com_Printf( "Server is not running.\n" );
return;
}
if ( Cmd_Argc() != 2 ) {
Com_Printf ("Usage: dumpuser <userid>\n");
return;
}
cl = SV_GetPlayerByHandle();
if ( !cl ) {
return;
}
Com_Printf( "userinfo\n" );
Com_Printf( "--------\n" );
Info_Print( cl->userinfo );
}
/*
=================
SV_KillServer
=================
*/
static void SV_KillServer_f( void ) {
SV_Shutdown( "killserver" );
}
void SV_WriteDemoMessage ( client_t *cl, msg_t *msg, int headerBytes ) {
int len, swlen;
// write the packet sequence
len = cl->netchan.outgoingSequence;
swlen = LittleLong( len );
FS_Write( &swlen, 4, cl->demo.demofile );
// skip the packet sequencing information
len = msg->cursize - headerBytes;
swlen = LittleLong( len );
FS_Write( &swlen, 4, cl->demo.demofile );
FS_Write( msg->data + headerBytes, len, cl->demo.demofile );
}
void SV_StopRecordDemo( client_t *cl ) {
int len;
if ( !cl->demo.demorecording ) {
Com_Printf( "Client %d is not recording a demo.\n", cl - svs.clients );
return;
}
// finish up
len = -1;
FS_Write (&len, 4, cl->demo.demofile);
FS_Write (&len, 4, cl->demo.demofile);
FS_FCloseFile (cl->demo.demofile);
cl->demo.demofile = 0;
cl->demo.demorecording = qfalse;
Com_Printf ("Stopped demo for client %d.\n", cl - svs.clients);
}
// stops all recording demos
void SV_StopAutoRecordDemos() {
if ( svs.clients && sv_autoDemo->integer ) {
for ( client_t *client = svs.clients; client - svs.clients < sv_maxclients->integer; client++ ) {
if ( client->demo.demorecording) {
SV_StopRecordDemo( client );
}
}
}
}
/*
====================
SV_StopRecording_f
stop recording a demo
====================
*/
void SV_StopRecord_f( void ) {
int i;
client_t *cl = NULL;
if ( Cmd_Argc() == 2 ) {
int clIndex = atoi( Cmd_Argv( 1 ) );
if ( clIndex < 0 || clIndex >= sv_maxclients->integer ) {
Com_Printf( "Unknown client number %d.\n", clIndex );
return;
}
cl = &svs.clients[clIndex];
} else {
for (i = 0; i < sv_maxclients->integer; i++) {
if ( svs.clients[i].demo.demorecording ) {
cl = &svs.clients[i];
break;
}
}
if ( cl == NULL ) {
Com_Printf( "No demo being recorded.\n" );
return;
}
}
SV_StopRecordDemo( cl );
}
/*
==================
SV_DemoFilename
==================
*/
void SV_DemoFilename( char *buf, int bufSize ) {
time_t rawtime;
char timeStr[32] = {0}; // should really only reach ~19 chars
time( &rawtime );
strftime( timeStr, sizeof( timeStr ), "%Y-%m-%d_%H-%M-%S", localtime( &rawtime ) ); // or gmtime
Com_sprintf( buf, bufSize, "demo%s", timeStr );
}
// defined in sv_client.cpp
extern void SV_CreateClientGameStateMessage( client_t *client, msg_t* msg );
void SV_RecordDemo( client_t *cl, char *demoName ) {
char name[MAX_OSPATH];
byte bufData[MAX_MSGLEN];
msg_t msg;
int len;
if ( cl->demo.demorecording ) {
Com_Printf( "Already recording.\n" );
return;
}
if ( cl->state != CS_ACTIVE ) {
Com_Printf( "Client is not active.\n" );
return;
}
// open the demo file
Q_strncpyz( cl->demo.demoName, demoName, sizeof( cl->demo.demoName ) );
Com_sprintf( name, sizeof( name ), "demos/%s.dm_%d", cl->demo.demoName, PROTOCOL_VERSION );
Com_Printf( "recording to %s.\n", name );
cl->demo.demofile = FS_FOpenFileWrite( name );
if ( !cl->demo.demofile ) {
Com_Printf ("ERROR: couldn't open.\n");
return;
}
cl->demo.demorecording = qtrue;
// don't start saving messages until a non-delta compressed message is received
cl->demo.demowaiting = qtrue;
cl->demo.isBot = ( cl->netchan.remoteAddress.type == NA_BOT ) ? qtrue : qfalse;
cl->demo.botReliableAcknowledge = cl->reliableSent;
// write out the gamestate message
MSG_Init( &msg, bufData, sizeof( bufData ) );
// NOTE, MRE: all server->client messages now acknowledge
int tmp = cl->reliableSent;
SV_CreateClientGameStateMessage( cl, &msg );
cl->reliableSent = tmp;
// finished writing the client packet
MSG_WriteByte( &msg, svc_EOF );
// write it to the demo file
len = LittleLong( cl->netchan.outgoingSequence - 1 );
FS_Write( &len, 4, cl->demo.demofile );
len = LittleLong( msg.cursize );
FS_Write( &len, 4, cl->demo.demofile );
FS_Write( msg.data, msg.cursize, cl->demo.demofile );
// the rest of the demo file will be copied from net messages
}
void SV_AutoRecordDemo( client_t *cl ) {
char demoName[MAX_OSPATH];
char demoFolderName[MAX_OSPATH];
char demoFileName[MAX_OSPATH];
char *demoNames[] = { demoFolderName, demoFileName };
char date[MAX_OSPATH];
char folderDate[MAX_OSPATH];
char folderTreeDate[MAX_OSPATH];
char demoPlayerName[MAX_NAME_LENGTH];
time_t rawtime;
struct tm * timeinfo;
time( &rawtime );
timeinfo = localtime( &rawtime );
strftime( date, sizeof( date ), "%Y-%m-%d_%H-%M-%S", timeinfo );
timeinfo = localtime( &sv.realMapTimeStarted );
strftime( folderDate, sizeof( folderDate ), "%Y-%m-%d_%H-%M-%S", timeinfo );
strftime( folderTreeDate, sizeof( folderTreeDate ), "%Y/%m/%d", timeinfo );
Q_strncpyz( demoPlayerName, cl->name, sizeof( demoPlayerName ) );
Q_CleanStr( demoPlayerName );
Com_sprintf( demoFileName, sizeof( demoFileName ), "%d %s %s %s",
cl - svs.clients, demoPlayerName, Cvar_VariableString( "mapname" ), date );
Com_sprintf( demoFolderName, sizeof( demoFolderName ), "%s %s", Cvar_VariableString( "mapname" ), folderDate );
// sanitize filename
for ( char **start = demoNames; start - demoNames < (ptrdiff_t)ARRAY_LEN( demoNames ); start++ ) {
Q_strstrip( *start, "\n\r;:.?*<>|\\/\"", NULL );
}
Com_sprintf( demoName, sizeof( demoName ), "autorecord/%s/%s/%s", folderTreeDate, demoFolderName, demoFileName );
SV_RecordDemo( cl, demoName );
}
static time_t SV_ExtractTimeFromDemoFolder( char *folder ) {
char *slash = strrchr( folder, '/' );
if ( slash ) {
folder = slash + 1;
}
size_t timeLen = strlen( "0000-00-00_00-00-00" );
if ( strlen( folder ) < timeLen ) {
return 0;
}
struct tm timeinfo;
timeinfo.tm_isdst = 0;
int numMatched = sscanf( folder + ( strlen(folder) - timeLen ), "%4d-%2d-%2d_%2d-%2d-%2d",
&timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
if ( numMatched < 6 ) {
// parsing failed
return 0;
}
timeinfo.tm_year -= 1900;
timeinfo.tm_mon--;
return mktime( &timeinfo );
}
static int QDECL SV_DemoFolderTimeComparator( const void *arg1, const void *arg2 ) {
char *left = (char *)arg1, *right = (char *)arg2;
time_t leftTime = SV_ExtractTimeFromDemoFolder( left );
time_t rightTime = SV_ExtractTimeFromDemoFolder( right );
if ( leftTime == 0 && rightTime == 0 ) {
return -strcmp( left, right );
} else if ( leftTime == 0 ) {
return 1;
} else if ( rightTime == 0 ) {
return -1;
}
return rightTime - leftTime;
}
// returns number of folders found. pass NULL result pointer for just a count.
static int SV_FindLeafFolders( const char *baseFolder, char *result, int maxResults, int maxFolderLength ) {
char *fileList = (char *)Z_Malloc( MAX_OSPATH * maxResults, TAG_FILESYS ); // too big for stack since this is recursive
char fullFolder[MAX_OSPATH];
int resultCount = 0;
char *fileName;
int i;
int numFiles = FS_GetFileList( baseFolder, "/", fileList, MAX_OSPATH * maxResults );
fileName = fileList;
for ( i = 0; i < numFiles; i++ ) {
if ( Q_stricmp( fileName, "." ) && Q_stricmp( fileName, ".." ) ) {
char *nextResult = NULL;
Com_sprintf( fullFolder, sizeof( fullFolder ), "%s/%s", baseFolder, fileName );
if ( result != NULL ) {
nextResult = &result[maxFolderLength * resultCount];
}
int newResults = SV_FindLeafFolders( fullFolder, nextResult, maxResults - resultCount, maxFolderLength );
resultCount += newResults;
if ( result != NULL && resultCount >= maxResults ) {
break;
}
if ( newResults == 0 ) {
if ( result != NULL ) {
Q_strncpyz( &result[maxFolderLength * resultCount], fullFolder, maxFolderLength );
}
resultCount++;
if ( result != NULL && resultCount >= maxResults ) {
break;
}
}
}
fileName += strlen( fileName ) + 1;
}
Z_Free( fileList );
return resultCount;
}
// starts demo recording on all active clients
void SV_BeginAutoRecordDemos() {
if ( sv_autoDemo->integer ) {
for ( client_t *client = svs.clients; client - svs.clients < sv_maxclients->integer; client++ ) {
if ( client->state == CS_ACTIVE && !client->demo.demorecording ) {
if ( client->netchan.remoteAddress.type != NA_BOT || sv_autoDemoBots->integer ) {
SV_AutoRecordDemo( client );
}
}
}
if ( sv_autoDemoMaxMaps->integer > 0 && sv.demosPruned == qfalse ) {
char autorecordDirList[500 * MAX_OSPATH], tmpFileList[5 * MAX_OSPATH];
int autorecordDirListCount = SV_FindLeafFolders( "demos/autorecord", autorecordDirList, 500, MAX_OSPATH );
int i;
qsort( autorecordDirList, autorecordDirListCount, MAX_OSPATH, SV_DemoFolderTimeComparator );
for ( i = sv_autoDemoMaxMaps->integer; i < autorecordDirListCount; i++ ) {
char *folder = &autorecordDirList[i * MAX_OSPATH], *slash = NULL;
FS_HomeRmdir( folder, qtrue );
// if this folder was the last thing in its parent folder (and its parent isn't the root folder),
// also delete the parent.
for (;;) {
slash = strrchr( folder, '/' );
if ( slash == NULL ) {
break;
}
slash[0] = '\0';
if ( !strcmp( folder, "demos/autorecord" ) ) {
break;
}
int numFiles = FS_GetFileList( folder, "", tmpFileList, sizeof( tmpFileList ) );
int numFolders = FS_GetFileList( folder, "/", tmpFileList, sizeof( tmpFileList ) );
// numFolders will include . and ..
if ( numFiles == 0 && numFolders == 2 ) {
// dangling empty folder, delete
FS_HomeRmdir( folder, qfalse );
} else {
break;
}
}
}
sv.demosPruned = qtrue;
}
}
}
// code is a merge of the cl_main.cpp function of the same name and SV_SendClientGameState in sv_client.cpp
static void SV_Record_f( void ) {
char demoName[MAX_OSPATH];
char name[MAX_OSPATH];
int i;
char *s;
client_t *cl;
if ( svs.clients == NULL ) {
Com_Printf( "cannot record server demo - null svs.clients\n" );
return;
}
if ( Cmd_Argc() > 3 ) {
Com_Printf( "record <demoname> <clientnum>\n" );
return;
}
if ( Cmd_Argc() == 3 ) {
int clIndex = atoi( Cmd_Argv( 2 ) );
if ( clIndex < 0 || clIndex >= sv_maxclients->integer ) {
Com_Printf( "Unknown client number %d.\n", clIndex );
return;
}
cl = &svs.clients[clIndex];
} else {
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++, cl++ )
{
if ( !cl->state )
{
continue;
}
if ( cl->demo.demorecording )
{
continue;
}
if ( cl->state == CS_ACTIVE )
{
break;
}
}
}
if (cl - svs.clients >= sv_maxclients->integer) {
Com_Printf( "No active client could be found.\n" );
return;
}
if ( cl->demo.demorecording ) {
Com_Printf( "Already recording.\n" );
return;
}
if ( cl->state != CS_ACTIVE ) {
Com_Printf( "Client is not active.\n" );
return;
}
if ( Cmd_Argc() >= 2 ) {
s = Cmd_Argv( 1 );
Q_strncpyz( demoName, s, sizeof( demoName ) );
Com_sprintf( name, sizeof( name ), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION );
} else {
// timestamp the file
SV_DemoFilename( demoName, sizeof( demoName ) );
Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION );
if ( FS_FileExists( name ) ) {
Com_Printf( "Record: Couldn't create a file\n");
return;
}
}
SV_RecordDemo( cl, demoName );
}
/*
=================
SV_WhitelistIP_f
=================
*/
static void SV_WhitelistIP_f( void ) {
if ( Cmd_Argc() < 2 ) {
Com_Printf ("Usage: whitelistip <ip>...\n");
return;
}
for ( int i = 1; i < Cmd_Argc(); i++ ) {
netadr_t adr;
if ( NET_StringToAdr( Cmd_Argv(i), &adr ) ) {
SVC_WhitelistAdr( adr );
Com_Printf("Added %s to the IP whitelist\n", NET_AdrToString(adr));
} else {
Com_Printf("Incorrect IP address: %s\n", Cmd_Argv(i));
}
}
}
//===========================================================
/*
==================
SV_CompleteMapName
==================
*/
static void SV_CompleteMapName( char *args, int argNum ) {
if ( argNum == 2 )
Field_CompleteFilename( "maps", "bsp", qtrue, qfalse );
}
/*
==================
SV_AddOperatorCommands
==================
*/
void SV_AddOperatorCommands( void ) {
static qboolean initialized;
if ( initialized ) {
return;
}
initialized = qtrue;
Cmd_AddCommand ("heartbeat", SV_Heartbeat_f, "Sends a heartbeat to the masterserver" );
Cmd_AddCommand ("kick", SV_Kick_f, "Kick a user from the server" );
Cmd_AddCommand ("kickbots", SV_KickBots_f, "Kick all bots from the server" );
Cmd_AddCommand ("kickall", SV_KickAll_f, "Kick all users from the server" );
Cmd_AddCommand ("kicknum", SV_KickNum_f, "Kick a user from the server by userid" );
Cmd_AddCommand ("clientkick", SV_KickNum_f, "Kick a user from the server by userid" );
Cmd_AddCommand ("status", SV_Status_f, "Prints status of server and connected clients" );
Cmd_AddCommand ("serverinfo", SV_Serverinfo_f, "Prints the serverinfo that is visible in the server browsers" );
Cmd_AddCommand ("systeminfo", SV_Systeminfo_f, "Prints the systeminfo variables that are replicated to clients" );
Cmd_AddCommand ("dumpuser", SV_DumpUser_f, "Prints the userinfo for a given userid" );
Cmd_AddCommand ("map_restart", SV_MapRestart_f, "Restart the current map" );
Cmd_AddCommand ("sectorlist", SV_SectorList_f);
Cmd_AddCommand ("map", SV_Map_f, "Load a new map with cheats disabled" );
Cmd_SetCommandCompletionFunc( "map", SV_CompleteMapName );
Cmd_AddCommand ("devmap", SV_Map_f, "Load a new map with cheats enabled" );
Cmd_SetCommandCompletionFunc( "devmap", SV_CompleteMapName );
// Cmd_AddCommand ("devmapbsp", SV_Map_f); // not used in MP codebase, no server BSP_cacheing
Cmd_AddCommand ("devmapmdl", SV_Map_f, "Load a new map with cheats enabled" );
Cmd_SetCommandCompletionFunc( "devmapmdl", SV_CompleteMapName );
Cmd_AddCommand ("devmapall", SV_Map_f, "Load a new map with cheats enabled" );
Cmd_SetCommandCompletionFunc( "devmapall", SV_CompleteMapName );
Cmd_AddCommand ("killserver", SV_KillServer_f, "Shuts the server down and disconnects all clients" );
Cmd_AddCommand ("svsay", SV_ConSay_f, "Broadcast server messages to clients" );
Cmd_AddCommand ("svtell", SV_ConTell_f, "Private message from the server to a user" );
Cmd_AddCommand ("forcetoggle", SV_ForceToggle_f, "Toggle g_forcePowerDisable bits" );
Cmd_AddCommand ("weapontoggle", SV_WeaponToggle_f, "Toggle g_weaponDisable bits" );
Cmd_AddCommand ("svrecord", SV_Record_f, "Record a server-side demo" );
Cmd_AddCommand ("svstoprecord", SV_StopRecord_f, "Stop recording a server-side demo" );
Cmd_AddCommand ("sv_rehashbans", SV_RehashBans_f, "Reloads banlist from file" );
Cmd_AddCommand ("sv_listbans", SV_ListBans_f, "Lists bans" );
Cmd_AddCommand ("sv_banaddr", SV_BanAddr_f, "Bans a user" );
Cmd_AddCommand ("sv_exceptaddr", SV_ExceptAddr_f, "Adds a ban exception for a user" );
Cmd_AddCommand ("sv_bandel", SV_BanDel_f, "Removes a ban" );
Cmd_AddCommand ("sv_exceptdel", SV_ExceptDel_f, "Removes a ban exception" );
Cmd_AddCommand ("sv_flushbans", SV_FlushBans_f, "Removes all bans and exceptions" );
Cmd_AddCommand ("whitelistip", SV_WhitelistIP_f, "Add IP to the whitelist" );
}
/*
==================
SV_RemoveOperatorCommands
==================
*/
void SV_RemoveOperatorCommands( void ) {
#if 0
// removing these won't let the server start again
Cmd_RemoveCommand ("heartbeat");
Cmd_RemoveCommand ("kick");
Cmd_RemoveCommand ("banUser");
Cmd_RemoveCommand ("banClient");
Cmd_RemoveCommand ("status");
Cmd_RemoveCommand ("serverinfo");
Cmd_RemoveCommand ("systeminfo");
Cmd_RemoveCommand ("dumpuser");
Cmd_RemoveCommand ("map_restart");
Cmd_RemoveCommand ("sectorlist");
Cmd_RemoveCommand ("svsay");
#endif
}