/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2013 Darklegion Development Copyright (C) 2012-2018 ET:Legacy team 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 =========================================================================== */ #include #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 ", then // cheats will not be allowed. If started with "devmap " // 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 ; iinteger ; 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 ; iinteger ; 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 }