mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-25 11:19:59 +01:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
1082 lines
35 KiB
C
1082 lines
35 KiB
C
/*
|
|
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.01f )); // 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_READ_ONLY, "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;
|
|
}
|