/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program 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. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * ======================================================================= * * Server commands received by clients. There are only two ways on which * those can be received. Typed via stdin into the server console or via * a network / internal communication datagram. * * ======================================================================= */ #include "header/server.h" /* * Specify a list of master servers */ void SV_SetMaster_f ( void ) { int i, slot; /* only dedicated servers send heartbeats */ if ( !dedicated->value ) { Com_Printf( "Only dedicated servers use masters.\n" ); return; } /* make sure the server is listed public */ Cvar_Set( "public", "1" ); for ( i = 1; i < MAX_MASTERS; i++ ) { memset( &master_adr [ i ], 0, sizeof ( master_adr [ i ] ) ); } slot = 1; /* slot 0 will always contain the id master */ for ( i = 1; i < Cmd_Argc(); i++ ) { if ( slot == MAX_MASTERS ) { break; } if ( !NET_StringToAdr( Cmd_Argv( i ), &master_adr [ i ] ) ) { Com_Printf( "Bad address: %s\n", Cmd_Argv( i ) ); continue; } if ( master_adr [ slot ].port == 0 ) { master_adr [ slot ].port = BigShort( PORT_MASTER ); } Com_Printf( "Master server at %s\n", NET_AdrToString( master_adr [ slot ] ) ); Com_Printf( "Sending a ping.\n" ); Netchan_OutOfBandPrint( NS_SERVER, master_adr [ slot ], "ping" ); slot++; } svs.last_heartbeat = -9999999; } /* * Sets sv_client and sv_player to the player with idnum Cmd_Argv(1) */ qboolean SV_SetPlayer ( void ) { client_t *cl; int i; int idnum; char *s; if ( Cmd_Argc() < 2 ) { return ( false ); } s = Cmd_Argv( 1 ); /* numeric values are just slot numbers */ if ( ( s [ 0 ] >= '0' ) && ( s [ 0 ] <= '9' ) ) { idnum = atoi( Cmd_Argv( 1 ) ); if ( ( idnum < 0 ) || ( idnum >= maxclients->value ) ) { Com_Printf( "Bad client slot: %i\n", idnum ); return ( false ); } sv_client = &svs.clients [ idnum ]; sv_player = sv_client->edict; if ( !sv_client->state ) { Com_Printf( "Client %i is not active\n", idnum ); return ( false ); } return ( true ); } /* check for a name match */ for ( i = 0, cl = svs.clients; i < maxclients->value; i++, cl++ ) { if ( !cl->state ) { continue; } if ( !strcmp( cl->name, s ) ) { sv_client = cl; sv_player = sv_client->edict; return ( true ); } } Com_Printf( "Userid %s is not on the server\n", s ); return ( false ); } /* * Puts the server in demo mode on a specific map/cinematic */ void SV_DemoMap_f ( void ) { if ( Cmd_Argc() != 2 ) { Com_Printf( "USAGE: demomap \n" ); return; } SV_Map( true, Cmd_Argv( 1 ), false ); } /* * ================== * SV_GameMap_f * * Saves the state of the map just being exited and goes to a new map. * * If the initial character of the map string is '*', the next map is * in a new unit, so the current savegame directory is cleared of * map files. * * Example: * inter.cin+jail * Clears the archived maps, plays the inter.cin cinematic, then * goes to map jail.bsp. * ================== */ void SV_GameMap_f ( void ) { char *map; int i; client_t *cl; qboolean *savedInuse; if ( Cmd_Argc() != 2 ) { Com_Printf( "USAGE: gamemap \n" ); return; } Com_DPrintf( "SV_GameMap(%s)\n", Cmd_Argv( 1 ) ); FS_CreatePath( va( "%s/save/current/", FS_Gamedir() ) ); /* check for clearing the current savegame */ map = Cmd_Argv( 1 ); if ( map [ 0 ] == '*' ) { /* wipe all the *.sav files */ SV_WipeSavegame( "current" ); } else { /* save the map just exited */ if ( sv.state == ss_game ) { /* clear all the client inuse flags before saving so that when the level is re-entered, the clients will spawn at spawn points instead of occupying body shells */ savedInuse = malloc( maxclients->value * sizeof ( qboolean ) ); for ( i = 0, cl = svs.clients; i < maxclients->value; i++, cl++ ) { savedInuse [ i ] = cl->edict->inuse; cl->edict->inuse = false; } SV_WriteLevelFile(); /* we must restore these for clients to transfer over correctly */ for ( i = 0, cl = svs.clients; i < maxclients->value; i++, cl++ ) { cl->edict->inuse = savedInuse [ i ]; } free( savedInuse ); } } /* start up the next map */ SV_Map( false, Cmd_Argv( 1 ), false ); /* archive server state */ strncpy( svs.mapcmd, Cmd_Argv( 1 ), sizeof ( svs.mapcmd ) - 1 ); /* copy off the level to the autosave slot */ if ( !dedicated->value ) { SV_WriteServerFile( true ); SV_CopySaveGame( "current", "save0" ); } } /* * Goes directly to a given map without any savegame archiving. * For development work */ void SV_Map_f ( void ) { char *map; char expanded [ MAX_QPATH ]; if ( Cmd_Argc() != 2 ) { Com_Printf( "USAGE: map \n" ); return; } /* if not a pcx, demo, or cinematic, check to make sure the level exists */ map = Cmd_Argv( 1 ); if ( !strstr( map, "." ) && !strstr( map, "$" ) && ( *map != '*' ) ) { Com_sprintf( expanded, sizeof ( expanded ), "maps/%s.bsp", map ); if ( FS_LoadFile( expanded, NULL ) == -1 ) { Com_Printf( "Can't find %s\n", expanded ); return; } } sv.state = ss_dead; /* don't save current level when changing */ SV_WipeSavegame( "current" ); SV_GameMap_f(); } /* * Kick a user off of the server */ void SV_Kick_f ( void ) { if ( !svs.initialized ) { Com_Printf( "No server running.\n" ); return; } if ( Cmd_Argc() != 2 ) { Com_Printf( "Usage: kick \n" ); return; } if ( !SV_SetPlayer() ) { return; } if ( ( sv_client->state == cs_spawned ) && *sv_client->name ) { SV_BroadcastPrintf( PRINT_HIGH, "%s was kicked\n", sv_client->name ); } /* print directly, because the dropped client won't get the SV_BroadcastPrintf message */ SV_ClientPrintf( sv_client, PRINT_HIGH, "You were kicked from the game\n" ); SV_DropClient( sv_client ); sv_client->lastmessage = svs.realtime; /* min case there is a funny zombie */ } void SV_Status_f ( void ) { int i, j, l; client_t *cl; char *s; int ping; if ( !svs.clients ) { Com_Printf( "No server running.\n" ); return; } Com_Printf( "map : %s\n", sv.name ); Com_Printf( "num score ping name lastmsg address qport \n" ); Com_Printf( "--- ----- ---- --------------- ------- --------------------- ------\n" ); for ( i = 0, cl = svs.clients; i < maxclients->value; i++, cl++ ) { if ( !cl->state ) { continue; } Com_Printf( "%2i ", i ); Com_Printf( "%5i ", cl->edict->client->ps.stats [ STAT_FRAGS ] ); 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 ); } Com_Printf( "%s", cl->name ); l = 16 - strlen( cl->name ); for ( j = 0; j < l; j++ ) { Com_Printf( " " ); } Com_Printf( "%7i ", svs.realtime - cl->lastmessage ); s = NET_AdrToString( cl->netchan.remote_address ); Com_Printf( "%s", s ); l = 22 - strlen( s ); for ( j = 0; j < l; j++ ) { Com_Printf( " " ); } Com_Printf( "%5i", cl->netchan.qport ); Com_Printf( "\n" ); } Com_Printf( "\n" ); } void SV_ConSay_f ( void ) { client_t *client; int j; char *p; char text [ 1024 ]; if ( Cmd_Argc() < 2 ) { return; } if ( !svs.initialized ) { Com_Printf( "No server running.\n" ); return; } strcpy( text, "console: " ); p = Cmd_Args(); if ( *p == '"' ) { p++; p [ strlen( p ) - 1 ] = 0; } strcat( text, p ); for ( j = 0, client = svs.clients; j < maxclients->value; j++, client++ ) { if ( client->state != cs_spawned ) { continue; } SV_ClientPrintf( client, PRINT_CHAT, "%s\n", text ); } } void SV_Heartbeat_f ( void ) { svs.last_heartbeat = -9999999; } /* * Examine or change the serverinfo string */ void SV_Serverinfo_f ( void ) { Com_Printf( "Server info settings:\n" ); Info_Print( Cvar_Serverinfo() ); } /* * Examine all a users info strings */ void SV_DumpUser_f ( void ) { if ( !svs.initialized ) { Com_Printf( "No server running.\n" ); return; } if ( Cmd_Argc() != 2 ) { Com_Printf( "Usage: info \n" ); return; } if ( !SV_SetPlayer() ) { return; } Com_Printf( "userinfo\n" ); Com_Printf( "--------\n" ); Info_Print( sv_client->userinfo ); } /* * Begins server demo recording. Every entity and every message will be * recorded, but no playerinfo will be stored. Primarily for demo merging. */ void SV_ServerRecord_f ( void ) { char name [ MAX_OSPATH ]; byte buf_data [ 32768 ]; sizebuf_t buf; int len; int i; if ( Cmd_Argc() != 2 ) { Com_Printf( "serverrecord \n" ); return; } if ( svs.demofile ) { Com_Printf( "Already recording.\n" ); return; } if ( sv.state != ss_game ) { Com_Printf( "You must be in a level to record.\n" ); return; } if ( strstr( Cmd_Argv( 1 ), ".." ) || strstr( Cmd_Argv( 1 ), "/" ) || strstr( Cmd_Argv( 1 ), "\\" ) ) { Com_Printf( "Illegal filename.\n" ); return; } /* open the demo file */ Com_sprintf( name, sizeof ( name ), "%s/demos/%s.dm2", FS_Gamedir(), Cmd_Argv( 1 ) ); Com_Printf( "recording to %s.\n", name ); FS_CreatePath( name ); svs.demofile = fopen( name, "wb" ); if ( !svs.demofile ) { Com_Printf( "ERROR: couldn't open.\n" ); return; } /* setup a buffer to catch all multicasts */ SZ_Init( &svs.demo_multicast, svs.demo_multicast_buf, sizeof ( svs.demo_multicast_buf ) ); /* write a single giant fake message with all the startup info */ SZ_Init( &buf, buf_data, sizeof ( buf_data ) ); /* serverdata needs to go over for all types of servers to make sure the protocol is right, and to set the gamedir */ MSG_WriteByte( &buf, svc_serverdata ); MSG_WriteLong( &buf, PROTOCOL_VERSION ); MSG_WriteLong( &buf, svs.spawncount ); /* 2 means server demo */ MSG_WriteByte( &buf, 2 ); /* demos are always attract loops */ MSG_WriteString( &buf, (char *) Cvar_VariableString( "gamedir" ) ); MSG_WriteShort( &buf, -1 ); /* send full levelname */ MSG_WriteString( &buf, sv.configstrings [ CS_NAME ] ); for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { if ( sv.configstrings [ i ] [ 0 ] ) { MSG_WriteByte( &buf, svc_configstring ); MSG_WriteShort( &buf, i ); MSG_WriteString( &buf, sv.configstrings [ i ] ); if ( buf.cursize + 67 >= buf.maxsize ) { Com_Printf( "not enough buffer space available.\n" ); fclose( svs.demofile ); return; } } } /* write it to the demo file */ Com_DPrintf( "signon message length: %i\n", buf.cursize ); len = LittleLong( buf.cursize ); fwrite( &len, 4, 1, svs.demofile ); fwrite( buf.data, buf.cursize, 1, svs.demofile ); } /* * Ends server demo recording */ void SV_ServerStop_f ( void ) { if ( !svs.demofile ) { Com_Printf( "Not doing a serverrecord.\n" ); return; } fclose( svs.demofile ); svs.demofile = NULL; Com_Printf( "Recording completed.\n" ); } /* * Kick everyone off, possibly in preparation for a new game */ void SV_KillServer_f ( void ) { if ( !svs.initialized ) { return; } SV_Shutdown( "Server was killed.\n", false ); NET_Config( false ); /* close network sockets */ } /* * Let the game dll handle a command */ void SV_ServerCommand_f ( void ) { if ( !ge ) { Com_Printf( "No game loaded.\n" ); return; } ge->ServerCommand(); } void SV_InitOperatorCommands ( void ) { Cmd_AddCommand( "heartbeat", SV_Heartbeat_f ); Cmd_AddCommand( "kick", SV_Kick_f ); Cmd_AddCommand( "status", SV_Status_f ); Cmd_AddCommand( "serverinfo", SV_Serverinfo_f ); Cmd_AddCommand( "dumpuser", SV_DumpUser_f ); Cmd_AddCommand( "map", SV_Map_f ); Cmd_AddCommand( "demomap", SV_DemoMap_f ); Cmd_AddCommand( "gamemap", SV_GameMap_f ); Cmd_AddCommand( "setmaster", SV_SetMaster_f ); if ( dedicated->value ) { Cmd_AddCommand( "say", SV_ConSay_f ); } Cmd_AddCommand( "serverrecord", SV_ServerRecord_f ); Cmd_AddCommand( "serverstop", SV_ServerStop_f ); Cmd_AddCommand( "save", SV_Savegame_f ); Cmd_AddCommand( "load", SV_Loadgame_f ); Cmd_AddCommand( "killserver", SV_KillServer_f ); Cmd_AddCommand( "sv", SV_ServerCommand_f ); }