/*
 * 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 */

cvar_t         *sv_killserver;

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;

	Q_strncpyz(status, Cvar_Serverinfo(), sizeof(status));
	strncat(status, "\n", sizeof(status) - strlen(status) - 1);
	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 */
			Q_strncpyz(status + statusLength, player, sizeof(status) - statusLength);
			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)
{
	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));

	Q_strncpyz(userinfo, Cmd_Argv(4), sizeof(userinfo));
	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)) {

			/*
			 * sku - avoid reusing slot of the client already
			 * connected
			 */
			if (cl->state != cs_zombie) {
				Netchan_OutOfBandPrint(NS_SERVER, adr, "print\nConnected client from this IP is already present.\n");
				return;
			}
			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 */
	Q_strncpyz(newcl->userinfo, userinfo, sizeof(newcl->userinfo));
	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;

		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
			cl->ping = total / count;

		/* 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 ================
 */
#define	HEARTBEAT_SECONDS	300
void
Master_Heartbeat(void)
{
	char           *string;
	int		i;

	/*
	 * pgm post3.19 change, cvar pointer not validated before
	 * dereferencing
	 */
	if (!dedicated || !dedicated->value)
		return;		/* only dedicated servers send heartbeats */

	/*
	 * pgm post3.19 change, cvar pointer not validated before
	 * dereferencing
	 */
	if (!public_server || !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;

	/*
	 * pgm post3.19 change, cvar pointer not validated before
	 * dereferencing
	 */
	if (!dedicated || !dedicated->value)
		return;		/* only dedicated servers send heartbeats */

	/*
	 * 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 */
	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 */
	Q_strncpyz(cl->name, Info_ValueForKey(cl->userinfo, "name"), sizeof(cl->name));
	/* 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", "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);

	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);

	sv_killserver = Cvar_Get("sv_killserver", "0", 0);

	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)
		FS_FCloseFile(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));
}
