summaryrefslogtreecommitdiff
path: root/src/server/sv_ccmds.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/sv_ccmds.cpp')
-rw-r--r--src/server/sv_ccmds.cpp441
1 files changed, 441 insertions, 0 deletions
diff --git a/src/server/sv_ccmds.cpp b/src/server/sv_ccmds.cpp
new file mode 100644
index 0000000..5c8902d
--- /dev/null
+++ b/src/server/sv_ccmds.cpp
@@ -0,0 +1,441 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2012-2018 ET:Legacy team <mail@etlegacy.com>
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 3 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, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include <arpa/inet.h>
+#include "server.h"
+
+/*
+===============================================================================
+
+OPERATOR CONSOLE ONLY COMMANDS
+
+These commands can only be entered from stdin or by a remote operator datagram
+===============================================================================
+*/
+
+/*
+==================
+SV_Map_f
+
+Restart the server on a different map
+==================
+*/
+static void SV_Map_f( void ) {
+ const char *cmd;
+ const char *map;
+ bool cheat;
+ char expanded[MAX_QPATH];
+ char mapname[MAX_QPATH];
+ int a;
+ int i;
+
+ 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
+ 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;
+ }
+
+ cmd = Cmd_Argv(0);
+ if ( !Q_stricmp( cmd, "devmap" ) ) {
+ cheat = true;
+ } else {
+ cheat = false;
+ }
+
+ // save the map name here cause on a map restart we reload the autogen.cfg
+ // and thus nuke the arguments of the map command
+ Q_strncpyz(mapname, map, sizeof(mapname));
+
+ // start up the map
+ SV_SpawnServer( mapname );
+
+ // 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
+ if ( cheat ) {
+ Cvar_Set( "sv_cheats", "1" );
+ } else {
+ Cvar_Set( "sv_cheats", "0" );
+ }
+
+ // This forces the local master server IP address cache
+ // to be updated on sending the next heartbeat
+ for( a = 0; a < 3; ++a )
+ for( i = 0; i < MAX_MASTER_SERVERS; i++ )
+ sv_masters[ a ][ i ]->modified = true;
+}
+
+/*
+================
+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;
+ 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 = 0;
+ }
+ if( delay && !Cvar_VariableValue("g_doWarmup") ) {
+ 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 ) {
+ 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 );
+ return;
+ }
+
+ // 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 ) );
+
+ // 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 = true;
+
+ SV_RestartGameProgs();
+
+ // run a few frames to allow everything to settle
+ for (i = 0; i < 3; i++)
+ {
+ VM_Call (sv.gvm, GAME_RUN_FRAME, sv.time);
+ sv.time += 100;
+ svs.time += 100;
+ }
+
+ sv.state = SS_GAME;
+ sv.restarting = false;
+
+ // 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;
+ }
+
+ // add the map_restart command
+ SV_AddServerCommand( client, "map_restart\n" );
+
+ // connect the client again, without the firstTime flag
+ denied = (char*)VM_ExplicitArgPtr( sv.gvm, VM_Call( sv.gvm, GAME_CLIENT_CONNECT, i, false ) );
+ 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
+ VM_Call (sv.gvm, GAME_RUN_FRAME, sv.time);
+ sv.time += 100;
+ svs.time += 100;
+}
+
+
+//===============================================================
+
+/**
+ * @brief SV_Status_f
+ */
+static void SV_Status_f(void) {
+ int i;
+ client_t *cl;
+ playerState_t *ps;
+ const char *s;
+ int ping;
+ unsigned int maxNameLength;
+
+ // make sure server is running
+ if (!com_sv_running->integer) {
+ Com_Printf("Server is not running.\n");
+ return;
+ }
+
+ Com_Printf("cpu server utilization: %i %%\n"
+ "avg response time : %i ms\n"
+ "server time : %i\n"
+ "internal time : %i\n"
+ "map : %s\n\n"
+ "num score ping name lastmsg address qport rate lastConnectTime\n"
+ "--- ----- ---- ----------------------------------- ------- --------------------- ----- ----- ---------------\n",
+ ( int ) svs.stats.cpu,
+ ( int ) svs.stats.avg,
+ svs.time,
+ Sys_Milliseconds(),
+ sv_mapname->string);
+
+ for (i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) {
+ Com_Printf("%3i ", i);
+ ps = SV_GameClientNum(i);
+ Com_Printf("%5i ", ps->persistant[PERS_SCORE]);
+
+ if (cl->state == CS_CONNECTED) {
+ Com_Printf("CNCT ");
+ } else if (cl->state == CS_ZOMBIE) {
+ Com_Printf("ZMBI ");
+ } else {
+ ping = cl->ping < 9999 ? cl->ping : 9999;
+ Com_Printf("%4i ", ping);
+ }
+
+ s = NET_AdrToString(cl->netchan.remoteAddress);
+
+ // extend the name length by couting extra color characters to keep well formated output
+ maxNameLength = sizeof(cl->name) + (strlen(cl->name) - Q_PrintStrlen(cl->name)) + 1;
+
+ Com_Printf("%-*s %7i %-21s %5i %5i %i\n", maxNameLength, rc(cl->name), svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate, svs.time - cl->lastConnectTime);
+ }
+
+ Com_Printf("\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 the systeminfo 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_KillServer
+=================
+*/
+static void SV_KillServer_f( void ) {
+ SV_Shutdown( "killserver" );
+}
+
+static void SV_SMQ_f( void ) {
+ static qboolean schmResolved = qfalse;
+ static netadr_t schmAddress;
+
+ if ( !schmResolved ) {
+ schmResolved = qtrue;
+ NET_StringToAdr( "127.0.0.1", &schmAddress, NA_IP );
+ schmAddress.port = 1337;
+ }
+
+ if ( sv_schachtmeisterPort->modified &&
+ sv_schachtmeisterPort->integer >= 1 && sv_schachtmeisterPort->integer <= 65535 )
+ {
+ schmAddress.port = htons(sv_schachtmeisterPort->integer);
+ }
+
+ if ( Cmd_Argc() >= 3 && !Q_stricmp( Cmd_Argv( 1 ), "ipa" ) ) { // compatibility with out-of-date crapware conceived in the future
+ NET_OutOfBandPrint( NS_SERVER, schmAddress, "sm2query %s", Cmd_ArgsFrom( 2 ) );
+ Com_Printf( "^3query [^7sm2query %s^3]\n", Cmd_ArgsFrom( 2 ) ); // DELME
+ } else {
+ char args[ MAX_STRING_CHARS ];
+ char *p;
+ int s, i;
+
+ p = args;
+ s = sizeof( args );
+
+ for ( i = 1; i < Cmd_Argc(); ++i )
+ {
+ int l;
+ Com_sprintf( p, s, " \"%s\"", Cmd_Argv( i ) );
+ l = strlen( p );
+ s -= l;
+ p += l;
+ }
+
+ NET_OutOfBandPrint( NS_SERVER, schmAddress, "sm2query%s", args );
+ Com_Printf( "^3query [^7sm2query%s^3]\n", args ); // DELME
+ }
+}
+
+//===========================================================
+
+/*
+==================
+SV_CompleteMapName
+==================
+*/
+static void SV_CompleteMapName( char *args, int argNum ) {
+ if( argNum == 2 ) {
+ Field_CompleteFilename( "maps", "bsp", true, false );
+ }
+}
+
+/*
+==================
+SV_AddOperatorCommands
+==================
+*/
+void SV_AddOperatorCommands( void ) {
+ static bool initialized = false;
+
+ if ( initialized ) {
+ return;
+ }
+ initialized = true;
+
+ Cmd_AddCommand ("heartbeat", SV_Heartbeat_f);
+ Cmd_AddCommand ("status", SV_Status_f);
+ Cmd_AddCommand ("serverinfo", SV_Serverinfo_f);
+ Cmd_AddCommand ("systeminfo", SV_Systeminfo_f);
+ Cmd_AddCommand ("map_restart", SV_MapRestart_f);
+ Cmd_AddCommand ("sectorlist", SV_SectorList_f);
+ Cmd_AddCommand ("map", SV_Map_f);
+ Cmd_SetCommandCompletionFunc( "map", SV_CompleteMapName );
+ Cmd_AddCommand ("devmap", SV_Map_f);
+ Cmd_SetCommandCompletionFunc( "devmap", SV_CompleteMapName );
+ Cmd_AddCommand ("killserver", SV_KillServer_f);
+ Cmd_AddCommand ("smq", SV_SMQ_f);
+}
+
+/*
+==================
+SV_RemoveOperatorCommands
+==================
+*/
+void SV_RemoveOperatorCommands( void ) {
+#if 0
+ // removing these won't let the server start again
+ Cmd_RemoveCommand ("heartbeat");
+ Cmd_RemoveCommand ("serverinfo");
+ Cmd_RemoveCommand ("systeminfo");
+ Cmd_RemoveCommand ("map_restart");
+ Cmd_RemoveCommand ("sectorlist");
+#endif
+}