You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1081 lines
34 KiB
1081 lines
34 KiB
/* |
|
sv_main.c - server main loop |
|
Copyright (C) 2007 Uncle Mike |
|
|
|
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 3 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. |
|
*/ |
|
|
|
#include "common.h" |
|
#include "server.h" |
|
#include "net_encode.h" |
|
|
|
#define HEARTBEAT_SECONDS 300.0f // 300 seconds |
|
|
|
// server cvars |
|
CVAR_DEFINE_AUTO( sv_lan, "0", 0, "server is a lan server ( no heartbeat, no authentication, no non-class C addresses, 9999.0 rate, etc." ); |
|
CVAR_DEFINE_AUTO( sv_lan_rate, "20000.0", 0, "rate for lan server" ); |
|
CVAR_DEFINE_AUTO( sv_aim, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "auto aiming option" ); |
|
CVAR_DEFINE_AUTO( sv_unlag, "1", 0, "allow lag compensation on server-side" ); |
|
CVAR_DEFINE_AUTO( sv_maxunlag, "0.5", 0, "max latency value which can be interpolated (by default ping should not exceed 500 units)" ); |
|
CVAR_DEFINE_AUTO( sv_unlagpush, "0.0", 0, "interpolation bias for unlag time" ); |
|
CVAR_DEFINE_AUTO( sv_unlagsamples, "1", 0, "max samples to interpolate" ); |
|
CVAR_DEFINE_AUTO( rcon_password, "", 0, "remote connect password" ); |
|
CVAR_DEFINE_AUTO( sv_filterban, "1", 0, "filter banned users" ); |
|
CVAR_DEFINE_AUTO( sv_cheats, "0", FCVAR_SERVER, "allow cheats on server" ); |
|
CVAR_DEFINE_AUTO( sv_instancedbaseline, "1", 0, "allow to use instanced baselines to saves network overhead" ); |
|
CVAR_DEFINE_AUTO( sv_contact, "", FCVAR_ARCHIVE|FCVAR_SERVER, "server techincal support contact address or web-page" ); |
|
CVAR_DEFINE_AUTO( sv_minupdaterate, "10.0", FCVAR_ARCHIVE, "minimal value for 'cl_updaterate' window" ); |
|
CVAR_DEFINE_AUTO( sv_maxupdaterate, "30.0", FCVAR_ARCHIVE, "maximal value for 'cl_updaterate' window" ); |
|
CVAR_DEFINE_AUTO( sv_minrate, "0", FCVAR_SERVER, "min bandwidth rate allowed on server, 0 == unlimited" ); |
|
CVAR_DEFINE_AUTO( sv_maxrate, "0", FCVAR_SERVER, "max bandwidth rate allowed on server, 0 == unlimited" ); |
|
CVAR_DEFINE_AUTO( sv_logrelay, "0", FCVAR_ARCHIVE, "allow log messages from remote machines to be logged on this server" ); |
|
CVAR_DEFINE_AUTO( sv_newunit, "0", 0, "clear level-saves from previous SP game chapter to help keep .sav file size as minimum" ); |
|
CVAR_DEFINE_AUTO( sv_clienttrace, "1", FCVAR_SERVER, "0 = big box(Quake), 0.5 = halfsize, 1 = normal (100%), otherwise it's a scaling factor" ); |
|
CVAR_DEFINE_AUTO( sv_timeout, "65", 0, "after this many seconds without a message from a client, the client is dropped" ); |
|
CVAR_DEFINE_AUTO( sv_failuretime, "0.5", 0, "after this long without a packet from client, don't send any more until client starts sending again" ); |
|
CVAR_DEFINE_AUTO( sv_password, "", FCVAR_SERVER|FCVAR_PROTECTED, "server password for entry into multiplayer games" ); |
|
CVAR_DEFINE_AUTO( sv_proxies, "1", FCVAR_SERVER, "maximum count of allowed proxies for HLTV spectating" ); |
|
CVAR_DEFINE_AUTO( sv_send_logos, "1", 0, "send custom decal logo to other players so they can view his too" ); |
|
CVAR_DEFINE_AUTO( sv_send_resources, "1", 0, "allow to download missed resources for players" ); |
|
CVAR_DEFINE_AUTO( sv_logbans, "0", 0, "print into the server log info about player bans" ); |
|
CVAR_DEFINE_AUTO( sv_allow_upload, "1", FCVAR_SERVER, "allow uploading custom resources on a server" ); |
|
CVAR_DEFINE_AUTO( sv_allow_download, "1", FCVAR_SERVER, "allow downloading custom resources to the client" ); |
|
CVAR_DEFINE_AUTO( sv_uploadmax, "0.5", FCVAR_SERVER, "max size to upload custom resources (500 kB as default)" ); |
|
CVAR_DEFINE_AUTO( sv_downloadurl, "", FCVAR_PROTECTED, "location from which clients can download missing files" ); |
|
CVAR_DEFINE( sv_consistency, "mp_consistency", "1", FCVAR_SERVER, "enbale consistency check in multiplayer" ); |
|
CVAR_DEFINE_AUTO( mp_logecho, "1", 0, "log multiplayer frags to server logfile" ); |
|
CVAR_DEFINE_AUTO( mp_logfile, "1", 0, "log multiplayer frags to console" ); |
|
|
|
// game-related cvars |
|
CVAR_DEFINE_AUTO( mapcyclefile, "mapcycle.txt", 0, "name of multiplayer map cycle configuration file" ); |
|
CVAR_DEFINE_AUTO( motdfile, "motd.txt", 0, "name of 'message of the day' file" ); |
|
CVAR_DEFINE_AUTO( logsdir, "logs", 0, "place to store multiplayer logs" ); |
|
CVAR_DEFINE_AUTO( bannedcfgfile, "banned.cfg", 0, "name of list of banned users" ); |
|
CVAR_DEFINE_AUTO( deathmatch, "0", 0, "deathmatch mode in multiplayer game" ); |
|
CVAR_DEFINE_AUTO( coop, "0", 0, "cooperative mode in multiplayer game" ); |
|
CVAR_DEFINE_AUTO( teamplay, "0", 0, "team mode in multiplayer game" ); |
|
CVAR_DEFINE_AUTO( skill, "1", 0, "skill level in singleplayer game" ); |
|
CVAR_DEFINE_AUTO( temp1, "0", 0, "temporary cvar that used by some mods" ); |
|
|
|
// physic-related variables |
|
CVAR_DEFINE_AUTO( sv_gravity, "800", FCVAR_MOVEVARS, "world gravity value" ); |
|
CVAR_DEFINE_AUTO( sv_stopspeed, "100", FCVAR_MOVEVARS, "how fast you come to a complete stop" ); |
|
CVAR_DEFINE_AUTO( sv_maxspeed, "320", FCVAR_MOVEVARS, "maximum speed a player can accelerate to when on ground" ); |
|
CVAR_DEFINE_AUTO( sv_spectatormaxspeed, "500", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "maximum speed a spectator can accelerate in air" ); |
|
CVAR_DEFINE_AUTO( sv_accelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed" ); |
|
CVAR_DEFINE_AUTO( sv_airaccelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed while in the air" ); |
|
CVAR_DEFINE_AUTO( sv_wateraccelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed while in the water" ); |
|
CVAR_DEFINE_AUTO( sv_friction, "4", FCVAR_MOVEVARS, "how fast you slow down" ); |
|
CVAR_DEFINE( sv_edgefriction, "edgefriction", "2", FCVAR_MOVEVARS, "how much you slow down when nearing a ledge you might fall off" ); |
|
CVAR_DEFINE_AUTO( sv_waterfriction, "1", FCVAR_MOVEVARS, "how fast you slow down in water" ); |
|
CVAR_DEFINE_AUTO( sv_bounce, "1", FCVAR_MOVEVARS, "bounce factor for entities with MOVETYPE_BOUNCE" ); |
|
CVAR_DEFINE_AUTO( sv_stepsize, "18", FCVAR_MOVEVARS, "how high you and NPS's can step up" ); |
|
CVAR_DEFINE_AUTO( sv_maxvelocity, "2000", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "max velocity for all things in the world" ); |
|
CVAR_DEFINE_AUTO( sv_zmax, "4096", FCVAR_MOVEVARS|FCVAR_SPONLY, "maximum viewable distance" ); |
|
CVAR_DEFINE_AUTO( sv_wateramp, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "world waveheight factor" ); |
|
CVAR_DEFINE( sv_footsteps, "mp_footsteps", "1", FCVAR_MOVEVARS, "world gravity value" ); |
|
CVAR_DEFINE_AUTO( sv_skyname, "desert", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skybox name (can be dynamically changed in-game)" ); |
|
CVAR_DEFINE_AUTO( sv_rollangle, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED|FCVAR_ARCHIVE, "how much to tilt the view when strafing" ); |
|
CVAR_DEFINE_AUTO( sv_rollspeed, "200", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "how much strafing is necessary to tilt the view" ); |
|
CVAR_DEFINE_AUTO( sv_skycolor_r, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight red component value" ); |
|
CVAR_DEFINE_AUTO( sv_skycolor_g, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight green component value" ); |
|
CVAR_DEFINE_AUTO( sv_skycolor_b, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight blue component value" ); |
|
CVAR_DEFINE_AUTO( sv_skyvec_x, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight direction by x-axis" ); |
|
CVAR_DEFINE_AUTO( sv_skyvec_y, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight direction by y-axis" ); |
|
CVAR_DEFINE_AUTO( sv_skyvec_z, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight direction by z-axis" ); |
|
CVAR_DEFINE_AUTO( sv_wateralpha, "1", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "world surfaces water transparency factor. 1.0 - solid, 0.0 - fully transparent" ); |
|
CVAR_DEFINE_AUTO( sv_background_freeze, "1", FCVAR_ARCHIVE, "freeze player movement on background maps (e.g. to prevent falling)" ); |
|
CVAR_DEFINE_AUTO( showtriggers, "0", FCVAR_LATCH, "debug cvar shows triggers" ); |
|
CVAR_DEFINE_AUTO( sv_airmove, "1", FCVAR_SERVER, "obsolete, compatibility issues" ); |
|
CVAR_DEFINE_AUTO( sv_version, "", FCVAR_READ_ONLY, "engine version string" ); |
|
CVAR_DEFINE_AUTO( hostname, "", FCVAR_SERVER|FCVAR_PRINTABLEONLY, "name of current host" ); |
|
CVAR_DEFINE_AUTO( sv_fps, "0.0", FCVAR_SERVER, "server framerate" ); |
|
|
|
// gore-related cvars |
|
CVAR_DEFINE_AUTO( violence_hblood, "1", 0, "draw human blood" ); |
|
CVAR_DEFINE_AUTO( violence_ablood, "1", 0, "draw alien blood" ); |
|
CVAR_DEFINE_AUTO( violence_hgibs, "1", 0, "show human gib entities" ); |
|
CVAR_DEFINE_AUTO( violence_agibs, "1", 0, "show alien gib entities" ); |
|
|
|
convar_t *sv_novis; // disable server culling entities by vis |
|
convar_t *sv_pausable; |
|
convar_t *timeout; // seconds without any message |
|
convar_t *sv_lighting_modulate; |
|
convar_t *sv_maxclients; |
|
convar_t *sv_check_errors; |
|
convar_t *public_server; // should heartbeats be sent |
|
convar_t *sv_reconnect_limit; // minimum seconds between connect messages |
|
convar_t *sv_validate_changelevel; |
|
convar_t *sv_sendvelocity; |
|
convar_t *sv_hostmap; |
|
|
|
convar_t *sv_allow_noinputdevices; |
|
convar_t *sv_allow_touch; |
|
convar_t *sv_allow_mouse; |
|
convar_t *sv_allow_joystick; |
|
convar_t *sv_allow_vr; |
|
|
|
void Master_Shutdown( void ); |
|
|
|
//============================================================================ |
|
/* |
|
================ |
|
SV_HasActivePlayers |
|
|
|
returns true if server have spawned players |
|
================ |
|
*/ |
|
qboolean SV_HasActivePlayers( void ) |
|
{ |
|
int i; |
|
|
|
// server inactive |
|
if( !svs.clients ) return false; |
|
|
|
for( i = 0; i < svs.maxclients; i++ ) |
|
{ |
|
if( svs.clients[i].state == cs_spawned ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
/* |
|
=================== |
|
SV_UpdateMovevars |
|
|
|
check movevars for changes every frame |
|
send updates to client if changed |
|
=================== |
|
*/ |
|
void SV_UpdateMovevars( qboolean initialize ) |
|
{ |
|
if( sv.state == ss_dead ) |
|
return; |
|
|
|
if( !initialize && !host.movevars_changed ) |
|
return; |
|
|
|
// check range |
|
if( sv_zmax.value < 256.0f ) Cvar_SetValue( "sv_zmax", 256.0f ); |
|
|
|
// clamp it right |
|
if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) |
|
{ |
|
if( sv_zmax.value > 131070.0f ) |
|
Cvar_SetValue( "sv_zmax", 131070.0f ); |
|
} |
|
else |
|
{ |
|
if( sv_zmax.value > 32767.0f ) |
|
Cvar_SetValue( "sv_zmax", 32767.0f ); |
|
} |
|
|
|
svgame.movevars.gravity = sv_gravity.value; |
|
svgame.movevars.stopspeed = sv_stopspeed.value; |
|
svgame.movevars.maxspeed = sv_maxspeed.value; |
|
svgame.movevars.spectatormaxspeed = sv_spectatormaxspeed.value; |
|
svgame.movevars.accelerate = sv_accelerate.value; |
|
svgame.movevars.airaccelerate = sv_airaccelerate.value; |
|
svgame.movevars.wateraccelerate = sv_wateraccelerate.value; |
|
svgame.movevars.friction = sv_friction.value; |
|
svgame.movevars.edgefriction = sv_edgefriction.value; |
|
svgame.movevars.waterfriction = sv_waterfriction.value; |
|
svgame.movevars.bounce = sv_bounce.value; |
|
svgame.movevars.stepsize = sv_stepsize.value; |
|
svgame.movevars.maxvelocity = sv_maxvelocity.value; |
|
svgame.movevars.zmax = sv_zmax.value; |
|
svgame.movevars.waveHeight = sv_wateramp.value; |
|
Q_strncpy( svgame.movevars.skyName, sv_skyname.string, sizeof( svgame.movevars.skyName )); |
|
svgame.movevars.footsteps = sv_footsteps.value; |
|
svgame.movevars.rollangle = sv_rollangle.value; |
|
svgame.movevars.rollspeed = sv_rollspeed.value; |
|
svgame.movevars.skycolor_r = sv_skycolor_r.value; |
|
svgame.movevars.skycolor_g = sv_skycolor_g.value; |
|
svgame.movevars.skycolor_b = sv_skycolor_b.value; |
|
svgame.movevars.skyvec_x = sv_skyvec_x.value; |
|
svgame.movevars.skyvec_y = sv_skyvec_y.value; |
|
svgame.movevars.skyvec_z = sv_skyvec_z.value; |
|
svgame.movevars.wateralpha = sv_wateralpha.value; |
|
svgame.movevars.features = host.features; // just in case. not really need |
|
svgame.movevars.entgravity = 1.0f; |
|
|
|
if( initialize ) return; // too early |
|
|
|
if( MSG_WriteDeltaMovevars( &sv.reliable_datagram, &svgame.oldmovevars, &svgame.movevars )) |
|
memcpy( &svgame.oldmovevars, &svgame.movevars, sizeof( movevars_t )); // oldstate changed |
|
|
|
host.movevars_changed = false; |
|
} |
|
|
|
/* |
|
================= |
|
SV_CheckCmdTimes |
|
================= |
|
*/ |
|
void SV_CheckCmdTimes( void ) |
|
{ |
|
sv_client_t *cl; |
|
static double lastreset = 0; |
|
float diff; |
|
int i; |
|
|
|
if( sv_fps.value != 0.0f ) |
|
{ |
|
if( sv_fps.value < MIN_FPS ) |
|
Cvar_SetValue( "sv_fps", MIN_FPS ); |
|
|
|
if( sv_fps.value > MAX_FPS ) |
|
Cvar_SetValue( "sv_fps", MAX_FPS ); |
|
} |
|
|
|
if( Host_IsLocalGame( )) |
|
return; |
|
|
|
if(( host.realtime - lastreset ) < 1.0 ) |
|
return; |
|
|
|
lastreset = host.realtime; |
|
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) |
|
{ |
|
if( cl->state != cs_spawned ) |
|
continue; |
|
|
|
if( cl->connecttime == 0.0 ) |
|
{ |
|
cl->connecttime = host.realtime; |
|
} |
|
|
|
diff = cl->connecttime + cl->cmdtime - host.realtime; |
|
|
|
if( diff > net_clockwindow->value ) |
|
{ |
|
cl->ignorecmdtime = net_clockwindow->value + host.realtime; |
|
cl->cmdtime = host.realtime - cl->connecttime; |
|
} |
|
else if( diff < -net_clockwindow->value ) |
|
{ |
|
cl->cmdtime = host.realtime - cl->connecttime; |
|
} |
|
} |
|
} |
|
|
|
/* |
|
================= |
|
SV_ProcessFile |
|
|
|
process incoming file (customization) |
|
================= |
|
*/ |
|
void SV_ProcessFile( sv_client_t *cl, const char *filename ) |
|
{ |
|
customization_t *pList; |
|
resource_t *resource; |
|
resource_t *next; |
|
byte md5[16]; |
|
qboolean bFound; |
|
qboolean bError; |
|
|
|
if( filename[0] != '!' ) |
|
{ |
|
Con_Printf( "Ignoring non-customization file upload of %s\n", filename ); |
|
return; |
|
} |
|
|
|
COM_HexConvert( filename + 4, 32, md5 ); |
|
|
|
for( resource = cl->resourcesneeded.pNext; resource != &cl->resourcesneeded; resource = next ) |
|
{ |
|
next = resource->pNext; |
|
|
|
if( !memcmp( resource->rgucMD5_hash, md5, 16 )) |
|
break; |
|
} |
|
|
|
if( resource == &cl->resourcesneeded ) |
|
{ |
|
Con_Printf( "SV_ProcessFile: Unrequested decal\n" ); |
|
return; |
|
} |
|
|
|
if( resource->nDownloadSize != cl->netchan.tempbuffersize ) |
|
{ |
|
Con_Printf( "Downloaded %i bytes for purported %i byte file\n", cl->netchan.tempbuffersize, resource->nDownloadSize ); |
|
return; |
|
} |
|
|
|
HPAK_AddLump( true, CUSTOM_RES_PATH, resource, cl->netchan.tempbuffer, NULL ); |
|
ClearBits( resource->ucFlags, RES_WASMISSING ); |
|
SV_MoveToOnHandList( cl, resource ); |
|
|
|
bError = false; |
|
bFound = false; |
|
|
|
for( pList = cl->customdata.pNext; pList; pList = pList->pNext ) |
|
{ |
|
if( !memcmp( pList->resource.rgucMD5_hash, resource->rgucMD5_hash, 16 )) |
|
{ |
|
bFound = true; |
|
break; |
|
} |
|
} |
|
|
|
if( !bFound ) |
|
{ |
|
if( !COM_CreateCustomization( &cl->customdata, resource, -1, FCUST_FROMHPAK|FCUST_WIPEDATA|FCUST_IGNOREINIT, NULL, NULL )) |
|
bError = true; |
|
} |
|
else |
|
{ |
|
Con_DPrintf( "Duplicate resource received and ignored.\n" ); |
|
} |
|
|
|
if( bError ) Con_Printf( S_ERROR "parsing custom decal from %s\n", cl->name ); |
|
} |
|
|
|
/* |
|
================= |
|
SV_ReadPackets |
|
================= |
|
*/ |
|
void SV_ReadPackets( void ) |
|
{ |
|
sv_client_t *cl; |
|
int i, qport; |
|
size_t curSize; |
|
|
|
while( NET_GetPacket( NS_SERVER, &net_from, net_message_buffer, &curSize )) |
|
{ |
|
MSG_Init( &net_message, "ClientPacket", net_message_buffer, curSize ); |
|
|
|
// check for connectionless packet (0xffffffff) first |
|
if( MSG_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 ) |
|
{ |
|
if( !svs.initialized ) |
|
{ |
|
char *args; |
|
const char *c; |
|
|
|
MSG_Clear( &net_message ); |
|
MSG_ReadLong( &net_message );// skip the -1 marker |
|
|
|
args = MSG_ReadStringLine( &net_message ); |
|
Cmd_TokenizeString( args ); |
|
c = Cmd_Argv( 0 ); |
|
|
|
if( !Q_strcmp( c, "rcon" )) |
|
SV_RemoteCommand( net_from, &net_message ); |
|
} |
|
else SV_ConnectionlessPacket( net_from, &net_message ); |
|
|
|
continue; |
|
} |
|
|
|
// read the qport out of the message so we can fix up |
|
// stupid address translating routers |
|
MSG_Clear( &net_message ); |
|
MSG_ReadLong( &net_message ); // sequence number |
|
MSG_ReadLong( &net_message ); // sequence number |
|
qport = (int)MSG_ReadShort( &net_message ) & 0xffff; |
|
|
|
// check for packets from connected clients |
|
for( i = 0, sv.current_client = svs.clients; i < svs.maxclients; i++, sv.current_client++ ) |
|
{ |
|
cl = sv.current_client; |
|
|
|
if( cl->state == cs_free || FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
continue; |
|
|
|
if( !NET_CompareBaseAdr( net_from, cl->netchan.remote_address )) |
|
continue; |
|
|
|
if( cl->netchan.qport != qport ) |
|
continue; |
|
|
|
if( cl->netchan.remote_address.port != net_from.port ) |
|
cl->netchan.remote_address.port = net_from.port; |
|
|
|
if( Netchan_Process( &cl->netchan, &net_message )) |
|
{ |
|
if(( svs.maxclients == 1 && !host_limitlocal->value ) || ( cl->state != cs_spawned )) |
|
SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame |
|
|
|
// this is a valid, sequenced packet, so process it |
|
if( cl->frames != NULL && cl->state != cs_zombie ) |
|
{ |
|
SV_ExecuteClientMessage( cl, &net_message ); |
|
svgame.globals->frametime = sv.frametime; |
|
svgame.globals->time = sv.time; |
|
} |
|
} |
|
|
|
// fragmentation/reassembly sending takes priority over all game messages, want this in the future? |
|
if( Netchan_IncomingReady( &cl->netchan )) |
|
{ |
|
if( Netchan_CopyNormalFragments( &cl->netchan, &net_message, &curSize )) |
|
{ |
|
MSG_Init( &net_message, "ClientPacket", net_message_buffer, curSize ); |
|
|
|
if(( svs.maxclients == 1 && !host_limitlocal->value ) || ( cl->state != cs_spawned )) |
|
SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame |
|
|
|
// this is a valid, sequenced packet, so process it |
|
if( cl->frames != NULL && cl->state != cs_zombie ) |
|
{ |
|
SV_ExecuteClientMessage( cl, &net_message ); |
|
svgame.globals->frametime = sv.frametime; |
|
svgame.globals->time = sv.time; |
|
} |
|
} |
|
|
|
if( Netchan_CopyFileFragments( &cl->netchan, &net_message )) |
|
{ |
|
SV_ProcessFile( cl, cl->netchan.incomingfilename ); |
|
} |
|
} |
|
break; |
|
} |
|
|
|
if( i != svs.maxclients ) |
|
continue; |
|
} |
|
|
|
sv.current_client = NULL; |
|
} |
|
|
|
/* |
|
================== |
|
SV_CheckTimeouts |
|
|
|
If a packet has not been received from a client for timeout->value |
|
seconds, drop the conneciton. Server frames are used instead of |
|
realtime to avoid dropping the local client while debugging. |
|
|
|
When a client is normally dropped, the sv_client_t goes into a zombie state |
|
for a few seconds to make sure any final reliable message gets resent |
|
if necessary |
|
================== |
|
*/ |
|
void SV_CheckTimeouts( void ) |
|
{ |
|
sv_client_t *cl; |
|
double droppoint; |
|
int i, numclients = 0; |
|
|
|
droppoint = host.realtime - timeout->value; |
|
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) |
|
{ |
|
if( cl->state >= cs_connected ) |
|
{ |
|
if( cl->edict && !FBitSet( cl->edict->v.flags, FL_SPECTATOR|FL_FAKECLIENT )) |
|
numclients++; |
|
} |
|
|
|
// fake clients do not timeout |
|
if( FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
continue; |
|
|
|
// FIXME: get rid of the zombie state |
|
if( cl->state == cs_zombie ) |
|
{ |
|
cl->state = cs_free; // can now be reused |
|
continue; |
|
} |
|
|
|
if(( cl->state == cs_connected || cl->state == cs_spawned ) && cl->netchan.last_received < droppoint ) |
|
{ |
|
if( !NET_IsLocalAddress( cl->netchan.remote_address )) |
|
{ |
|
SV_BroadcastPrintf( NULL, "%s timed out\n", cl->name ); |
|
SV_DropClient( cl, false ); |
|
cl->state = cs_free; // don't bother with zombie state |
|
} |
|
} |
|
} |
|
|
|
if( svs.maxclients > 1 && sv.paused && !numclients ) |
|
{ |
|
// nobody left, unpause the server |
|
SV_TogglePause( "Pause released since no players are left." ); |
|
} |
|
} |
|
|
|
/* |
|
================ |
|
SV_PrepWorldFrame |
|
|
|
This has to be done before the world logic, because |
|
player processing happens outside RunWorldFrame |
|
================ |
|
*/ |
|
void SV_PrepWorldFrame( void ) |
|
{ |
|
edict_t *ent; |
|
int i; |
|
|
|
for( i = 1; i < svgame.numEntities; i++ ) |
|
{ |
|
ent = EDICT_NUM( i ); |
|
if( ent->free ) continue; |
|
|
|
ClearBits( ent->v.effects, EF_MUZZLEFLASH|EF_NOINTERP ); |
|
} |
|
|
|
if( svgame.physFuncs.pfnPrepWorldFrame != NULL ) |
|
svgame.physFuncs.pfnPrepWorldFrame(); |
|
} |
|
|
|
/* |
|
================= |
|
SV_IsSimulating |
|
================= |
|
*/ |
|
qboolean SV_IsSimulating( void ) |
|
{ |
|
if( sv.background && SV_Active() && CL_Active()) |
|
{ |
|
if( CL_IsInConsole( )) |
|
return false; |
|
return true; // force simulating for background map |
|
} |
|
|
|
if( Host_IsDedicated() ) |
|
return true; // always active for dedicated servers |
|
|
|
if( !SV_HasActivePlayers( )) |
|
return false; |
|
|
|
// allow to freeze everything in singleplayer |
|
if( svs.maxclients <= 1 && sv.playersonly ) |
|
return false; |
|
|
|
if( !sv.paused && CL_IsInGame( )) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
/* |
|
================= |
|
SV_RunGameFrame |
|
================= |
|
*/ |
|
qboolean SV_RunGameFrame( void ) |
|
{ |
|
sv.simulating = SV_IsSimulating(); |
|
|
|
if( !sv.simulating ) |
|
return true; |
|
|
|
if( sv_fps.value != 0.0f ) |
|
{ |
|
double fps = (1.0 / (double)( sv_fps.value - 0.01 )); // FP issues |
|
int numFrames = 0; |
|
|
|
while( sv.time_residual >= fps ) |
|
{ |
|
sv.frametime = fps; |
|
|
|
SV_Physics(); |
|
|
|
sv.time_residual -= fps; |
|
sv.time += fps; |
|
numFrames++; |
|
} |
|
|
|
return (numFrames != 0); |
|
} |
|
else |
|
{ |
|
SV_Physics(); |
|
sv.time += sv.frametime; |
|
return true; |
|
} |
|
} |
|
|
|
/* |
|
================== |
|
Host_ServerFrame |
|
|
|
================== |
|
*/ |
|
void Host_ServerFrame( void ) |
|
{ |
|
// if server is not active, do nothing |
|
if( !svs.initialized ) return; |
|
|
|
if( sv_fps.value != 0.0f && ( sv.simulating || sv.state != ss_active )) |
|
sv.time_residual += host.frametime; |
|
|
|
if( sv_fps.value == 0.0f ) |
|
sv.frametime = host.frametime; |
|
svgame.globals->frametime = sv.frametime; |
|
|
|
// check clients timewindow |
|
SV_CheckCmdTimes (); |
|
|
|
// read packets from clients |
|
SV_ReadPackets (); |
|
|
|
// refresh physic movevars on the client side |
|
SV_UpdateMovevars ( false ); |
|
|
|
// request missing resources for clients |
|
SV_RequestMissingResources(); |
|
|
|
// check timeouts |
|
SV_CheckTimeouts (); |
|
|
|
// let everything in the world think and move |
|
if( !SV_RunGameFrame ()) return; |
|
|
|
// send messages back to the clients that had packets read this frame |
|
SV_SendClientMessages (); |
|
|
|
// clear edict flags for next frame |
|
SV_PrepWorldFrame (); |
|
|
|
// send a heartbeat to the master if needed |
|
Master_Heartbeat (); |
|
} |
|
|
|
/* |
|
================== |
|
Host_SetServerState |
|
================== |
|
*/ |
|
void Host_SetServerState( int state ) |
|
{ |
|
Cvar_FullSet( "host_serverstate", va( "%i", state ), FCVAR_READ_ONLY ); |
|
sv.state = state; |
|
} |
|
|
|
//============================================================================ |
|
|
|
/* |
|
================= |
|
Master_Add |
|
================= |
|
*/ |
|
void Master_Add( void ) |
|
{ |
|
netadr_t adr; |
|
|
|
NET_Config( true ); // allow remote |
|
|
|
if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) |
|
Con_Printf( "can't resolve adr: %s\n", MASTERSERVER_ADR ); |
|
else NET_SendPacket( NS_SERVER, 2, "q\xFF", adr ); |
|
} |
|
|
|
/* |
|
================ |
|
Master_Heartbeat |
|
|
|
Send a message to the master every few minutes to |
|
let it know we are alive, and log information |
|
================ |
|
*/ |
|
void Master_Heartbeat( void ) |
|
{ |
|
if( !public_server->value || svs.maxclients == 1 ) |
|
return; // only public servers send heartbeats |
|
|
|
// check for time wraparound |
|
if( svs.last_heartbeat > host.realtime ) |
|
svs.last_heartbeat = host.realtime; |
|
|
|
if(( host.realtime - svs.last_heartbeat ) < HEARTBEAT_SECONDS ) |
|
return; // not time to send yet |
|
|
|
svs.last_heartbeat = host.realtime; |
|
|
|
Master_Add(); |
|
} |
|
|
|
/* |
|
================= |
|
Master_Shutdown |
|
|
|
Informs all masters that this server is going down |
|
================= |
|
*/ |
|
void Master_Shutdown( void ) |
|
{ |
|
netadr_t adr; |
|
|
|
NET_Config( true ); // allow remote |
|
|
|
if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) |
|
Con_Printf( "can't resolve addr: %s\n", MASTERSERVER_ADR ); |
|
else NET_SendPacket( NS_SERVER, 2, "\x62\x0A", adr ); |
|
} |
|
|
|
/* |
|
================= |
|
SV_AddToMaster |
|
|
|
A server info answer to master server. |
|
Master will validate challenge and this server to public list |
|
================= |
|
*/ |
|
void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) |
|
{ |
|
uint challenge; |
|
char s[MAX_INFO_STRING] = "0\n"; // skip 2 bytes of header |
|
int clients = 0, bots = 0, index; |
|
int len = sizeof( s ); |
|
|
|
if( svs.clients ) |
|
{ |
|
for( index = 0; index < svs.maxclients; index++ ) |
|
{ |
|
if( svs.clients[index].state >= cs_connected ) |
|
{ |
|
if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) |
|
bots++; |
|
else clients++; |
|
} |
|
} |
|
} |
|
|
|
challenge = MSG_ReadUBitLong( msg, sizeof( uint ) << 3 ); |
|
|
|
Info_SetValueForKey( s, "protocol", va( "%d", PROTOCOL_VERSION ), len ); // protocol version |
|
Info_SetValueForKey( s, "challenge", va( "%u", challenge ), len ); // challenge number |
|
Info_SetValueForKey( s, "players", va( "%d", clients ), len ); // current player number, without bots |
|
Info_SetValueForKey( s, "max", va( "%d", svs.maxclients ), len ); // max_players |
|
Info_SetValueForKey( s, "bots", va( "%d", bots ), len ); // bot count |
|
Info_SetValueForKey( s, "gamedir", GI->gamefolder, len ); // gamedir |
|
Info_SetValueForKey( s, "map", sv.name, len ); // current map |
|
Info_SetValueForKey( s, "type", (Host_IsDedicated()) ? "d" : "l", len ); // dedicated or local |
|
Info_SetValueForKey( s, "password", "0", len ); // is password set |
|
Info_SetValueForKey( s, "os", "w", len ); // Windows |
|
Info_SetValueForKey( s, "secure", "0", len ); // server anti-cheat |
|
Info_SetValueForKey( s, "lan", "0", len ); // LAN servers doesn't send info to master |
|
Info_SetValueForKey( s, "version", va( "%s", XASH_VERSION ), len ); // server region. 255 -- all regions |
|
Info_SetValueForKey( s, "region", "255", len ); // server region. 255 -- all regions |
|
Info_SetValueForKey( s, "product", GI->gamefolder, len ); // product? Where is the difference with gamedir? |
|
|
|
NET_SendPacket( NS_SERVER, Q_strlen( s ), s, from ); |
|
} |
|
|
|
/* |
|
==================== |
|
SV_ProcessUserAgent |
|
|
|
send error message and return false on wrong input devices |
|
==================== |
|
*/ |
|
qboolean SV_ProcessUserAgent( netadr_t from, const char *useragent ) |
|
{ |
|
const char *input_devices_str = Info_ValueForKey( useragent, "d" ); |
|
const char *id = Info_ValueForKey( useragent, "i" ); |
|
|
|
if( !sv_allow_noinputdevices->value && ( !input_devices_str || !input_devices_str[0] ) ) |
|
{ |
|
SV_RejectConnection( from, "This server does not allow\nconnect without input devices list.\nPlease update your engine.\n" ); |
|
return false; |
|
} |
|
|
|
if( input_devices_str ) |
|
{ |
|
int input_devices = Q_atoi( input_devices_str ); |
|
|
|
if( !sv_allow_touch->value && ( input_devices & INPUT_DEVICE_TOUCH ) ) |
|
{ |
|
SV_RejectConnection( from, "This server does not allow touch\nDisable it (touch_enable 0)\nto play on this server\n" ); |
|
return false; |
|
} |
|
if( !sv_allow_mouse->value && ( input_devices & INPUT_DEVICE_MOUSE) ) |
|
{ |
|
SV_RejectConnection( from, "This server does not allow mouse\nDisable it(m_ignore 1)\nto play on this server\n" ); |
|
return false; |
|
} |
|
if( !sv_allow_joystick->value && ( input_devices & INPUT_DEVICE_JOYSTICK) ) |
|
{ |
|
SV_RejectConnection( from, "This server does not allow joystick\nDisable it(joy_enable 0)\nto play on this server\n" ); |
|
return false; |
|
} |
|
if( !sv_allow_vr->value && ( input_devices & INPUT_DEVICE_VR) ) |
|
{ |
|
SV_RejectConnection( from, "This server does not allow VR\n" ); |
|
return false; |
|
} |
|
} |
|
|
|
if( id ) |
|
{ |
|
qboolean banned = SV_CheckID( id ); |
|
|
|
if( banned ) |
|
{ |
|
SV_RejectConnection( from, "You are banned!\n" ); |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//============================================================================ |
|
|
|
/* |
|
=============== |
|
SV_Init |
|
|
|
Only called at startup, not for each game |
|
=============== |
|
*/ |
|
void SV_Init( void ) |
|
{ |
|
string versionString; |
|
|
|
SV_InitHostCommands(); |
|
|
|
Cvar_Get ("protocol", va( "%i", PROTOCOL_VERSION ), FCVAR_READ_ONLY, "displays server protocol version" ); |
|
Cvar_Get ("suitvolume", "0.25", FCVAR_ARCHIVE, "HEV suit volume" ); |
|
Cvar_Get ("sv_background", "0", FCVAR_READ_ONLY, "indicate what background map is running" ); |
|
Cvar_Get( "gamedir", GI->gamefolder, FCVAR_SERVER, "game folder" ); |
|
Cvar_Get( "sv_alltalk", "1", 0, "allow to talking for all players (legacy, unused)" ); |
|
Cvar_Get( "sv_allow_PhysX", "1", FCVAR_ARCHIVE, "allow XashXT to usage PhysX engine" ); // XashXT cvar |
|
Cvar_Get( "sv_precache_meshes", "1", FCVAR_ARCHIVE, "cache SOLID_CUSTOM meshes before level loading" ); // Paranoia 2 cvar |
|
Cvar_Get ("mapcyclefile", "mapcycle.txt", 0, "name of config file for map changing rules" ); |
|
Cvar_Get ("servercfgfile","server.cfg", 0, "name of dedicated server configuration file" ); |
|
Cvar_Get ("lservercfgfile","listenserver.cfg", 0, "name of listen server configuration file" ); |
|
Cvar_Get ("logsdir","logs", 0, "default folder to write server logs" ); |
|
|
|
Cvar_RegisterVariable (&sv_zmax); |
|
Cvar_RegisterVariable (&sv_wateramp); |
|
Cvar_RegisterVariable (&sv_skycolor_r); |
|
Cvar_RegisterVariable (&sv_skycolor_g); |
|
Cvar_RegisterVariable (&sv_skycolor_b); |
|
Cvar_RegisterVariable (&sv_skyvec_x); |
|
Cvar_RegisterVariable (&sv_skyvec_y); |
|
Cvar_RegisterVariable (&sv_skyvec_z); |
|
Cvar_RegisterVariable (&sv_skyname); |
|
Cvar_RegisterVariable (&sv_footsteps); |
|
Cvar_RegisterVariable (&sv_wateralpha); |
|
Cvar_RegisterVariable (&sv_cheats); |
|
Cvar_RegisterVariable (&sv_airmove); |
|
Cvar_RegisterVariable (&sv_fps); |
|
Cvar_RegisterVariable (&showtriggers); |
|
Cvar_RegisterVariable (&sv_aim); |
|
Cvar_RegisterVariable (&motdfile); |
|
Cvar_RegisterVariable (&deathmatch); |
|
Cvar_RegisterVariable (&coop); |
|
Cvar_RegisterVariable (&teamplay); |
|
Cvar_RegisterVariable (&skill); |
|
Cvar_RegisterVariable (&temp1); |
|
|
|
Cvar_RegisterVariable (&rcon_password); |
|
Cvar_RegisterVariable (&sv_stepsize); |
|
Cvar_RegisterVariable (&sv_newunit); |
|
Cvar_RegisterVariable (&hostname); |
|
timeout = Cvar_Get( "timeout", "125", FCVAR_SERVER, "connection timeout" ); |
|
sv_pausable = Cvar_Get( "pausable", "1", FCVAR_SERVER, "allow players to pause or not" ); |
|
sv_validate_changelevel = Cvar_Get( "sv_validate_changelevel", "1", FCVAR_ARCHIVE, "test change level for level-designer errors" ); |
|
Cvar_RegisterVariable (&sv_clienttrace); |
|
Cvar_RegisterVariable (&sv_bounce); |
|
Cvar_RegisterVariable (&sv_spectatormaxspeed); |
|
Cvar_RegisterVariable (&sv_waterfriction); |
|
Cvar_RegisterVariable (&sv_wateraccelerate); |
|
Cvar_RegisterVariable (&sv_rollangle); |
|
Cvar_RegisterVariable (&sv_rollspeed); |
|
Cvar_RegisterVariable (&sv_airaccelerate); |
|
Cvar_RegisterVariable (&sv_maxvelocity); |
|
Cvar_RegisterVariable (&sv_gravity); |
|
Cvar_RegisterVariable (&sv_maxspeed); |
|
Cvar_RegisterVariable (&sv_accelerate); |
|
Cvar_RegisterVariable (&sv_friction); |
|
Cvar_RegisterVariable (&sv_edgefriction); |
|
Cvar_RegisterVariable (&sv_stopspeed); |
|
sv_maxclients = Cvar_Get( "maxplayers", "1", FCVAR_LATCH, "server max capacity" ); |
|
sv_check_errors = Cvar_Get( "sv_check_errors", "0", FCVAR_ARCHIVE, "check edicts for errors" ); |
|
public_server = Cvar_Get ("public", "0", 0, "change server type from private to public" ); |
|
sv_lighting_modulate = Cvar_Get( "r_lighting_modulate", "0.6", FCVAR_ARCHIVE, "lightstyles modulate scale" ); |
|
sv_reconnect_limit = Cvar_Get ("sv_reconnect_limit", "3", FCVAR_ARCHIVE, "max reconnect attempts" ); |
|
Cvar_RegisterVariable (&sv_failuretime ); |
|
Cvar_RegisterVariable (&sv_unlag); |
|
Cvar_RegisterVariable (&sv_maxunlag); |
|
Cvar_RegisterVariable (&sv_unlagpush); |
|
Cvar_RegisterVariable (&sv_unlagsamples); |
|
Cvar_RegisterVariable (&sv_allow_upload); |
|
Cvar_RegisterVariable (&sv_allow_download); |
|
Cvar_RegisterVariable (&sv_send_logos); |
|
Cvar_RegisterVariable (&sv_send_resources); |
|
Cvar_RegisterVariable (&sv_uploadmax); |
|
Cvar_RegisterVariable (&sv_version); |
|
Cvar_RegisterVariable (&sv_instancedbaseline); |
|
Cvar_RegisterVariable (&sv_consistency); |
|
Cvar_RegisterVariable (&sv_downloadurl); |
|
sv_novis = Cvar_Get( "sv_novis", "0", 0, "force to ignore server visibility" ); |
|
sv_hostmap = Cvar_Get( "hostmap", GI->startmap, 0, "keep name of last entered map" ); |
|
Cvar_RegisterVariable (&sv_password); |
|
Cvar_RegisterVariable (&sv_lan); |
|
Cvar_RegisterVariable (&violence_ablood); |
|
Cvar_RegisterVariable (&violence_hblood); |
|
Cvar_RegisterVariable (&violence_agibs); |
|
Cvar_RegisterVariable (&violence_hgibs); |
|
Cvar_RegisterVariable (&mp_logecho); |
|
Cvar_RegisterVariable (&mp_logfile); |
|
Cvar_RegisterVariable (&sv_background_freeze); |
|
|
|
sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" ); |
|
sv_allow_mouse = Cvar_Get( "sv_allow_mouse", "1", FCVAR_ARCHIVE, "allow connect with mouse" ); |
|
sv_allow_touch = Cvar_Get( "sv_allow_touch", "1", FCVAR_ARCHIVE, "allow connect with touch controls" ); |
|
sv_allow_vr = Cvar_Get( "sv_allow_vr", "1", FCVAR_ARCHIVE, "allow connect from vr version" ); |
|
sv_allow_noinputdevices = Cvar_Get( "sv_allow_noinputdevices", "1", FCVAR_ARCHIVE, "allow connect from old versions without useragent" ); |
|
|
|
// when we in developer-mode automatically turn cheats on |
|
if( host_developer.value ) Cvar_SetValue( "sv_cheats", 1.0f ); |
|
|
|
MSG_Init( &net_message, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); |
|
|
|
Q_snprintf( versionString, sizeof( versionString ), "%s: %s-%s(%s-%s),%i,%i", |
|
XASH_ENGINE_NAME, XASH_VERSION, Q_buildcommit(), Q_buildos(), Q_buildarch(), PROTOCOL_VERSION, Q_buildnum() ); |
|
|
|
Cvar_FullSet( "sv_version", versionString, FCVAR_READ_ONLY ); |
|
|
|
SV_InitFilter(); |
|
SV_ClearGameState (); // delete all temporary *.hl files |
|
} |
|
|
|
/* |
|
================== |
|
SV_FinalMessage |
|
|
|
Used by SV_Shutdown to send a final message to all |
|
connected clients before the server goes down. The messages are sent immediately, |
|
not just stuck on the outgoing message list, because the server is going |
|
to totally exit after returning from this function. |
|
================== |
|
*/ |
|
void SV_FinalMessage( const char *message, qboolean reconnect ) |
|
{ |
|
byte msg_buf[1024]; |
|
sv_client_t *cl; |
|
sizebuf_t msg; |
|
int i; |
|
|
|
MSG_Init( &msg, "FinalMessage", msg_buf, sizeof( msg_buf )); |
|
|
|
if( COM_CheckString( message )) |
|
{ |
|
MSG_BeginServerCmd( &msg, svc_print ); |
|
MSG_WriteString( &msg, message ); |
|
} |
|
|
|
if( reconnect ) |
|
{ |
|
if( svs.maxclients <= 1 ) |
|
{ |
|
MSG_BeginServerCmd( &msg, svc_changing ); |
|
MSG_WriteOneBit( &msg, GameState->loadGame ); |
|
} |
|
else SV_BuildReconnect( &msg ); |
|
} |
|
else |
|
{ |
|
MSG_BeginServerCmd( &msg, svc_disconnect ); |
|
} |
|
|
|
// send it twice |
|
// stagger the packets to crutch operating system limited buffers |
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) |
|
if( cl->state >= cs_connected && !FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
Netchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg )); |
|
|
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) |
|
if( cl->state >= cs_connected && !FBitSet( cl->flags, FCL_FAKECLIENT )) |
|
Netchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg )); |
|
} |
|
|
|
/* |
|
================ |
|
SV_FreeClients |
|
|
|
release server clients |
|
================ |
|
*/ |
|
void SV_FreeClients( void ) |
|
{ |
|
if( svs.maxclients != 0 ) |
|
{ |
|
// free server static data |
|
if( svs.clients ) |
|
{ |
|
Z_Free( svs.clients ); |
|
svs.clients = NULL; |
|
} |
|
|
|
if( svs.packet_entities ) |
|
{ |
|
Z_Free( svs.packet_entities ); |
|
svs.packet_entities = NULL; |
|
svs.num_client_entities = 0; |
|
svs.next_client_entities = 0; |
|
} |
|
} |
|
} |
|
|
|
/* |
|
================ |
|
SV_Shutdown |
|
|
|
Called when each game quits, |
|
before Sys_Quit or Sys_Error |
|
================ |
|
*/ |
|
void SV_Shutdown( const char *finalmsg ) |
|
{ |
|
// already freed |
|
if( !SV_Initialized( )) |
|
{ |
|
// drop the client if want to load a new map |
|
if( CL_IsPlaybackDemo( )) |
|
CL_Drop(); |
|
return; |
|
} |
|
|
|
if( COM_CheckString( finalmsg )) |
|
Con_Printf( "%s", finalmsg ); |
|
|
|
// rcon will be disconnected |
|
SV_EndRedirect(); |
|
|
|
if( svs.clients ) |
|
SV_FinalMessage( finalmsg, false ); |
|
|
|
if( public_server->value && svs.maxclients != 1 ) |
|
Master_Shutdown(); |
|
|
|
NET_Config( false ); |
|
SV_UnloadProgs (); |
|
CL_Drop(); |
|
|
|
// free current level |
|
memset( &sv, 0, sizeof( sv )); |
|
|
|
SV_FreeClients(); |
|
svs.maxclients = 0; |
|
|
|
// release all models |
|
Mod_FreeAll(); |
|
|
|
HPAK_FlushHostQueue(); |
|
Log_Printf( "Server shutdown\n" ); |
|
Log_Close(); |
|
|
|
svs.initialized = false; |
|
}
|
|
|