This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/server/sv_main.c

1063 lines
26 KiB
C
Raw Normal View History

2007-06-21 22:00:00 +02:00
/*
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 "engine.h"
#include "server.h"
netadr_t master_adr[MAX_MASTERS]; // address of group servers
client_t *sv_client; // current client
cvar_t *sv_paused;
cvar_t *sv_timedemo;
cvar_t *sv_enforcetime;
cvar_t *timeout; // seconds without any message
cvar_t *zombietime; // seconds to sink messages after disconnect
cvar_t *rcon_password; // password for remote server commands
cvar_t *allow_download;
cvar_t *allow_download_players;
cvar_t *allow_download_models;
cvar_t *allow_download_sounds;
cvar_t *allow_download_maps;
2007-09-02 22:00:00 +02:00
cvar_t *sv_airaccelerate;
cvar_t *sv_maxvelocity;
cvar_t *sv_gravity;
2007-06-21 22:00:00 +02:00
cvar_t *sv_noreload; // don't reload level state when reentering
cvar_t *maxclients; // FIXME: rename sv_maxclients
cvar_t *sv_showclamp;
cvar_t *hostname;
2007-09-10 22:00:00 +02:00
cvar_t *public_server; // should heartbeats be sent
2007-06-21 22:00:00 +02:00
2007-09-10 22:00:00 +02:00
cvar_t *sv_reconnect_limit;// minimum seconds between connect messages
2007-06-21 22:00:00 +02:00
void Master_Shutdown (void);
2007-09-10 22:00:00 +02:00
static int rd_target;
static char *rd_buffer;
static int rd_buffersize;
static void (*rd_flush)(int target, char *buffer);
2007-06-21 22:00:00 +02:00
//============================================================================
2007-09-10 22:00:00 +02:00
void SVC_BeginRedirect (int target, char *buffer, int buffersize, void (*flush))
{
if (!target || !buffer || !buffersize || !flush) return;
host.rd.target = target;
host.rd.buffer = buffer;
host.rd.buffersize = buffersize;
host.rd.flush = flush;
*host.rd.buffer = 0;
}
void SVC_EndRedirect (void)
{
host.rd.flush(rd_target, rd_buffer);
host.rd.target = 0;
host.rd.buffer = NULL;
host.rd.buffersize = 0;
host.rd.flush = NULL;
}
2007-06-21 22:00:00 +02:00
/*
=====================
SV_DropClient
Called when the player is totally leaving the server, either willingly
or unwillingly. This is NOT called if the entire server is quiting
or crashing.
=====================
*/
void SV_DropClient (client_t *drop)
{
// add the disconnect
MSG_WriteByte (&drop->netchan.message, svc_disconnect);
if (drop->state == cs_spawned)
{
// call the prog function for removing a client
// this will remove the body, among other things
2007-09-06 22:00:00 +02:00
SV_ClientDisconnect(drop->edict);
2007-06-21 22:00:00 +02:00
}
if (drop->download)
{
drop->download = NULL;
}
drop->state = cs_zombie; // become free in a few seconds
drop->name[0] = 0;
}
/*
==============================================================================
CONNECTIONLESS COMMANDS
==============================================================================
*/
/*
===============
SV_StatusString
Builds the string that is sent as heartbeats and status replies
===============
*/
2007-09-10 22:00:00 +02:00
char *SV_StatusString (void)
2007-06-21 22:00:00 +02:00
{
char player[1024];
static char status[MAX_MSGLEN - 16];
int i;
client_t *cl;
int statusLength;
int playerLength;
strcpy (status, Cvar_Serverinfo());
strcat (status, "\n");
statusLength = strlen(status);
2007-09-06 22:00:00 +02:00
for (i=0 ; i<maxclients->value ; i++)
2007-06-21 22:00:00 +02:00
{
cl = &svs.clients[i];
if (cl->state == cs_connected || cl->state == cs_spawned )
{
2007-09-16 22:00:00 +02:00
sprintf (player, "%i %i \"%s\"\n", cl->edict->priv.sv->client->ps.stats[STAT_FRAGS], cl->ping, cl->name);
2007-06-21 22:00:00 +02:00
playerLength = strlen(player);
if (statusLength + playerLength >= sizeof(status) )
break; // can't hold any more
strcpy (status + statusLength, player);
statusLength += playerLength;
}
}
return status;
}
/*
================
SVC_Status
Responds with all the info that qplug or qspy can see
================
*/
void SVC_Status (void)
{
Netchan_OutOfBandPrint (NS_SERVER, net_from, "print\n%s", SV_StatusString());
}
/*
================
SVC_Ack
================
*/
void SVC_Ack (void)
{
2007-07-28 22:00:00 +02:00
Msg ("Ping acknowledge from %s\n", NET_AdrToString(net_from));
2007-06-21 22:00:00 +02:00
}
/*
================
SVC_Info
Responds with short info for broadcast scans
The second parameter should be the current protocol version number.
================
*/
void SVC_Info (void)
{
char string[64];
int i, count;
int version;
2007-09-06 22:00:00 +02:00
if (maxclients->value == 1)
2007-06-21 22:00:00 +02:00
return; // ignore in single player
version = atoi (Cmd_Argv(1));
if (version != PROTOCOL_VERSION)
2007-08-11 22:00:00 +02:00
sprintf (string, "%s: wrong version\n", hostname->string, sizeof(string));
2007-06-21 22:00:00 +02:00
else
{
count = 0;
2007-09-06 22:00:00 +02:00
for (i=0 ; i<maxclients->value ; i++)
2007-06-21 22:00:00 +02:00
if (svs.clients[i].state >= cs_connected)
count++;
2007-09-06 22:00:00 +02:00
sprintf (string, "%16s %8s %2i/%2i\n", hostname->string, sv.name, count, (int)maxclients->value);
2007-06-21 22:00:00 +02:00
}
Netchan_OutOfBandPrint (NS_SERVER, net_from, "info\n%s", string);
}
/*
================
SVC_Ping
Just responds with an acknowledgement
================
*/
void SVC_Ping (void)
{
Netchan_OutOfBandPrint (NS_SERVER, net_from, "ack");
}
/*
=================
SVC_GetChallenge
Returns a challenge number that can be used
in a subsequent client_connect command.
We do this to prevent denial of service attacks that
flood the server with invalid connection IPs. With a
challenge, they must give a valid IP address.
=================
*/
void SVC_GetChallenge (void)
{
int i;
int oldest;
2007-09-09 22:00:00 +02:00
float oldestTime;
2007-06-21 22:00:00 +02:00
oldest = 0;
2007-09-09 22:00:00 +02:00
oldestTime = 2147483647.0f;
2007-06-21 22:00:00 +02:00
// see if we already have a challenge for this ip
for (i = 0 ; i < MAX_CHALLENGES ; i++)
{
if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
break;
if (svs.challenges[i].time < oldestTime)
{
oldestTime = svs.challenges[i].time;
oldest = i;
}
}
if (i == MAX_CHALLENGES)
{
// overwrite the oldest
svs.challenges[oldest].challenge = rand() & 0x7fff;
svs.challenges[oldest].adr = net_from;
2007-09-09 22:00:00 +02:00
svs.challenges[oldest].time = svs.realtime;
2007-06-21 22:00:00 +02:00
i = oldest;
}
// send it back
Netchan_OutOfBandPrint (NS_SERVER, net_from, "challenge %i", svs.challenges[i].challenge);
}
/*
==================
SVC_DirectConnect
A connection request that did not come from the master
==================
*/
2007-09-17 22:00:00 +02:00
void SVC_DirectConnect( void )
2007-06-21 22:00:00 +02:00
{
char userinfo[MAX_INFO_STRING];
2007-09-09 22:00:00 +02:00
netadr_t adr;
int i;
client_t *cl, *newcl;
client_t temp;
2007-09-06 22:00:00 +02:00
edict_t *ent;
2007-09-09 22:00:00 +02:00
int edictnum;
int version;
int qport;
int challenge;
2007-06-21 22:00:00 +02:00
adr = net_from;
2007-09-03 22:00:00 +02:00
MsgDev (D_INFO, "SVC_DirectConnect()\n");
2007-06-21 22:00:00 +02:00
version = atoi(Cmd_Argv(1));
if (version != PROTOCOL_VERSION)
{
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nServer is version %4.2f.\n", VERSION);
2007-08-17 22:00:00 +02:00
MsgWarn ("SVC_DirectConnect: rejected connect from version %i\n", version);
2007-06-21 22:00:00 +02:00
return;
}
qport = atoi(Cmd_Argv(2));
challenge = atoi(Cmd_Argv(3));
strncpy (userinfo, Cmd_Argv(4), sizeof(userinfo)-1);
userinfo[sizeof(userinfo) - 1] = 0;
// force the IP key/value pair so the game can filter based on ip
Info_SetValueForKey (userinfo, "ip", NET_AdrToString(net_from));
// attractloop servers are ONLY for local clients
if (sv.attractloop)
{
if (!NET_IsLocalAddress (adr))
{
2007-07-28 22:00:00 +02:00
Msg ("Remote connect in attract loop. Ignored.\n");
2007-06-21 22:00:00 +02:00
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nConnection refused.\n");
return;
}
}
// see if the challenge is valid
if (!NET_IsLocalAddress (adr))
{
for (i=0 ; i<MAX_CHALLENGES ; i++)
{
if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
{
if (challenge == svs.challenges[i].challenge)
2007-09-17 22:00:00 +02:00
break; // good
2007-06-21 22:00:00 +02:00
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nBad challenge->\n");
return;
}
}
if (i == MAX_CHALLENGES)
{
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nNo challenge for address.\n");
return;
}
}
newcl = &temp;
memset (newcl, 0, sizeof(client_t));
// if there is already a slot for this ip, reuse it
2007-09-09 22:00:00 +02:00
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
2007-06-21 22:00:00 +02:00
{
2007-09-17 22:00:00 +02:00
if (cl->state == cs_free) continue;
2007-09-09 22:00:00 +02:00
if (NET_CompareBaseAdr (adr, cl->netchan.remote_address) && ( cl->netchan.qport == qport || adr.port == cl->netchan.remote_address.port))
2007-06-21 22:00:00 +02:00
{
2007-09-09 22:00:00 +02:00
if (!NET_IsLocalAddress (adr) && (svs.realtime - cl->lastconnect) < sv_reconnect_limit->value)
2007-06-21 22:00:00 +02:00
{
2007-08-17 22:00:00 +02:00
MsgWarn("SVC_DirectConnect: %s:reconnect rejected : too soon\n", NET_AdrToString (adr));
2007-06-21 22:00:00 +02:00
return;
}
2007-07-28 22:00:00 +02:00
Msg ("%s:reconnect\n", NET_AdrToString (adr));
2007-06-21 22:00:00 +02:00
newcl = cl;
goto gotnewcl;
}
}
// find a client slot
newcl = NULL;
2007-09-06 22:00:00 +02:00
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
2007-06-21 22:00:00 +02:00
{
if (cl->state == cs_free)
{
newcl = cl;
break;
}
}
if (!newcl)
{
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nServer is full.\n");
2007-09-03 22:00:00 +02:00
MsgDev(D_INFO, "SVC_DirectConnect: Rejected a connection.\n");
2007-06-21 22:00:00 +02:00
return;
}
gotnewcl:
// build a new connection
// accept the new client
// this is the only place a client_t is ever initialized
*newcl = temp;
sv_client = newcl;
edictnum = (newcl - svs.clients) + 1;
2007-09-16 22:00:00 +02:00
ent = PRVM_EDICT_NUM( edictnum );
2007-06-21 22:00:00 +02:00
newcl->edict = ent;
newcl->challenge = challenge; // save challenge for checksumming
2007-09-06 22:00:00 +02:00
// get the game a chance to reject this connection or modify the userinfo
if (!(SV_ClientConnect(ent, userinfo)))
{
2007-06-21 22:00:00 +02:00
if (*Info_ValueForKey (userinfo, "rejmsg"))
2007-08-17 22:00:00 +02:00
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\n%s\nConnection refused.\n", Info_ValueForKey (userinfo, "rejmsg"));
else Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nConnection refused.\n" );
MsgWarn("SVC_DirectConnect: Game rejected a connection.\n");
2007-06-21 22:00:00 +02:00
return;
2007-09-06 22:00:00 +02:00
}
2007-06-21 22:00:00 +02:00
// parse some info from the info strings
strncpy (newcl->userinfo, userinfo, sizeof(newcl->userinfo)-1);
SV_UserinfoChanged (newcl);
// send the connect packet to the client
Netchan_OutOfBandPrint (NS_SERVER, adr, "client_connect");
Netchan_Setup (NS_SERVER, &newcl->netchan , adr, qport);
newcl->state = cs_connected;
SZ_Init (&newcl->datagram, newcl->datagram_buf, sizeof(newcl->datagram_buf) );
2007-10-19 22:00:00 +02:00
newcl->lastmessage = svs.realtime; // don't timeout
2007-09-09 22:00:00 +02:00
newcl->lastconnect = svs.realtime;
2007-06-21 22:00:00 +02:00
}
int Rcon_Validate (void)
{
if (!strlen (rcon_password->string))
return 0;
if (strcmp (Cmd_Argv(1), rcon_password->string) )
return 0;
return 1;
}
/*
===============
SVC_RemoteCommand
A client issued an rcon command.
Shift down the remaining args
Redirect all printfs
===============
*/
void SVC_RemoteCommand (void)
{
int i;
2007-09-10 22:00:00 +02:00
char remaining[1024];
2007-06-21 22:00:00 +02:00
i = Rcon_Validate ();
2007-09-10 22:00:00 +02:00
if (i == 0) MsgDev(D_INFO, "Bad rcon from %s:\n%s\n", NET_AdrToString (net_from), net_message.data + 4);
else MsgDev(D_INFO, "Rcon from %s:\n%s\n", NET_AdrToString (net_from), net_message.data + 4);
SVC_BeginRedirect (RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
2007-06-21 22:00:00 +02:00
2007-09-10 22:00:00 +02:00
if (!Rcon_Validate()) Msg("Bad rcon_password.\n");
2007-06-21 22:00:00 +02:00
else
{
remaining[0] = 0;
2007-09-10 22:00:00 +02:00
for (i = 2; i < Cmd_Argc(); i++)
2007-06-21 22:00:00 +02:00
{
strcat (remaining, Cmd_Argv(i) );
strcat (remaining, " ");
}
Cmd_ExecuteString (remaining);
}
2007-09-10 22:00:00 +02:00
SVC_EndRedirect ();
2007-06-21 22:00:00 +02:00
}
/*
=================
SV_ConnectionlessPacket
A connectionless packet has four leading 0xff
characters to distinguish it from a game channel.
Clients that are in the game can still send
connectionless packets.
=================
*/
void SV_ConnectionlessPacket (void)
{
char *s;
char *c;
MSG_BeginReading (&net_message);
2007-08-17 22:00:00 +02:00
MSG_ReadLong (&net_message);// skip the -1 marker
2007-06-21 22:00:00 +02:00
s = MSG_ReadStringLine (&net_message);
Cmd_TokenizeString (s, false);
c = Cmd_Argv(0);
2007-08-17 22:00:00 +02:00
MsgWarn("SV_ConnectionlessPacket: %s : %s\n", NET_AdrToString(net_from), c);
if (!strcmp(c, "ping")) SVC_Ping ();
else if (!strcmp(c, "ack")) SVC_Ack ();
else if (!strcmp(c,"status")) SVC_Status ();
else if (!strcmp(c,"info")) SVC_Info ();
else if (!strcmp(c,"getchallenge")) SVC_GetChallenge ();
else if (!strcmp(c,"connect")) SVC_DirectConnect ();
else if (!strcmp(c, "rcon")) SVC_RemoteCommand ();
else Msg ("bad connectionless packet from %s:\n%s\n", NET_AdrToString (net_from), s);
2007-06-21 22:00:00 +02:00
}
//============================================================================
/*
===================
SV_CalcPings
Updates the cl->ping variables
===================
*/
void SV_CalcPings (void)
{
int i, j;
client_t *cl;
int total, count;
2007-09-06 22:00:00 +02:00
for (i=0 ; i<maxclients->value ; i++)
2007-06-21 22:00:00 +02:00
{
cl = &svs.clients[i];
if (cl->state != cs_spawned )
continue;
#if 0
if (cl->lastframe > 0)
cl->frame_latency[sv.framenum&(LATENCY_COUNTS-1)] = sv.framenum - cl->lastframe + 1;
else
cl->frame_latency[sv.framenum&(LATENCY_COUNTS-1)] = 0;
#endif
total = 0;
count = 0;
for (j=0 ; j<LATENCY_COUNTS ; j++)
{
if (cl->frame_latency[j] > 0)
{
count++;
total += cl->frame_latency[j];
}
}
if (!count)
cl->ping = 0;
else
#if 0
cl->ping = total*100/count - 100;
#else
cl->ping = total / count;
#endif
// let the game dll know about the ping
2007-09-16 22:00:00 +02:00
cl->edict->priv.sv->client->ping = cl->ping;
2007-06-21 22:00:00 +02:00
}
}
/*
===================
SV_GiveMsec
Every few frames, gives all clients an allotment of milliseconds
for their command moves. If they exceed it, assume cheating.
===================
*/
void SV_GiveMsec (void)
{
int i;
client_t *cl;
2007-09-06 22:00:00 +02:00
if (sv.framenum & 15)
return;
2007-06-21 22:00:00 +02:00
2007-09-06 22:00:00 +02:00
for (i=0 ; i<maxclients->value ; i++)
2007-06-21 22:00:00 +02:00
{
cl = &svs.clients[i];
if (cl->state == cs_free )
continue;
2007-09-06 22:00:00 +02:00
cl->commandMsec = 1800; // 1600 + some slop
2007-06-21 22:00:00 +02:00
}
}
/*
=================
SV_ReadPackets
=================
*/
void SV_ReadPackets (void)
{
int i;
client_t *cl;
int qport;
while (NET_GetPacket (NS_SERVER, &net_from, &net_message))
{
// check for connectionless packet (0xffffffff) first
if (*(int *)net_message.data == -1)
{
SV_ConnectionlessPacket ();
continue;
}
// read the qport out of the message so we can fix up
// stupid address translating routers
MSG_BeginReading (&net_message);
MSG_ReadLong (&net_message); // sequence number
MSG_ReadLong (&net_message); // sequence number
qport = MSG_ReadShort (&net_message) & 0xffff;
// check for packets from connected clients
2007-10-19 22:00:00 +02:00
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
2007-06-21 22:00:00 +02:00
{
2007-10-19 22:00:00 +02:00
if (cl->state == cs_free) continue;
if (!NET_CompareBaseAdr (net_from, cl->netchan.remote_address)) continue;
if (cl->netchan.qport != qport) continue;
2007-06-21 22:00:00 +02:00
if (cl->netchan.remote_address.port != net_from.port)
{
2007-10-19 22:00:00 +02:00
MsgDev(D_INFO, "SV_ReadPackets: fixing up a translated port\n");
2007-06-21 22:00:00 +02:00
cl->netchan.remote_address.port = net_from.port;
}
if (Netchan_Process(&cl->netchan, &net_message))
2007-10-19 22:00:00 +02:00
{
// this is a valid, sequenced packet, so process it
2007-06-21 22:00:00 +02:00
if (cl->state != cs_zombie)
{
2007-10-19 22:00:00 +02:00
cl->lastmessage = svs.realtime; // don't timeout
2007-06-21 22:00:00 +02:00
SV_ExecuteClientMessage (cl);
}
}
break;
}
2007-10-19 22:00:00 +02:00
if (i != maxclients->value) continue;
2007-06-21 22:00:00 +02:00
}
}
/*
==================
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 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)
{
int i;
client_t *cl;
2007-09-07 22:00:00 +02:00
float droppoint;
float zombiepoint;
2007-06-21 22:00:00 +02:00
2007-09-07 22:00:00 +02:00
droppoint = svs.realtime - timeout->value;
zombiepoint = svs.realtime - zombietime->value;
2007-06-21 22:00:00 +02:00
2007-09-06 22:00:00 +02:00
for (i=0,cl=svs.clients ; i<maxclients->value ; i++,cl++)
2007-06-21 22:00:00 +02:00
{
// message times may be wrong across a changelevel
2007-09-09 22:00:00 +02:00
if (cl->lastmessage > svs.realtime)
cl->lastmessage = svs.realtime;
2007-06-21 22:00:00 +02:00
2007-09-06 22:00:00 +02:00
if (cl->state == cs_zombie
2007-09-09 22:00:00 +02:00
&& cl->lastmessage < zombiepoint)
2007-06-21 22:00:00 +02:00
{
cl->state = cs_free; // can now be reused
continue;
}
2007-09-09 22:00:00 +02:00
if ( (cl->state == cs_connected || cl->state == cs_spawned) && cl->lastmessage < droppoint)
2007-06-21 22:00:00 +02:00
{
SV_BroadcastPrintf (PRINT_HIGH, "%s timed out\n", cl->name);
SV_DropClient (cl);
cl->state = cs_free; // don't bother with zombie state
}
}
}
/*
================
SV_PrepWorldFrame
This has to be done before the world logic, because
player processing happens outside RunWorldFrame
================
*/
void SV_PrepWorldFrame (void)
{
2007-09-16 22:00:00 +02:00
edict_t *ent;
2007-06-21 22:00:00 +02:00
int i;
2007-09-16 22:00:00 +02:00
for (i = 0; i < prog->num_edicts ; i++, ent++)
2007-06-21 22:00:00 +02:00
{
2007-09-16 22:00:00 +02:00
ent = PRVM_EDICT_NUM(i);
2007-06-21 22:00:00 +02:00
// events only last for a single message
2007-09-18 22:00:00 +02:00
ent->priv.sv->event = 0;
2007-09-06 22:00:00 +02:00
}
}
/*
=================
SV_RunGameFrame
=================
*/
void SV_RunGameFrame (void)
{
if (host_speeds->value)
2007-09-07 22:00:00 +02:00
time_before_game = Sys_DoubleTime();
2007-09-06 22:00:00 +02:00
// we always need to bump framenum, even if we
// don't run the world, otherwise the delta
// compression can get confused when a client
// has the "current" frame
sv.framenum++;
2007-10-19 22:00:00 +02:00
sv.frametime = 0.1f;//1000 fps
2007-09-17 22:00:00 +02:00
sv.time = sv.framenum * sv.frametime;
2007-09-06 22:00:00 +02:00
// don't run if paused
if (!sv_paused->value || maxclients->value > 1)
{
2007-10-27 22:00:00 +02:00
Phys->Frame( sv.frametime );
2007-09-06 22:00:00 +02:00
SV_RunFrame ();
// never get more than one tic behind
if (sv.time < svs.realtime)
{
if (sv_showclamp->value)
Msg ("sv highclamp\n");
svs.realtime = sv.time;
}
2007-06-21 22:00:00 +02:00
}
2007-09-17 22:00:00 +02:00
sv.time += sv.frametime;
2007-09-06 22:00:00 +02:00
if (host_speeds->value)
2007-09-07 22:00:00 +02:00
time_after_game = Sys_DoubleTime ();
2007-09-06 22:00:00 +02:00
2007-06-21 22:00:00 +02:00
}
/*
==================
SV_Frame
==================
*/
2007-09-07 22:00:00 +02:00
void SV_Frame (float time)
2007-06-21 22:00:00 +02:00
{
2007-09-06 22:00:00 +02:00
time_before_game = time_after_game = 0;
2007-06-21 22:00:00 +02:00
// if server is not active, do nothing
2007-09-06 22:00:00 +02:00
if (!svs.initialized)
return;
2007-06-21 22:00:00 +02:00
2007-09-07 22:00:00 +02:00
svs.realtime += time;
2007-06-21 22:00:00 +02:00
// keep the random time dependent
rand ();
2007-09-16 22:00:00 +02:00
// setup the VM frame
SV_VM_Begin();
2007-06-21 22:00:00 +02:00
// check timeouts
SV_CheckTimeouts ();
2007-09-06 22:00:00 +02:00
2007-06-21 22:00:00 +02:00
// get packets from clients
SV_ReadPackets ();
// move autonomous things around if enough time has passed
if (!sv_timedemo->value && svs.realtime < sv.time)
{
// never let the time get too far off
2007-09-17 22:00:00 +02:00
if (sv.time - svs.realtime > sv.frametime)
2007-06-21 22:00:00 +02:00
{
2007-09-06 22:00:00 +02:00
if (sv_showclamp->value)
Msg ("sv lowclamp\n");
2007-09-17 22:00:00 +02:00
svs.realtime = sv.time - sv.frametime;
2007-06-21 22:00:00 +02:00
}
2007-09-17 22:00:00 +02:00
2007-09-07 22:00:00 +02:00
NET_Sleep((sv.time - svs.realtime) * 1000);
2007-09-17 22:00:00 +02:00
SV_VM_End(); //end frame
2007-06-21 22:00:00 +02:00
return;
}
// update ping based on the last known frame from all clients
SV_CalcPings ();
// give the clients some timeslices
SV_GiveMsec ();
// let everything in the world think and move
2007-09-06 22:00:00 +02:00
SV_RunGameFrame ();
2007-06-21 22:00:00 +02:00
// send messages back to the clients that had packets read this frame
SV_SendClientMessages ();
// save the entire world state if recording a serverdemo
SV_RecordDemoMessage ();
// send a heartbeat to the master if needed
Master_Heartbeat ();
// clear teleport flags, etc for next frame
SV_PrepWorldFrame ();
2007-09-16 22:00:00 +02:00
// end the server VM frame
SV_VM_End();
2007-06-21 22:00:00 +02:00
}
//============================================================================
/*
================
Master_Heartbeat
Send a message to the master every few minutes to
let it know we are alive, and log information
================
*/
2007-09-09 22:00:00 +02:00
#define HEARTBEAT_SECONDS 300.0f
2007-06-21 22:00:00 +02:00
void Master_Heartbeat (void)
{
char *string;
int i;
2007-09-06 22:00:00 +02:00
// pgm post3.19 change, cvar pointer not validated before dereferencing
if (!dedicated || !dedicated->value)
return; // only dedicated servers send heartbeats
2007-06-21 22:00:00 +02:00
// pgm post3.19 change, cvar pointer not validated before dereferencing
if (!public_server || !public_server->value)
2007-09-09 22:00:00 +02:00
return; // a private dedicated game
2007-06-21 22:00:00 +02:00
// check for time wraparound
2007-09-09 22:00:00 +02:00
if (svs.last_heartbeat > svs.realtime) svs.last_heartbeat = svs.realtime;
2007-06-21 22:00:00 +02:00
2007-09-09 22:00:00 +02:00
if (svs.realtime - svs.last_heartbeat < HEARTBEAT_SECONDS)
2007-06-21 22:00:00 +02:00
return; // not time to send yet
2007-09-09 22:00:00 +02:00
svs.last_heartbeat = svs.realtime;
2007-06-21 22:00:00 +02:00
// send the same string that we would give for a status OOB command
string = SV_StatusString();
// send to group master
2007-09-09 22:00:00 +02:00
for (i = 0; i < MAX_MASTERS; i++)
{
2007-06-21 22:00:00 +02:00
if (master_adr[i].port)
{
2007-07-28 22:00:00 +02:00
Msg ("Sending heartbeat to %s\n", NET_AdrToString (master_adr[i]));
2007-06-21 22:00:00 +02:00
Netchan_OutOfBandPrint (NS_SERVER, master_adr[i], "heartbeat\n%s", string);
}
2007-09-09 22:00:00 +02:00
}
2007-06-21 22:00:00 +02:00
}
/*
=================
Master_Shutdown
Informs all masters that this server is going down
=================
*/
void Master_Shutdown (void)
{
int i;
2007-09-06 22:00:00 +02:00
// pgm post3.19 change, cvar pointer not validated before dereferencing
if (!dedicated || !dedicated->value)
return; // only dedicated servers send heartbeats
2007-06-21 22:00:00 +02:00
// pgm post3.19 change, cvar pointer not validated before dereferencing
if (!public_server || !public_server->value)
return; // a private dedicated game
// send to group master
2007-09-06 22:00:00 +02:00
for (i=0 ; i<MAX_MASTERS ; i++)
2007-06-21 22:00:00 +02:00
if (master_adr[i].port)
{
2007-09-06 22:00:00 +02:00
if (i > 0)
Msg ("Sending heartbeat to %s\n", NET_AdrToString (master_adr[i]));
2007-06-21 22:00:00 +02:00
Netchan_OutOfBandPrint (NS_SERVER, master_adr[i], "shutdown");
}
}
//============================================================================
/*
=================
SV_UserinfoChanged
Pull specific info from a newly changed userinfo string
into a more C freindly form.
=================
*/
void SV_UserinfoChanged (client_t *cl)
{
char *val;
int i;
// call prog code to allow overrides
2007-09-06 22:00:00 +02:00
SV_ClientUserinfoChanged(cl->edict, cl->userinfo);
2007-06-21 22:00:00 +02:00
// name for C code
strncpy (cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name)-1);
// mask off high bit
for (i=0 ; i<sizeof(cl->name) ; i++)
cl->name[i] &= 127;
// rate command
val = Info_ValueForKey (cl->userinfo, "rate");
if (strlen(val))
{
i = atoi(val);
cl->rate = i;
if (cl->rate < 100)
cl->rate = 100;
if (cl->rate > 15000)
cl->rate = 15000;
}
else
cl->rate = 5000;
// msg command
val = Info_ValueForKey (cl->userinfo, "msg");
if (strlen(val))
{
cl->messagelevel = atoi(val);
}
}
//============================================================================
/*
===============
SV_Init
Only called at xash.exe startup, not for each game
===============
*/
void SV_Init (void)
{
SV_InitOperatorCommands ();
rcon_password = Cvar_Get ("rcon_password", "", 0);
Cvar_Get ("skill", "1", 0);
Cvar_Get ("deathmatch", "0", CVAR_LATCH);
Cvar_Get ("coop", "0", CVAR_LATCH);
Cvar_Get ("dmflags", va("%i", DF_INSTANT_ITEMS), CVAR_SERVERINFO);
Cvar_Get ("fraglimit", "0", CVAR_SERVERINFO);
Cvar_Get ("timelimit", "0", CVAR_SERVERINFO);
Cvar_Get ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO|CVAR_NOSET);;
2007-09-06 22:00:00 +02:00
maxclients = Cvar_Get ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
hostname = Cvar_Get ("hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE);
2007-06-21 22:00:00 +02:00
timeout = Cvar_Get ("timeout", "125", 0);
zombietime = Cvar_Get ("zombietime", "2", 0);
sv_showclamp = Cvar_Get ("showclamp", "0", 0);
sv_paused = Cvar_Get ("paused", "0", 0);
sv_timedemo = Cvar_Get ("timedemo", "0", 0);
sv_enforcetime = Cvar_Get ("sv_enforcetime", "0", 0);
allow_download = Cvar_Get ("allow_download", "1", CVAR_ARCHIVE);
allow_download_players = Cvar_Get ("allow_download_players", "0", CVAR_ARCHIVE);
allow_download_models = Cvar_Get ("allow_download_models", "1", CVAR_ARCHIVE);
allow_download_sounds = Cvar_Get ("allow_download_sounds", "1", CVAR_ARCHIVE);
allow_download_maps = Cvar_Get ("allow_download_maps", "1", CVAR_ARCHIVE);
sv_noreload = Cvar_Get ("sv_noreload", "0", 0);
2007-09-06 22:00:00 +02:00
sv_airaccelerate = Cvar_Get("sv_airaccelerate", "0", CVAR_LATCH);
sv_maxvelocity = Cvar_Get("sv_maxvelocity", "2000", 0 );
sv_gravity = Cvar_Get("sv_gravity", "800", 0 );
2007-06-21 22:00:00 +02:00
public_server = Cvar_Get ("public", "0", 0);
sv_reconnect_limit = Cvar_Get ("sv_reconnect_limit", "3", CVAR_ARCHIVE);
SZ_Init (&net_message, net_message_buffer, sizeof(net_message_buffer));
}
/*
==================
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 (char *message, bool reconnect)
{
int i;
client_t *cl;
SZ_Clear (&net_message);
MSG_WriteByte (&net_message, svc_print);
MSG_WriteByte (&net_message, PRINT_HIGH);
MSG_WriteString (&net_message, message);
if (reconnect)
2007-08-01 22:00:00 +02:00
{
2007-06-21 22:00:00 +02:00
MSG_WriteByte (&net_message, svc_reconnect);
2007-08-01 22:00:00 +02:00
}
2007-06-21 22:00:00 +02:00
else
2007-08-01 22:00:00 +02:00
{
2007-06-21 22:00:00 +02:00
MSG_WriteByte (&net_message, svc_disconnect);
2007-08-01 22:00:00 +02:00
}
2007-06-21 22:00:00 +02:00
// send it twice
// stagger the packets to crutch operating system limited buffers
2007-09-06 22:00:00 +02:00
for (i=0, cl = svs.clients ; i<maxclients->value ; i++, cl++)
2007-06-21 22:00:00 +02:00
if (cl->state >= cs_connected)
Netchan_Transmit (&cl->netchan, net_message.cursize
, net_message.data);
2007-09-06 22:00:00 +02:00
for (i=0, cl = svs.clients ; i<maxclients->value ; i++, cl++)
2007-06-21 22:00:00 +02:00
if (cl->state >= cs_connected)
Netchan_Transmit (&cl->netchan, net_message.cursize
, net_message.data);
}
/*
================
SV_Shutdown
Called when each game quits,
before Sys_Quit or Sys_Error
================
*/
void SV_Shutdown (char *finalmsg, bool reconnect)
{
2007-09-22 22:00:00 +02:00
Msg("SV_Shutdown: %s\n", finalmsg );
2007-06-21 22:00:00 +02:00
if (svs.clients) SV_FinalMessage (finalmsg, reconnect);
Master_Shutdown ();
2007-09-06 22:00:00 +02:00
SV_ShutdownGameProgs ();
2007-06-21 22:00:00 +02:00
// free current level
if (sv.demofile) FS_Close (sv.demofile);
memset (&sv, 0, sizeof(sv));
Com_SetServerState (sv.state);
// free server static data
if (svs.clients) Z_Free (svs.clients);
if (svs.client_entities) Z_Free (svs.client_entities);
if (svs.demofile) FS_Close (svs.demofile);
memset (&svs, 0, sizeof(svs));
}