/* 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. */ #include "sv_local.h" server_static_t svs; // persistant server info server_t sv; // local server void SV_ClientReset( client_t *client ) { if( client->state < cs_connected ) { return; } // any partially connected client will be restarted client->state = cs_connected; client->lastframe = -1; client->frames_nodelta = 0; client->send_delta = 0; client->surpressCount = 0; memset( &client->lastcmd, 0, sizeof( client->lastcmd ) ); } #if USE_FPS static void SV_SetFrameTime( void ) { int i = sv_fps->integer / 10; clamp( i, 1, 6 ); sv.frametime = 100 / i; sv.framemult = i; Cvar_SetInteger( sv_fps, i * 10, CVAR_SET_DIRECT ); } #endif /* ================ SV_SpawnServer Change the server to a new map, taking all connected clients along with it. ================ */ static void SV_SpawnServer( cm_t *cm, const char *server, const char *spawnpoint ) { int i; char string[MAX_QPATH]; client_t *client; Com_Printf( "------- Server Initialization -------\n" ); Com_Printf( "SpawnServer: %s\n", server ); CM_FreeMap( &sv.cm ); // wipe the entire per-level structure memset( &sv, 0, sizeof( sv ) ); sv.spawncount = ( rand() | ( rand() << 16 ) ) ^ Sys_Milliseconds(); sv.spawncount &= 0x7FFFFFFF; // reset entity counter svs.nextEntityStates = 0; // save name for levels that don't set message Q_strlcpy( sv.configstrings[CS_NAME], server, MAX_QPATH ); Q_strlcpy( sv.name, server, sizeof( sv.name ) ); if( Cvar_VariableInteger( "deathmatch" ) ) { sprintf( sv.configstrings[CS_AIRACCEL], "%d", sv_airaccelerate->integer ); } else { strcpy( sv.configstrings[CS_AIRACCEL], "0" ); } FOR_EACH_CLIENT( client ) { // needs to reconnect SV_ClientReset( client ); client->spawncount = sv.spawncount; } Q_concat( string, sizeof( string ), "maps/", server, ".bsp", NULL ); sv.cm = *cm; strcpy( sv.configstrings[CS_MODELS + 1], string ); sprintf( sv.configstrings[CS_MAPCHECKSUM], "%d", ( int )cm->cache->checksum ); for( i = 1; i < sv.cm.cache->nummodels; i++ ) { sprintf( sv.configstrings[ CS_MODELS + 1 + i ], "*%d", i ); } // // clear physics interaction links // SV_ClearWorld(); // // spawn the rest of the entities on the map // #if USE_FPS SV_SetFrameTime(); #endif // precache and static commands can be issued during // map initialization sv.state = ss_loading; // load and spawn all other entities ge->SpawnEntities ( sv.name, cm->cache->entitystring, spawnpoint ); // run two frames to allow everything to settle ge->RunFrame (); ge->RunFrame (); // make sure maxclients string is correct sprintf( sv.configstrings[CS_MAXCLIENTS], "%d", sv_maxclients->integer ); // all precaches are complete sv.state = ss_game; #if USE_MVD_SERVER // respawn dummy MVD client, set base states, etc SV_MvdMapChanged(); #endif // set serverinfo variable SV_InfoSet( "mapname", sv.name ); SV_InfoSet( "port", net_port->string ); Cvar_SetInteger( sv_running, ss_game, FROM_CODE ); Cvar_Set( "sv_paused", "0" ); Cvar_Set( "timedemo", "0" ); EXEC_TRIGGER( sv_changemapcmd ); #if USE_SYSCON SV_SetConsoleTitle(); #endif Com_Printf ("-------------------------------------\n"); } /* ============== SV_InitGame A brand new game has been started. If ismvd is true, load the built-in MVD game module. ============== */ void SV_InitGame( qboolean ismvd ) { int i, entnum; edict_t *ent; client_t *client; if( svs.initialized ) { // cause any connected clients to reconnect SV_Shutdown( "Server restarted\n", KILL_RESTART ); } else { #if USE_CLIENT // make sure the client is down CL_Disconnect( ERR_SILENT, NULL ); SCR_BeginLoadingPlaque(); #endif CM_FreeMap( &sv.cm ); memset( &sv, 0, sizeof( sv ) ); } // get any latched variable changes (maxclients, etc) Cvar_GetLatchedVars (); #if !USE_CLIENT Cvar_Reset( sv_recycle ); #endif if( ismvd ) { Cvar_Set( "deathmatch", "1" ); Cvar_Set( "coop", "0" ); } else { if( Cvar_VariableInteger( "coop" ) && Cvar_VariableInteger( "deathmatch" ) ) { Com_Printf( "Deathmatch and Coop both set, disabling Coop\n" ); Cvar_Set( "coop", "0" ); } // dedicated servers can't be single player and are usually DM // so unless they explicity set coop, force it to deathmatch if( Com_IsDedicated() ) { if( !Cvar_VariableInteger( "coop" ) ) Cvar_Set( "deathmatch", "1" ); } } // init clients if( Cvar_VariableInteger( "deathmatch" ) ) { if( sv_maxclients->integer <= 1 ) { Cvar_SetInteger( sv_maxclients, 8, FROM_CODE ); } else if( sv_maxclients->integer > CLIENTNUM_RESERVED ) { Cvar_SetInteger( sv_maxclients, CLIENTNUM_RESERVED, FROM_CODE ); } } else if( Cvar_VariableInteger( "coop" ) ) { if( sv_maxclients->integer <= 1 || sv_maxclients->integer > 4 ) Cvar_Set( "maxclients", "4" ); } else { // non-deathmatch, non-coop is one player Cvar_FullSet( "maxclients", "1", CVAR_SERVERINFO|CVAR_LATCH, FROM_CODE ); } // enable networking if( sv_maxclients->integer > 1 ) { NET_Config( NET_SERVER ); } svs.udp_client_pool = SV_Mallocz( sizeof( client_t ) * sv_maxclients->integer ); svs.numEntityStates = sv_maxclients->integer * UPDATE_BACKUP * MAX_PACKET_ENTITIES; svs.entityStates = SV_Mallocz( sizeof( entity_state_t ) * svs.numEntityStates ); #if USE_MVD_SERVER // initialize MVD server if( !ismvd ) { SV_MvdInit(); } #endif Cvar_ClampInteger( sv_reserved_slots, 0, sv_maxclients->integer - 1 ); #if USE_ZLIB svs.z.zalloc = SV_Zalloc; svs.z.zfree = SV_Zfree; if( deflateInit2( &svs.z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 9, Z_DEFAULT_STRATEGY ) != Z_OK ) { Com_Error( ERR_FATAL, "%s: deflateInit2() failed", __func__ ); } #endif // init game #if USE_MVD_CLIENT if( ismvd ) { if( ge ) { SV_ShutdownGameProgs(); } ge = &mvd_ge; ge->Init(); } else #endif SV_InitGameProgs(); // init rate limits SV_RateInit( &svs.ratelimit_status, sv_status_limit->integer, 1000 ); SV_RateInit( &svs.ratelimit_badpass, 1, sv_badauth_time->value * 1000 ); SV_RateInit( &svs.ratelimit_badrcon, 1, sv_badauth_time->value * 1000 ); List_Init( &svs.udp_client_list ); for( i = 0; i < sv_maxclients->integer; i++ ) { client = svs.udp_client_pool + i; entnum = i + 1; ent = EDICT_NUM( entnum ); ent->s.number = entnum; client->edict = ent; client->number = i; } #if USE_AC_SERVER AC_Connect( ismvd ); #endif svs.initialized = qtrue; } /* ====================== SV_Map the full syntax is: map [*]$+ command from the console or progs. Map can also be a.cin, .pcx, or .dm2 file Nextserver is used to allow a cinematic to play, then proceed to another level: map tram.cin+jail_e3 ====================== */ void SV_Map (const char *levelstring, qboolean restart) { char level[MAX_QPATH]; char spawnpoint[MAX_QPATH]; char expanded[MAX_QPATH]; char *ch; cm_t cm; qerror_t ret; // skip the end-of-unit flag if necessary if( *levelstring == '*' ) { levelstring++; } // save levelstring as it typically points to cmd_argv Q_strlcpy( level, levelstring, sizeof( level ) ); // if there is a + in the map, set nextserver to the remainder ch = strchr(level, '+'); if (ch) { *ch = 0; Cvar_Set ("nextserver", va("gamemap \"%s\"", ch+1)); } else { Cvar_Set ("nextserver", ""); } // if there is a $, use the remainder as a spawnpoint ch = strchr( level, '$' ); if( ch ) { *ch = 0; strcpy( spawnpoint, ch + 1 ); } else { spawnpoint[0] = 0; } Q_concat( expanded, sizeof( expanded ), "maps/", level, ".bsp", NULL ); ret = CM_LoadMap( &cm, expanded ); if( ret < 0 ) { Com_Printf( "Couldn't load %s: %s\n", expanded, Q_ErrorString( ret ) ); return; } if( sv.state != ss_game || restart ) { SV_InitGame( qfalse ); // the game is just starting } // change state to loading if( sv.state > ss_loading ) { sv.state = ss_loading; } #if USE_CLIENT SCR_BeginLoadingPlaque(); // for local system #endif SV_BroadcastCommand( "changing map=%s\n", level ); SV_SendClientMessages(); SV_SendAsyncPackets(); SV_SpawnServer( &cm, level, spawnpoint ); SV_BroadcastCommand( "reconnect\n" ); }