mirror of
https://github.com/libretro/quake2-for-ps2.git
synced 2026-11-07 04:25:58 -10:00
- Finished texture image loading for PCX, WAL and TGA; - Added the NET driver for the PS2, loopback only for local single-player; - Replaced all malloc/free calls with either Quake’s Z_Malloc or PS2_MemAlloc, so we can keep precise memory usage statistics; - Several other miscellaneous changes.
1037 lines
26 KiB
C
1037 lines
26 KiB
C
/*
|
|
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 "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;
|
|
cvar_t * sv_airaccelerate;
|
|
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;
|
|
cvar_t * public_server; // should heartbeats be sent
|
|
cvar_t * sv_reconnect_limit; // minimum seconds between connect messages
|
|
|
|
void Master_Shutdown(void);
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
=====================
|
|
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
|
|
ge->ClientDisconnect(drop->edict);
|
|
}
|
|
|
|
if (drop->download)
|
|
{
|
|
FS_FreeFile(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
|
|
===============
|
|
*/
|
|
char * SV_StatusString(void)
|
|
{
|
|
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);
|
|
|
|
for (i = 0; i < maxclients->value; i++)
|
|
{
|
|
cl = &svs.clients[i];
|
|
if (cl->state == cs_connected || cl->state == cs_spawned)
|
|
{
|
|
Com_sprintf(player, sizeof(player), "%i %i \"%s\"\n",
|
|
cl->edict->client->ps.stats[STAT_FRAGS], cl->ping, cl->name);
|
|
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());
|
|
#if 0
|
|
Com_BeginRedirect (RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
|
|
Com_Printf (SV_StatusString());
|
|
Com_EndRedirect ();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
SVC_Ack
|
|
|
|
================
|
|
*/
|
|
void SVC_Ack(void)
|
|
{
|
|
Com_Printf("Ping acknowledge from %s\n", NET_AdrToString(net_from));
|
|
}
|
|
|
|
/*
|
|
================
|
|
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;
|
|
|
|
if (maxclients->value == 1)
|
|
return; // ignore in single player
|
|
|
|
version = atoi(Cmd_Argv(1));
|
|
|
|
if (version != PROTOCOL_VERSION)
|
|
Com_sprintf(string, sizeof(string), "%s: wrong version\n", hostname->string, sizeof(string));
|
|
else
|
|
{
|
|
count = 0;
|
|
for (i = 0; i < maxclients->value; i++)
|
|
if (svs.clients[i].state >= cs_connected)
|
|
count++;
|
|
|
|
Com_sprintf(string, sizeof(string), "%16s %8s %2i/%2i\n", hostname->string, sv.name, count, (int)maxclients->value);
|
|
}
|
|
|
|
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;
|
|
int oldestTime;
|
|
|
|
oldest = 0;
|
|
oldestTime = 0x7fffffff;
|
|
|
|
// 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;
|
|
svs.challenges[oldest].time = curtime;
|
|
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
|
|
==================
|
|
*/
|
|
void SVC_DirectConnect(void)
|
|
{
|
|
char userinfo[MAX_INFO_STRING];
|
|
netadr_t adr;
|
|
int i;
|
|
client_t *cl, *newcl;
|
|
client_t temp;
|
|
edict_t * ent;
|
|
int edictnum;
|
|
int version;
|
|
int qport;
|
|
int challenge;
|
|
|
|
adr = net_from;
|
|
|
|
Com_DPrintf("SVC_DirectConnect ()\n");
|
|
|
|
version = atoi(Cmd_Argv(1));
|
|
if (version != PROTOCOL_VERSION)
|
|
{
|
|
Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is version %4.2f.\n", VERSION);
|
|
Com_DPrintf(" rejected connect from version %i\n", version);
|
|
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))
|
|
{
|
|
Com_Printf("Remote connect in attract loop. Ignored.\n");
|
|
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)
|
|
break; // good
|
|
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
|
|
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
|
|
{
|
|
if (cl->state == cs_free)
|
|
continue;
|
|
if (NET_CompareBaseAdr(adr, cl->netchan.remote_address) && (cl->netchan.qport == qport || adr.port == cl->netchan.remote_address.port))
|
|
{
|
|
if (!NET_IsLocalAddress(adr) && (svs.realtime - cl->lastconnect) < ((int)sv_reconnect_limit->value * 1000))
|
|
{
|
|
Com_DPrintf("%s:reconnect rejected : too soon\n", NET_AdrToString(adr));
|
|
return;
|
|
}
|
|
Com_Printf("%s:reconnect\n", NET_AdrToString(adr));
|
|
newcl = cl;
|
|
goto gotnewcl;
|
|
}
|
|
}
|
|
|
|
// find a client slot
|
|
newcl = NULL;
|
|
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
|
|
{
|
|
if (cl->state == cs_free)
|
|
{
|
|
newcl = cl;
|
|
break;
|
|
}
|
|
}
|
|
if (!newcl)
|
|
{
|
|
Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nServer is full.\n");
|
|
Com_DPrintf("Rejected a connection.\n");
|
|
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;
|
|
ent = EDICT_NUM(edictnum);
|
|
newcl->edict = ent;
|
|
newcl->challenge = challenge; // save challenge for checksumming
|
|
|
|
// get the game a chance to reject this connection or modify the userinfo
|
|
if (!(ge->ClientConnect(ent, userinfo)))
|
|
{
|
|
if (*Info_ValueForKey(userinfo, "rejmsg"))
|
|
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");
|
|
Com_DPrintf("Game rejected a connection.\n");
|
|
return;
|
|
}
|
|
|
|
// 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));
|
|
newcl->datagram.allowoverflow = true;
|
|
newcl->lastmessage = svs.realtime; // don't timeout
|
|
newcl->lastconnect = svs.realtime;
|
|
}
|
|
|
|
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;
|
|
char remaining[1024];
|
|
|
|
i = Rcon_Validate();
|
|
|
|
if (i == 0)
|
|
Com_Printf("Bad rcon from %s:\n%s\n", NET_AdrToString(net_from), net_message.data + 4);
|
|
else
|
|
Com_Printf("Rcon from %s:\n%s\n", NET_AdrToString(net_from), net_message.data + 4);
|
|
|
|
Com_BeginRedirect(RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
|
|
|
|
if (!Rcon_Validate())
|
|
{
|
|
Com_Printf("Bad rcon_password.\n");
|
|
}
|
|
else
|
|
{
|
|
remaining[0] = 0;
|
|
|
|
for (i = 2; i < Cmd_Argc(); i++)
|
|
{
|
|
strcat(remaining, Cmd_Argv(i));
|
|
strcat(remaining, " ");
|
|
}
|
|
|
|
Cmd_ExecuteString(remaining);
|
|
}
|
|
|
|
Com_EndRedirect();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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);
|
|
MSG_ReadLong(&net_message); // skip the -1 marker
|
|
|
|
s = MSG_ReadStringLine(&net_message);
|
|
|
|
Cmd_TokenizeString(s, false);
|
|
|
|
c = Cmd_Argv(0);
|
|
Com_DPrintf("Packet %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
|
|
Com_Printf("bad connectionless packet from %s:\n%s\n", NET_AdrToString(net_from), s);
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
===================
|
|
SV_CalcPings
|
|
|
|
Updates the cl->ping variables
|
|
===================
|
|
*/
|
|
void SV_CalcPings(void)
|
|
{
|
|
int i, j;
|
|
client_t * cl;
|
|
int total, count;
|
|
|
|
for (i = 0; i < maxclients->value; i++)
|
|
{
|
|
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
|
|
cl->edict->client->ping = cl->ping;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
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;
|
|
|
|
if (sv.framenum & 15)
|
|
return;
|
|
|
|
for (i = 0; i < maxclients->value; i++)
|
|
{
|
|
cl = &svs.clients[i];
|
|
if (cl->state == cs_free)
|
|
continue;
|
|
|
|
cl->commandMsec = 1800; // 1600 + some slop
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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
|
|
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
|
|
{
|
|
if (cl->state == cs_free)
|
|
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)
|
|
{
|
|
Com_Printf("SV_ReadPackets: fixing up a translated port\n");
|
|
cl->netchan.remote_address.port = net_from.port;
|
|
}
|
|
|
|
if (Netchan_Process(&cl->netchan, &net_message))
|
|
{ // this is a valid, sequenced packet, so process it
|
|
if (cl->state != cs_zombie)
|
|
{
|
|
cl->lastmessage = svs.realtime; // don't timeout
|
|
SV_ExecuteClientMessage(cl);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (i != maxclients->value)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
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;
|
|
int droppoint;
|
|
int zombiepoint;
|
|
|
|
droppoint = svs.realtime - 1000 * timeout->value;
|
|
zombiepoint = svs.realtime - 1000 * zombietime->value;
|
|
|
|
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
|
|
{
|
|
// message times may be wrong across a changelevel
|
|
if (cl->lastmessage > svs.realtime)
|
|
cl->lastmessage = svs.realtime;
|
|
|
|
if (cl->state == cs_zombie && cl->lastmessage < zombiepoint)
|
|
{
|
|
cl->state = cs_free; // can now be reused
|
|
continue;
|
|
}
|
|
if ((cl->state == cs_connected || cl->state == cs_spawned) && cl->lastmessage < droppoint)
|
|
{
|
|
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)
|
|
{
|
|
edict_t * ent;
|
|
int i;
|
|
|
|
for (i = 0; i < ge->num_edicts; i++, ent++)
|
|
{
|
|
ent = EDICT_NUM(i);
|
|
// events only last for a single message
|
|
ent->s.event = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SV_RunGameFrame
|
|
=================
|
|
*/
|
|
void SV_RunGameFrame(void)
|
|
{
|
|
if (host_speeds->value)
|
|
{
|
|
time_before_game = Sys_Milliseconds();
|
|
}
|
|
|
|
// 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++;
|
|
sv.time = sv.framenum * 100;
|
|
|
|
// don't run if paused
|
|
if (!sv_paused->value || maxclients->value > 1)
|
|
{
|
|
ge->RunFrame();
|
|
|
|
// never get more than one tic behind
|
|
if (sv.time < svs.realtime)
|
|
{
|
|
if (sv_showclamp->value)
|
|
{
|
|
Com_Printf("sv highclamp\n");
|
|
}
|
|
svs.realtime = sv.time;
|
|
}
|
|
}
|
|
|
|
if (host_speeds->value)
|
|
{
|
|
time_after_game = Sys_Milliseconds();
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Frame
|
|
==================
|
|
*/
|
|
void SV_Frame(int msec)
|
|
{
|
|
time_before_game = time_after_game = 0;
|
|
|
|
// if server is not active, do nothing
|
|
if (!svs.initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
svs.realtime += msec;
|
|
|
|
// keep the random time dependent
|
|
rand();
|
|
|
|
// check timeouts
|
|
SV_CheckTimeouts();
|
|
|
|
// 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
|
|
if (sv.time - svs.realtime > 100)
|
|
{
|
|
if (sv_showclamp->value)
|
|
{
|
|
Com_Printf("sv lowclamp\n");
|
|
}
|
|
svs.realtime = sv.time - 100;
|
|
}
|
|
NET_Sleep(sv.time - svs.realtime);
|
|
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
|
|
SV_RunGameFrame();
|
|
|
|
// 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();
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
================
|
|
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)
|
|
{
|
|
enum
|
|
{
|
|
HEARTBEAT_SECONDS = 300
|
|
};
|
|
|
|
char * string;
|
|
int i;
|
|
|
|
if (!dedicated->value)
|
|
return; // only dedicated servers send heartbeats
|
|
|
|
if (!public_server->value)
|
|
return; // a private dedicated game
|
|
|
|
// check for time wraparound
|
|
if (svs.last_heartbeat > svs.realtime)
|
|
svs.last_heartbeat = svs.realtime;
|
|
|
|
if (svs.realtime - svs.last_heartbeat < HEARTBEAT_SECONDS * 1000)
|
|
return; // not time to send yet
|
|
|
|
svs.last_heartbeat = svs.realtime;
|
|
|
|
// send the same string that we would give for a status OOB command
|
|
string = SV_StatusString();
|
|
|
|
// send to group master
|
|
for (i = 0; i < MAX_MASTERS; i++)
|
|
{
|
|
if (master_adr[i].port)
|
|
{
|
|
Com_Printf("Sending heartbeat to %s\n", NET_AdrToString(master_adr[i]));
|
|
Netchan_OutOfBandPrint(NS_SERVER, master_adr[i], "heartbeat\n%s", string);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Master_Shutdown
|
|
|
|
Informs all masters that this server is going down
|
|
=================
|
|
*/
|
|
void Master_Shutdown(void)
|
|
{
|
|
int i;
|
|
|
|
if (!dedicated->value)
|
|
return; // only dedicated servers send heartbeats
|
|
|
|
if (!public_server->value)
|
|
return; // a private dedicated game
|
|
|
|
// send to group master
|
|
for (i = 0; i < MAX_MASTERS; i++)
|
|
if (master_adr[i].port)
|
|
{
|
|
if (i > 0)
|
|
Com_Printf("Sending heartbeat to %s\n", NET_AdrToString(master_adr[i]));
|
|
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
|
|
ge->ClientUserinfoChanged(cl->edict, cl->userinfo);
|
|
|
|
// 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 quake2.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);
|
|
|
|
maxclients = Cvar_Get("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
|
|
hostname = Cvar_Get("hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE);
|
|
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", "0", 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);
|
|
sv_airaccelerate = Cvar_Get("sv_airaccelerate", "0", CVAR_LATCH);
|
|
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, qboolean 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)
|
|
MSG_WriteByte(&net_message, svc_reconnect);
|
|
else
|
|
MSG_WriteByte(&net_message, svc_disconnect);
|
|
|
|
// send it twice
|
|
// stagger the packets to crutch operating system limited buffers
|
|
|
|
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
|
|
if (cl->state >= cs_connected)
|
|
Netchan_Transmit(&cl->netchan, net_message.cursize, net_message.data);
|
|
|
|
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
|
|
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, qboolean reconnect)
|
|
{
|
|
if (svs.clients)
|
|
SV_FinalMessage(finalmsg, reconnect);
|
|
|
|
Master_Shutdown();
|
|
SV_ShutdownGameProgs();
|
|
|
|
// free current level
|
|
if (sv.demofile)
|
|
fclose(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)
|
|
fclose(svs.demofile);
|
|
|
|
memset(&svs, 0, sizeof(svs));
|
|
}
|