/*
	sv_user.c

	server code for moving users

	Copyright (C) 1996-1997  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:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
static const char rcsid[] = 
	"$Id$";

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>

#include "QF/checksum.h"
#include "QF/clip_hull.h"
#include "QF/cmd.h"
#include "QF/cvar.h"
#include "QF/msg.h"
#include "QF/net_clc.h"
#include "QF/net_svc.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "QF/vfs.h"

#include "compat.h"
#include "bothdefs.h"
#include "pmove.h"
#include "server.h"
#include "sv_progs.h"
#include "world.h"

edict_t    *sv_player;

usercmd_t   cmd;

cvar_t     *cl_rollspeed;
cvar_t     *cl_rollangle;
cvar_t     *sv_spectalk;

cvar_t     *sv_mapcheck;

cvar_t     *sv_timekick;
cvar_t     *sv_timekick_fuzz;
cvar_t     *sv_timekick_interval;

cvar_t     *sv_kickfake;

qboolean	move_issued;


void        SV_FullClientUpdateToClient (client_t *client, client_t *cl);

/*
	USER STRINGCMD EXECUTION

	host_client and sv_player will be valid.
*/

/*
	SV_New_f

	Sends the first message from the server to a connected client.
	This will be sent on the initial connection and upon each server load.
*/
void
SV_New_f (void)
{
	const char *gamedir;
	net_svc_serverdata_t serverdata;
	net_svc_qwcdtrack_t cdtrack;
	net_svc_stufftext_t stufftext;

	if (host_client->state == cs_spawned)
		return;

	host_client->state = cs_connected;
	host_client->connection_started = realtime;

	// send the info about the new client to all connected clients
//  SV_FullClientUpdate (host_client, &sv.reliable_datagram);
//  host_client->sendinfo = true;

	gamedir = Info_ValueForKey (svs.info, "*gamedir");
	if (!gamedir[0])
		gamedir = "qw";

//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
//spawns.  These functions are written to not overflow
	if (host_client->num_backbuf) {
		SV_Printf ("WARNING %s: [SV_New] Back buffered (%d), clearing\n",
					host_client->name, host_client->netchan.message.cursize);
		host_client->num_backbuf = 0;
		SZ_Clear (&host_client->netchan.message);
	}

	// send the serverdata
	serverdata.protocolversion = PROTOCOL_VERSION;
	serverdata.servercount = svs.spawncount;
	serverdata.gamedir = gamedir;
	serverdata.playernum = NUM_FOR_EDICT (&sv_pr_state, host_client->edict) - 1;
	serverdata.spectator = host_client->spectator;
	serverdata.levelname = PR_GetString (&sv_pr_state,
									SVstring (sv.edicts, message));
	serverdata.movevars = movevars;
	NET_SVC_Emit (svc_serverdata, &serverdata, &host_client->netchan.message);

	// send music
	cdtrack.cdtrack = SVfloat (sv.edicts, sounds);
	NET_SVC_Emit (svc_qwcdtrack, &cdtrack, &host_client->netchan.message);

	// send server info string
	stufftext.commands = va ("fullserverinfo \"%s\"\n", Info_MakeString (svs.info, 0));
	NET_SVC_Emit (svc_stufftext, &stufftext, &host_client->netchan.message);
}

/*
	SV_Soundlist_f
*/
void
SV_Soundlist_f (void)
{
	const char **s;
	int			i, size;
	net_svc_soundlist_t block;

	if (host_client->state != cs_connected) {
		SV_Printf ("soundlist not valid -- already spawned\n");
		return;
	}

	// handle the case of a level changing while a client was connecting
	if (atoi (Cmd_Argv (1)) != svs.spawncount) {
		SV_Printf ("SV_Soundlist_f from different level\n");
		SV_New_f ();
		return;
	}

	block.startsound = atoi (Cmd_Argv (2));
	if (block.startsound >= MAX_SOUNDS) {
		SV_Printf ("SV_Soundlist_f: Invalid soundlist index\n");
		SV_New_f ();
		return;
	}

	// NOTE:  This doesn't go through ClientReliableWrite since it's
	// before the user spawns.  These functions are written to not
	// overflow
	if (host_client->num_backbuf) {
		SV_Printf ("WARNING %s: [SV_Soundlist] Back buffered (%d), clearing",
					host_client->name, host_client->netchan.message.cursize);
		host_client->num_backbuf = 0;
		SZ_Clear (&host_client->netchan.message);
	}

	for (s = sv.sound_precache + 1 + block.startsound, i = 0, size = 0;
		 *s; i++, s++) {
		if (host_client->netchan.message.cursize + size >= (MAX_MSGLEN / 2))
			break;
		size += strlen (*s) + 1;
		block.sounds[i] = *s;
	}
	block.sounds[i] = "";

	// next msg
	if (*s)
		block.nextsound = block.startsound + i;
	else
		block.nextsound = 0;

	NET_SVC_Emit (svc_soundlist, &block, &host_client->netchan.message);
}

/*
	SV_Modellist_f
*/
void
SV_Modellist_f (void)
{
	const char **s;
	int			i, size;
	net_svc_modellist_t block;

	if (host_client->state != cs_connected) {
		SV_Printf ("modellist not valid -- already spawned\n");
		return;
	}

	// handle the case of a level changing while a client was connecting
	if (atoi (Cmd_Argv (1)) != svs.spawncount) {
		SV_Printf ("SV_Modellist_f from different level\n");
		SV_New_f ();
		return;
	}

	block.startmodel = atoi (Cmd_Argv (2));
	if (block.startmodel >= MAX_MODELS) {
		SV_Printf ("SV_Modellist_f: Invalid modellist index\n");
		SV_New_f ();
		return;
	}

	// NOTE:  This doesn't go through ClientReliableWrite since it's
	// before the user spawns.  These functions are written to not
	// overflow
	if (host_client->num_backbuf) {
		SV_Printf ("WARNING %s: [SV_Modellist] Back buffered (%d), clearing",
					host_client->name, host_client->netchan.message.cursize);
		host_client->num_backbuf = 0;
		SZ_Clear (&host_client->netchan.message);
	}

	for (s = sv.model_precache + 1 + block.startmodel, i = 0, size = 0;
		 *s; i++, s++) {
		if (host_client->netchan.message.cursize + size >= (MAX_MSGLEN / 2))
			break;
		size += strlen (*s) + 1;
		block.models[i] = *s;
	}
	block.models[i] = "";

	// next msg
	if (*s)
		block.nextmodel = block.startmodel + i;
	else
		block.nextmodel = 0;

	NET_SVC_Emit (svc_modellist, &block, &host_client->netchan.message);
}

/*
	SV_PreSpawn_f
*/
void
SV_PreSpawn_f (void)
{
	unsigned int buf;
	unsigned int check;
	sizebuf_t  *msg;
	char *command;
	int size;
	net_svc_stufftext_t block;

	if (host_client->state != cs_connected) {
		SV_Printf ("prespawn not valid -- already spawned\n");
		return;
	}
	// handle the case of a level changing while a client was connecting
	if (atoi (Cmd_Argv (1)) != svs.spawncount) {
		SV_Printf ("SV_PreSpawn_f from different level\n");
		SV_New_f ();
		return;
	}

	buf = atoi (Cmd_Argv (2));
	if (buf >= sv.num_signon_buffers)
		buf = 0;

	if (!buf) {
		// should be three numbers following containing checksums
		check = atoi (Cmd_Argv (3));

//      Con_DPrintf("Client check = %d\n", check);

		if (sv_mapcheck->int_val && check != sv.worldmodel->checksum &&
			check != sv.worldmodel->checksum2) {
			SV_ClientPrintf (host_client, PRINT_HIGH,
							 "Map model file does not match (%s), %i != %i/%i.\n"
							 "You may need a new version of the map, or the proper install files.\n",
							 sv.modelname, check, sv.worldmodel->checksum,
							 sv.worldmodel->checksum2);
			SV_DropClient (host_client);
			return;
		}
		host_client->checksum = check;
	}

	host_client->prespawned = true;

	if (buf == sv.num_signon_buffers - 1)
		command = va ("cmd spawn %i 0\n", svs.spawncount);
	else
		command = va ("cmd prespawn %i %i\n", svs.spawncount, buf + 1);

	size = sv.signon_buffer_size[buf] + 1 + strlen(command) + 1;

	ClientReliableCheckBlock (host_client, size);
	if (host_client->num_backbuf)
		msg = &host_client->backbuf;
	else
		msg = &host_client->netchan.message;

	SZ_Write (msg, sv.signon_buffers[buf], sv.signon_buffer_size[buf]);

	block.commands = command;
	NET_SVC_Emit (svc_stufftext, &block, msg);
}

/*
	SV_Spawn_f
*/
void
SV_Spawn_f (void)
{
	int         i;
	client_t   *client;
	edict_t    *ent;
	pr_type_t  *val;
	int         n;
	net_svc_lightstyle_t lightstyle;
	net_svc_updatestatlong_t updatestatlong;
	net_svc_stufftext_t stufftext;

	if (host_client->state != cs_connected) {
		SV_Printf ("Spawn not valid -- already spawned\n");
		return;
	}
// handle the case of a level changing while a client was connecting
	if (atoi (Cmd_Argv (1)) != svs.spawncount) {
		SV_Printf ("SV_Spawn_f from different level\n");
		SV_New_f ();
		return;
	}
// make sure they're not trying to cheat by spawning without prespawning
	if (host_client->prespawned == false) {
		SV_BroadcastPrintf (PRINT_HIGH,
							va ("%s has been kicked for trying to spawn before prespawning!\n",
								host_client->name));
		SV_DropClient (host_client);
		return;
	}

	n = atoi (Cmd_Argv (2));

	// make sure n is valid
	if (n < 0 || n > MAX_CLIENTS) {
		SV_Printf ("SV_Spawn_f invalid client start\n");
		SV_New_f ();
		return;
	}

	host_client->spawned = true;

	// send all current names, colors, and frag counts
	// FIXME: is this a good thing?
	SZ_Clear (&host_client->netchan.message);

	// send current status of all other players

	// normally this could overflow, but no need to check due to backbuf
	for (i = n, client = svs.clients + n; i < MAX_CLIENTS; i++, client++)
		SV_FullClientUpdateToClient (client, host_client);

	// send all current light styles
	for (i = 0; i < MAX_LIGHTSTYLES; i++) {
		lightstyle.stylenum = i;
		lightstyle.map = sv.lightstyles[i] ?: "";
		SV_ReliableSVC_Emit (host_client, svc_lightstyle, &lightstyle);
	}

	// set up the edict
	ent = host_client->edict;

	memset (&ent->v, 0, sv_pr_state.progs->entityfields * 4);
	SVfloat (ent, colormap) = NUM_FOR_EDICT (&sv_pr_state, ent);
	SVfloat (ent, team) = 0;					// FIXME
	SVstring (ent, netname) = PR_SetString (&sv_pr_state, host_client->name);

	host_client->entgravity = 1.0;
	val = GetEdictFieldValue (&sv_pr_state, ent, "gravity");
	if (val)
		val->float_var = 1.0;
	host_client->maxspeed = sv_maxspeed->value;
	val = GetEdictFieldValue (&sv_pr_state, ent, "maxspeed");
	if (val)
		val->float_var = sv_maxspeed->value;

//
// force stats to be updated
//
	memset (host_client->stats, 0, sizeof (host_client->stats));

	updatestatlong.stat = STAT_TOTALSECRETS;
	updatestatlong.value = *sv_globals.total_secrets;
	SV_ReliableSVC_Emit (host_client, svc_updatestatlong, &updatestatlong);

	updatestatlong.stat = STAT_TOTALMONSTERS;
	updatestatlong.value = *sv_globals.total_monsters;
	SV_ReliableSVC_Emit (host_client, svc_updatestatlong, &updatestatlong);

	updatestatlong.stat = STAT_SECRETS;
	updatestatlong.value = *sv_globals.found_secrets;
	SV_ReliableSVC_Emit (host_client, svc_updatestatlong, &updatestatlong);

	updatestatlong.stat = STAT_MONSTERS;
	updatestatlong.value = *sv_globals.killed_monsters;
	SV_ReliableSVC_Emit (host_client, svc_updatestatlong, &updatestatlong);

	// get the client to check and download skins
	// when that is completed, a begin command will be issued
	stufftext.commands = "skins\n";
	SV_ReliableSVC_Emit (host_client, svc_stufftext, &stufftext);
}

/*
	SV_SpawnSpectator
*/
void
SV_SpawnSpectator (void)
{
	int         i;
	edict_t    *e;

	VectorCopy (vec3_origin, SVvector (sv_player, origin));
	VectorCopy (vec3_origin, SVvector (sv_player, view_ofs));
	SVvector (sv_player, view_ofs)[2] = 22;

	// search for an info_playerstart to spawn the spectator at
	for (i = MAX_CLIENTS - 1; i < sv.num_edicts; i++) {
		e = EDICT_NUM (&sv_pr_state, i);
		if (!strcmp (PR_GetString (&sv_pr_state, SVstring (e, classname)), "info_player_start")) {
			VectorCopy (SVvector (e, origin), SVvector (sv_player, origin));
			return;
		}
	}

}

/*
	SV_Begin_f
*/
void
SV_Begin_f (void)
{
	unsigned int pmodel = 0, emodel = 0;
	int         i;
	net_svc_setpause_t block;

	if (host_client->state == cs_spawned)
		return;							// don't begin again

	host_client->state = cs_spawned;

	// handle the case of a level changing while a client was connecting
	if (atoi (Cmd_Argv (1)) != svs.spawncount) {
		SV_Printf ("SV_Begin_f from different level\n");
		SV_New_f ();
		return;
	}

	// make sure they're not trying to cheat by beginning without spawning
	if (host_client->spawned == false) {
		SV_BroadcastPrintf (PRINT_HIGH,
							va ("%s has been kicked for trying to begin before spawning!\n"
									"Have a nice day!\n", // 1 string!
								host_client->name));
		SV_DropClient (host_client);
		return;
	}

	if (host_client->spectator) {
		SV_SpawnSpectator ();

		if (SpectatorConnect) {
			// copy spawn parms out of the client_t
			for (i = 0; i < NUM_SPAWN_PARMS; i++)
				sv_globals.parms[i] = host_client->spawn_parms[i];

			// call the spawn function
			*sv_globals.time = sv.time;
			*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
			PR_ExecuteProgram (&sv_pr_state, SpectatorConnect);
		}
	} else {
		// copy spawn parms out of the client_t
		for (i = 0; i < NUM_SPAWN_PARMS; i++)
			sv_globals.parms[i] = host_client->spawn_parms[i];

		// call the spawn function
		*sv_globals.time = sv.time;
		*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
		PR_ExecuteProgram (&sv_pr_state, sv_funcs.ClientConnect);

		// actually spawn the player
		*sv_globals.time = sv.time;
		*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
		PR_ExecuteProgram (&sv_pr_state, sv_funcs.PutClientInServer);
	}

	// clear the net statistics, because connecting gives a bogus picture
	host_client->last_check = -1;
	host_client->netchan.frame_latency = 0;
	host_client->netchan.frame_rate = 0;
	host_client->netchan.drop_count = 0;
	host_client->netchan.good_count = 0;

	// check he's not cheating
	pmodel = atoi (Info_ValueForKey (host_client->userinfo, "pmodel"));
	emodel = atoi (Info_ValueForKey (host_client->userinfo, "emodel"));

	if (pmodel != sv.model_player_checksum || emodel != sv.eyes_player_checksum)
		SV_BroadcastPrintf (PRINT_HIGH,
							"%s WARNING: non standard player/eyes model detected\n",
							host_client->name);

	// if we are paused, tell the client
	if (sv.paused) {
		block.paused = sv.paused;
		SV_ReliableSVC_Emit (host_client, svc_setpause, &block);
		SV_ClientPrintf (host_client, PRINT_HIGH, "Server is paused.\n");
	}
#if 0
//
// send a fixangle over the reliable channel to make sure it gets there
// Never send a roll angle, because savegames can catch the server
// in a state where it is expecting the client to correct the angle
// and it won't happen if the game was just loaded, so you wind up
// with a permanent head tilt
	ent = EDICT_NUM (&sv_pr_state, 1 + (host_client - svs.clients));
	setangles.angles[0] = SVvector (ent, angles)[0];
	setangles.angles[1] = SVvector (ent, angles)[1];
	setangles.angles[2] = 0;
	NET_SVC_Emit (svc_setangles, &setangle, &host_client->netchan.message);
#endif
}

//=============================================================================

/*
	SV_NextDownload_f
*/
void
SV_NextDownload_f (void)
{
	byte        buffer[1024];
	net_svc_download_t block;

	if (!host_client->download)
		return;

	block.size = host_client->downloadsize - host_client->downloadcount;
	if (block.size > 768)
		block.size = 768;
	block.size = Qread (host_client->download, buffer, block.size);

	host_client->downloadcount += block.size;
	block.percent = host_client->downloadcount * 100 /
				  (host_client->downloadsize ?: 1);

	block.data = buffer;
	SV_ReliableSVC_Emit (host_client, svc_download, &block);

	if (host_client->downloadcount != host_client->downloadsize)
		return;

	Qclose (host_client->download);
	host_client->download = NULL;

}

void
OutofBandPrintf (netadr_t where, char *fmt, ...)
{
	va_list     argptr;
	char        send[1024];

	send[0] = 0xff;
	send[1] = 0xff;
	send[2] = 0xff;
	send[3] = 0xff;
	send[4] = A2C_PRINT;
	va_start (argptr, fmt);
	vsnprintf (send + 5, sizeof (send - 5), fmt, argptr);
	va_end (argptr);

	NET_SendPacket (strlen (send) + 1, send, where);
}

/*
	SV_ParseUpload
*/
void
SV_ParseUpload ()
{
	net_svc_stufftext_t stufftext;
	net_clc_upload_t upload;

	if (NET_CLC_Upload_Parse (&upload, net_message)) {
		SV_Printf ("Parse Error\n");
		SV_DropClient (host_client);
		return;
	}

	if (!*host_client->uploadfn) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Upload denied\n");
		stufftext.commands = "stopul\n";
		SV_ReliableSVC_Emit (host_client, svc_stufftext, &stufftext);
		return;
	}

	if (!host_client->upload) {
		host_client->upload = Qopen (host_client->uploadfn, "wb");
		if (!host_client->upload) {
			SV_Printf ("Can't create %s\n", host_client->uploadfn);
			stufftext.commands = "stopul\n";
			SV_ReliableSVC_Emit (host_client, svc_stufftext, &stufftext);
			*host_client->uploadfn = 0;
			return;
		}
		SV_Printf ("Receiving %s from %d...\n", host_client->uploadfn,
					host_client->userid);
		if (host_client->remote_snap)
			OutofBandPrintf (host_client->snap_from,
							 "Server receiving %s from %d...\n",
							 host_client->uploadfn, host_client->userid);
	}

	Qwrite (host_client->upload, upload.data, upload.size);

	Con_DPrintf ("UPLOAD: %d received\n", upload.size);

	if (upload.percent != 100) {
		stufftext.commands = "nextul\n";
		SV_ReliableSVC_Emit (host_client, svc_stufftext, &stufftext);
	} else {
		Qclose (host_client->upload);
		host_client->upload = NULL;

		SV_Printf ("%s upload completed.\n", host_client->uploadfn);

		if (host_client->remote_snap) {
			char       *p;

			if ((p = strchr (host_client->uploadfn, '/')) != NULL)
				p++;
			else
				p = host_client->uploadfn;
			OutofBandPrintf (host_client->snap_from, "%s upload "
							 "completed.\nTo download, enter:\n"
							 "download %s\n", host_client->uploadfn, p);
		}
	}
}

/*
	SV_BeginDownload_f
*/
void
SV_BeginDownload_f (void)
{
	const char *name;
	VFile      *file;
	int         size;
	char        realname[MAX_OSPATH];
	int         zip;
	net_svc_download_t block;

	name = Cmd_Argv (1);
// hacked by zoid to allow more conrol over download
	// first off, no .. or global allow check
	if (strstr (name, "..") || !allow_download->int_val
		// leading dot is no good
		|| *name == '.'
		// next up, skin check
		|| (strncmp (name, "skins/", 6) == 0 && !allow_download_skins->int_val)
		// now models
		|| (strncmp (name, "progs/", 6) == 0 && !allow_download_models->int_val)
		// now sounds
		|| (strncmp (name, "sound/", 6) == 0 && !allow_download_sounds->int_val)
		// now maps (note special case for maps, must not be in pak)
		|| (strncmp (name, "maps/", 5) == 0 && !allow_download_maps->int_val)
		// MUST be in a subdirectory    
		|| !strstr (name, "/")) {		// don't allow anything with .. path
		block.size = -1;
		block.percent = 0;
		SV_ReliableSVC_Emit (host_client, svc_download, &block);
		return;
	}

	if (host_client->download) {
		Qclose (host_client->download);
		host_client->download = NULL;
	}
	// lowercase name (needed for casesen file systems)
	{
		char *p = Hunk_TempAlloc (strlen (name) + 1);
		char *n = p;

		while (*name)
			*p++ = tolower ((int) *name++);
		name = n;
	}

	zip = strchr (Info_ValueForKey (host_client->userinfo, "*cap"), 'z') != 0;

	size = _COM_FOpenFile (name, &file, realname, !zip);

	host_client->download = file;
	host_client->downloadsize = size;
	host_client->downloadcount = 0;

	if (!host_client->download
		// special check for maps, if it came from a pak file, don't allow
		// download  ZOID
		|| (strncmp (name, "maps/", 5) == 0 && file_from_pak)) {
		if (host_client->download) {
			Qclose (host_client->download);
			host_client->download = NULL;
		}

		SV_Printf ("Couldn't download %s to %s\n", name, host_client->name);

		block.size = -1;
		block.percent = 0;
		SV_ReliableSVC_Emit (host_client, svc_download, &block);
		return;
	}

	if (zip && strcmp (realname, name)) {
		SV_Printf ("download renamed to %s\n", realname);

		block.size = -2;
		block.percent = 0;
		block.name = realname;
		SV_ReliableSVC_Emit (host_client, svc_download, &block);
	}

	SV_NextDownload_f ();
	SV_Printf ("Downloading %s to %s\n", name, host_client->name);
}

//=============================================================================

/*
	SV_Say
*/
void
SV_Say (qboolean team)
{
	client_t   *client;
	int         j, tmp;
	char       *p;
	char        text[2048];
	char        t1[32];
	const char *t2;
	char       *i;

	if (Cmd_Argc () < 2)
		return;

	if (team) {
		strncpy (t1, Info_ValueForKey (host_client->userinfo, "team"), 31);
		t1[31] = 0;
	}

	if (host_client->spectator && (!sv_spectalk->int_val || team))
		snprintf (text, sizeof (text), "[SPEC] %s: ", host_client->name);
	else if (team)
		snprintf (text, sizeof (text), "(%s): ", host_client->name);
	else {
		snprintf (text, sizeof (text), "%s: ", host_client->name);
	}

	if (fp_messages) {
		if (!sv.paused && realtime < host_client->lockedtill) {
			SV_ClientPrintf (host_client, PRINT_CHAT,
							 "You can't talk for %d more seconds\n",
							 (int) (host_client->lockedtill - realtime));
			return;
		}
		tmp = host_client->whensaidhead - fp_messages + 1;
		if (tmp < 0)
			tmp = 10 + tmp;
		if (!sv.paused &&
			host_client->whensaid[tmp]
			&& (realtime - host_client->whensaid[tmp] < fp_persecond)) {
			host_client->lockedtill = realtime + fp_secondsdead;
			if (fp_msg[0])
				SV_ClientPrintf (host_client, PRINT_CHAT,
								 "FloodProt: %s\n", fp_msg);
			else
				SV_ClientPrintf (host_client, PRINT_CHAT,
								 "FloodProt: You can't talk for %d seconds.\n",
								 fp_secondsdead);
			return;
		}
		host_client->whensaidhead++;
		if (host_client->whensaidhead > 9)
			host_client->whensaidhead = 0;
		host_client->whensaid[host_client->whensaidhead] = realtime;
	}

	p = Hunk_TempAlloc (strlen(Cmd_Args (1)) + 1);
	strcpy (p, Cmd_Args (1));

	if (*p == '"') {
		p++;
		p[strlen (p) - 1] = 0;
	}

	for (i = p; *i; i++)
		if (*i == 13) { // ^M
			if (sv_kickfake->int_val) {
				SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked for attempting to fake messages\n", host_client->name);
				SV_ClientPrintf (host_client, PRINT_HIGH, "You were kicked for attempting to fake messages\n");
				SV_DropClient (host_client);
				return;
			} else
				*i = '#';
		}

	strncat (text, p, sizeof (text) - strlen (text));
	strncat (text, "\n", sizeof (text) - strlen (text));

	SV_Printf ("%s", text);

	for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
		if (client->state < cs_connected)	// Clients connecting can hear.
			continue;
		if (host_client->spectator && !sv_spectalk->int_val)
			if (!client->spectator)
				continue;

		if (team) {
			// the spectator team
			if (host_client->spectator) {
				if (!client->spectator)
					continue;
			} else {
				t2 = Info_ValueForKey (client->userinfo, "team");
				if (strcmp (t1, t2) || client->spectator)
					continue;			// on different teams
			}
		}
		SV_ClientPrintf (client, PRINT_CHAT, "%s", text);
	}
}

/*
	SV_Say_f
*/
void
SV_Say_f (void)
{
	SV_Say (false);
}

/*
	SV_Say_Team_f
*/
void
SV_Say_Team_f (void)
{
	SV_Say (true);
}



//============================================================================

/*
	SV_Pings_f

	The client is showing the scoreboard, so send new ping times for all
	clients
*/
void
SV_Pings_f (void)
{
	client_t   *client;
	int         j;
	net_svc_updateping_t ping;
	net_svc_updatepl_t pl;

	for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
		if (client->state != cs_spawned)
			continue;

		ping.player = j;
		ping.ping = SV_CalcPing (client);
		SV_ReliableSVC_Emit (host_client, svc_updateping, &ping);

		pl.player = j;
		pl.packetloss = client->lossage;
		SV_ReliableSVC_Emit (host_client, svc_updatepl, &pl);
	}
}



/*
	SV_Kill_f
*/
void
SV_Kill_f (void)
{
	if (SVfloat (sv_player, health) <= 0) {
		SV_BeginRedirect (RD_CLIENT);
		SV_ClientPrintf (host_client, PRINT_HIGH,
						 "Can't suicide -- already dead!\n");
		SV_EndRedirect ();
		return;
	}

	*sv_globals.time = sv.time;
	*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
	PR_ExecuteProgram (&sv_pr_state, sv_funcs.ClientKill);
}

/*
	SV_TogglePause
*/
void
SV_TogglePause (const char *msg)
{
	int         i;
	client_t   *cl;
	net_svc_setpause_t block;

	sv.paused ^= 1;

	if (msg)
		SV_BroadcastPrintf (PRINT_HIGH, "%s", msg);

	// send notification to all clients
	for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
		if (!cl->state)
			continue;
		block.paused = sv.paused;
		SV_ReliableSVC_Emit (cl, svc_setpause, &block);
	}
}


/*
	SV_Pause_f
*/
void
SV_Pause_f (void)
{
	static double lastpausetime;
	double      currenttime;
	char        st[sizeof (host_client->name) + 32];

	currenttime = Sys_DoubleTime ();

	if (lastpausetime + 1 > currenttime) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Pause flood not allowed.\n");
		return;
	}

	lastpausetime = currenttime;

	if (!pausable->int_val) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Pause not allowed.\n");
		return;
	}

	if (host_client->spectator) {
		SV_ClientPrintf (host_client, PRINT_HIGH,
						 "Spectators can not pause.\n");
		return;
	}

	if (!sv.paused)
		snprintf (st, sizeof (st), "%s paused the game\n", host_client->name);
	else
		snprintf (st, sizeof (st), "%s unpaused the game\n", host_client->name);

	SV_TogglePause (st);
}


/*
	SV_Drop_f

	The client is going to disconnect, so remove the connection immediately
*/
void
SV_Drop_f (void)
{
	SV_EndRedirect ();
	if (!host_client->spectator)
		SV_BroadcastPrintf (PRINT_HIGH, "%s dropped\n", host_client->name);
	SV_DropClient (host_client);
}

/*
	SV_PTrack_f

	Change the bandwidth estimate for a client
*/
void
SV_PTrack_f (void)
{
	int         i;
	edict_t    *ent, *tent;

	if (!host_client->spectator)
		return;

	if (Cmd_Argc () != 2) {
		// turn off tracking
		host_client->spec_track = 0;
		ent = EDICT_NUM (&sv_pr_state, host_client - svs.clients + 1);
		tent = EDICT_NUM (&sv_pr_state, 0);
		SVentity (ent, goalentity) = EDICT_TO_PROG (&sv_pr_state, tent);
		return;
	}

	i = atoi (Cmd_Argv (1));
	if (i < 0 || i >= MAX_CLIENTS || svs.clients[i].state != cs_spawned ||
		svs.clients[i].spectator) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Invalid client to track\n");
		host_client->spec_track = 0;
		ent = EDICT_NUM (&sv_pr_state, host_client - svs.clients + 1);
		tent = EDICT_NUM (&sv_pr_state, 0);
		SVentity (ent, goalentity) = EDICT_TO_PROG (&sv_pr_state, tent);
		return;
	}
	host_client->spec_track = i + 1;	// now tracking

	ent = EDICT_NUM (&sv_pr_state, host_client - svs.clients + 1);
	tent = EDICT_NUM (&sv_pr_state, i + 1);
	SVentity (ent, goalentity) = EDICT_TO_PROG (&sv_pr_state, tent);
}


/*
	SV_Rate_f

	Change the bandwidth estimate for a client
*/
void
SV_Rate_f (void)
{
	int         rate;

	if (Cmd_Argc () != 2) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Current rate is %i\n",
						 (int) (1.0 / host_client->netchan.rate + 0.5));
		return;
	}

	rate = atoi (Cmd_Argv (1));
	if ((sv_maxrate->int_val) && (rate > sv_maxrate->int_val)) {
		rate = bound (500, rate, sv_maxrate->int_val);
	} else {
		rate = bound (500, rate, 10000);
	}

	SV_ClientPrintf (host_client, PRINT_HIGH, "Net rate set to %i\n", rate);
	host_client->netchan.rate = 1.0 / rate;
}


/*
	SV_Msg_f

	Change the message level for a client
*/
void
SV_Msg_f (void)
{
	if (Cmd_Argc () != 2) {
		SV_ClientPrintf (host_client, PRINT_HIGH, "Current msg level is %i\n",
						 host_client->messagelevel);
		return;
	}

	host_client->messagelevel = atoi (Cmd_Argv (1));

	SV_ClientPrintf (host_client, PRINT_HIGH, "Msg level set to %i\n",
					 host_client->messagelevel);
}

/*
	SV_SetInfo_f

	Allow clients to change userinfo
*/
void
SV_SetInfo_f (void)
{
	net_svc_setinfo_t block;

	if (Cmd_Argc () == 1) {
		SV_Printf ("User info settings:\n");
		Info_Print (host_client->userinfo);
		return;
	}

	if (Cmd_Argc () != 3) {
		SV_Printf ("usage: setinfo [ <key> <value> ]\n");
		return;
	}

	if (Cmd_Argv (1)[0] == '*')
		return;							// don't set priveledged values


	if (UserInfoCallback) {
		*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
		G_var (&sv_pr_state, OFS_PARM0, string) = PR_SetString (&sv_pr_state,
																Cmd_Argv (1));
		G_var (&sv_pr_state, OFS_PARM1, string) = PR_SetString (&sv_pr_state,
																Cmd_Argv (2));
		PR_ExecuteProgram (&sv_pr_state, UserInfoCallback);
		return;
	} else {
		char        oldval[MAX_INFO_STRING];

		strcpy (oldval, Info_ValueForKey (host_client->userinfo, Cmd_Argv (1)));
		Info_SetValueForKey (host_client->userinfo, Cmd_Argv (1), Cmd_Argv (2),
							 !sv_highchars->int_val);
		if (strequal
			(Info_ValueForKey (host_client->userinfo, Cmd_Argv (1)), oldval))
			return;								// key hasn't changed
	}

	// process any changed values
	SV_ExtractFromUserinfo (host_client);

	if (Info_FilterForKey (Cmd_Argv (1), client_info_filters)) {
		block.slot = host_client - svs.clients;
		block.key = Cmd_Argv (1);
		block.value = Info_ValueForKey (host_client->userinfo, Cmd_Argv (1));
		NET_SVC_Emit (svc_setinfo, &block, &sv.reliable_datagram);
	}
}

/*
	SV_ShowServerinfo_f

	Dump serverinfo into a string
*/
void
SV_ShowServerinfo_f (void)
{
	Info_Print (svs.info);
}

void
SV_NoSnap_f (void)
{
	if (*host_client->uploadfn) {
		*host_client->uploadfn = 0;
		SV_BroadcastPrintf (PRINT_HIGH, "%s refused remote screenshot\n",
							host_client->name);
	}
}

typedef struct {
	const char *name;
	void        (*func) (void);
	int         no_redirect;
} ucmd_t;

ucmd_t      ucmds[] = {
	{"new", SV_New_f},
	{"modellist", SV_Modellist_f},
	{"soundlist", SV_Soundlist_f},
	{"prespawn", SV_PreSpawn_f},
	{"spawn", SV_Spawn_f},
	{"begin", SV_Begin_f, 1},

	{"drop", SV_Drop_f},
	{"pings", SV_Pings_f},

// issued by hand at client consoles    
	{"rate", SV_Rate_f},
	{"kill", SV_Kill_f, 1},
	{"pause", SV_Pause_f, 1},
	{"msg", SV_Msg_f},

	{"say", SV_Say_f, 1},
	{"say_team", SV_Say_Team_f, 1},

	{"setinfo", SV_SetInfo_f, 1},

	{"serverinfo", SV_ShowServerinfo_f},

	{"download", SV_BeginDownload_f, 1},
	{"nextdl", SV_NextDownload_f},

	{"ptrack", SV_PTrack_f},			// ZOID - used with autocam

	{"snap", SV_NoSnap_f},

};

static int
ucmds_compare (const void *_a, const void *_b)
{
	ucmd_t *a = (ucmd_t*)_a;
	ucmd_t *b = (ucmd_t*)_b;
	return strcmp (a->name, b->name);
}

/*
	SV_ExecuteUserCommand

	Uhh...execute user command. :)
*/
void
SV_ExecuteUserCommand (const char *s)
{
	ucmd_t     *u;
	ucmd_t		cmd;

	Cmd_TokenizeString (s);
	sv_player = host_client->edict;
	cmd.name = Cmd_Argv(0);

	u = (ucmd_t*) bsearch (&cmd, ucmds, sizeof (ucmds) / sizeof (ucmds[0]),
						   sizeof (ucmds[0]), ucmds_compare);

	if (!u) {
		SV_BeginRedirect (RD_CLIENT);
		SV_Printf ("Bad user command: %s\n", Cmd_Argv (0));
		SV_EndRedirect ();
	} else {
		if (!u->no_redirect)
			SV_BeginRedirect (RD_CLIENT);
		u->func ();
		if (!u->no_redirect)
			SV_EndRedirect ();
	}
}

/*
	USER CMD EXECUTION
*/

/*
	SV_CalcRoll

	Used by view and sv_user
*/
float
SV_CalcRoll (vec3_t angles, vec3_t velocity)
{
	vec3_t      forward, right, up;
	float       sign;
	float       side;
	float       value;

	AngleVectors (angles, forward, right, up);
	side = DotProduct (velocity, right);
	sign = side < 0 ? -1 : 1;
	side = fabs (side);

	value = cl_rollangle->value;

	if (side < cl_rollspeed->value)
		side = side * value / cl_rollspeed->value;
	else
		side = value;

	return side * sign;

}




//============================================================================

vec3_t      pmove_mins, pmove_maxs;

/*
	AddLinksToPmove
*/
void
AddLinksToPmove (areanode_t *node)
{
	link_t     *l, *next;
	edict_t    *check;
	int         pl;
	int         i;
	physent_t  *pe;

	pl = EDICT_TO_PROG (&sv_pr_state, sv_player);

	// touch linked edicts
	for (l = node->solid_edicts.next; l != &node->solid_edicts; l = next) {
		next = l->next;
		check = EDICT_FROM_AREA (l);

		if (SVentity (check, owner) == pl)
			continue;					// player's own missile
		if (SVfloat (check, solid) == SOLID_BSP
			|| SVfloat (check, solid) == SOLID_BBOX || SVfloat (check, solid) == SOLID_SLIDEBOX) {
			if (check == sv_player)
				continue;

			for (i = 0; i < 3; i++)
				if (SVvector (check, absmin)[i] > pmove_maxs[i]
					|| SVvector (check, absmax)[i] < pmove_mins[i])
					break;
			if (i != 3)
				continue;
			if (pmove.numphysent == MAX_PHYSENTS)
				return;
			pe = &pmove.physents[pmove.numphysent];
			pmove.numphysent++;

			VectorCopy (SVvector (check, origin), pe->origin);
			pe->info = NUM_FOR_EDICT (&sv_pr_state, check);

			if (sv_fields.rotated_bbox != -1
				&& SVinteger (check, rotated_bbox)) {
				int h = SVinteger (check, rotated_bbox) - 1;
				extern clip_hull_t *pf_hull_list[];
				pe->hull = pf_hull_list[h]->hulls[1];
			} else {
				pe->hull = 0;
				if (SVfloat (check, solid) == SOLID_BSP) {
					pe->model = sv.models[(int) (SVfloat (check, modelindex))];
				} else {
					pe->model = NULL;
					VectorCopy (SVvector (check, mins), pe->mins);
					VectorCopy (SVvector (check, maxs), pe->maxs);
				}
			}
		}
	}

	// recurse down both sides
	if (node->axis == -1)
		return;

	if (pmove_maxs[node->axis] > node->dist)
		AddLinksToPmove (node->children[0]);

	if (pmove_mins[node->axis] < node->dist)
		AddLinksToPmove (node->children[1]);
}


/*
	AddAllEntsToPmove

	For debugging
*/
void
AddAllEntsToPmove (void)
{
	int         e;
	edict_t    *check;
	int         i;
	physent_t  *pe;
	int         pl;

	pl = EDICT_TO_PROG (&sv_pr_state, sv_player);
	check = NEXT_EDICT (&sv_pr_state, sv.edicts);
	for (e = 1; e < sv.num_edicts; e++,
								   check = NEXT_EDICT (&sv_pr_state, check)) {
		if (check->free)
			continue;
		if (SVentity (check, owner) == pl)
			continue;
		if (SVfloat (check, solid) == SOLID_BSP
			|| SVfloat (check, solid) == SOLID_BBOX
			|| SVfloat (check, solid) == SOLID_SLIDEBOX) {
			if (check == sv_player)
				continue;

			for (i = 0; i < 3; i++)
				if (SVvector (check, absmin)[i] > pmove_maxs[i]
					|| SVvector (check, absmax)[i] < pmove_mins[i])
					break;
			if (i != 3)
				continue;
			pe = &pmove.physents[pmove.numphysent];

			VectorCopy (SVvector (check, origin), pe->origin);
			pmove.physents[pmove.numphysent].info = e;
			if (SVfloat (check, solid) == SOLID_BSP)
				pe->model = sv.models[(int) (SVfloat (check, modelindex))];
			else {
				pe->model = NULL;
				VectorCopy (SVvector (check, mins), pe->mins);
				VectorCopy (SVvector (check, maxs), pe->maxs);
			}

			if (++pmove.numphysent == MAX_PHYSENTS)
				break;
		}
	}
}

/*
	SV_PreRunCmd

	Done before running a player command.  Clears the touch array
*/
byte        playertouch[(MAX_EDICTS + 7) / 8];

void
SV_PreRunCmd (void)
{
	memset (playertouch, 0, sizeof (playertouch));
}

/*
	SV_RunCmd
*/

void
SV_RunCmd (usercmd_t *ucmd, qboolean inside)
{
	edict_t    *ent;
	int         i, n, oldmsec;
	double      tmp_time;
	int         tmp_time1;

	if (!inside) {						// prevent infinite loop
		host_client->msecs += ucmd->msec;

		if ((sv_timekick->int_val)
			&& ((tmp_time = realtime - host_client->last_check) >=
				sv_timekick_interval->value)) {

			tmp_time1 = tmp_time * (1000 + sv_timekick_fuzz->value);

			if ((host_client->last_check != -1)	// don't do it if new player
				&& (host_client->msecs > tmp_time1)) {
				host_client->msec_cheating++;
				SV_BroadcastPrintf (PRINT_HIGH,
									va
									("%s thinks there are %d ms in %d seconds (Strike %d/%d)\n",
									 host_client->name, host_client->msecs,
									 (int) tmp_time, host_client->msec_cheating,
									 sv_timekick->int_val));

				if (host_client->msec_cheating >= sv_timekick->int_val) {
					SV_BroadcastPrintf (PRINT_HIGH, va ("Strike %d for %s!!\n",
														host_client->
														msec_cheating,
														host_client->name));
					SV_BroadcastPrintf (PRINT_HIGH,
										"Please see http://www.quakeforge.net/speed_cheat.php for infomation on QuakeForge's time cheat protection, and to explain how some may be cheating without knowing it.\n");
					SV_DropClient (host_client);
				}
			}

			host_client->msecs = 0;
			host_client->last_check = realtime;
		}
	}

	cmd = *ucmd;

	// chop up very long commands
	if (cmd.msec > 50) {
		oldmsec = ucmd->msec;
		cmd.msec = oldmsec / 2;
		SV_RunCmd (&cmd, 1);
		cmd.msec = oldmsec / 2;
		cmd.impulse = 0;
		SV_RunCmd (&cmd, 1);
		return;
	}

	if (!SVfloat (sv_player, fixangle))
		VectorCopy (ucmd->angles, SVvector (sv_player, v_angle));

	SVfloat (sv_player, button0) = ucmd->buttons & 1;
// 1999-10-29 +USE fix by Maddes  start
	if (!nouse) {
		SVfloat (sv_player, button1) = (ucmd->buttons & 4) >> 2;
	}
// 1999-10-29 +USE fix by Maddes  end
	SVfloat (sv_player, button2) = (ucmd->buttons & 2) >> 1;
	if (ucmd->impulse)
		SVfloat (sv_player, impulse) = ucmd->impulse;
	if (host_client->cuff_time > realtime)
		SVfloat (sv_player, button0) = SVfloat (sv_player, impulse) = 0;

//
// angles
// show 1/3 the pitch angle and all the roll angle
	if (SVfloat (sv_player, health) > 0) {
		if (!SVfloat (sv_player, fixangle)) {
			SVvector (sv_player, angles)[PITCH] = -SVvector (sv_player, v_angle)[PITCH] / 3;
			SVvector (sv_player, angles)[YAW] = SVvector (sv_player, v_angle)[YAW];
		}
		SVvector (sv_player, angles)[ROLL] =
			SV_CalcRoll (SVvector (sv_player, angles), SVvector (sv_player, velocity)) * 4;
	}

	sv_frametime = min (0.1, ucmd->msec * 0.001);

	if (!host_client->spectator) {
		*sv_globals.frametime = sv_frametime;

		*sv_globals.time = sv.time;
		*sv_globals.self = EDICT_TO_PROG (&sv_pr_state,
															sv_player);
		PR_ExecuteProgram (&sv_pr_state,
						   sv_funcs.PlayerPreThink);

		SV_RunThink (sv_player);
	}

	for (i = 0; i < 3; i++)
		pmove.origin[i] =
			SVvector (sv_player, origin)[i]
			+ (SVvector (sv_player, mins)[i] - player_mins[i]);
	VectorCopy (SVvector (sv_player, velocity), pmove.velocity);
	VectorCopy (SVvector (sv_player, v_angle), pmove.angles);

	pmove.flying = SVfloat (sv_player, movetype) == MOVETYPE_FLY;
	pmove.spectator = host_client->spectator;
	pmove.waterjumptime = SVfloat (sv_player, teleport_time);
	pmove.numphysent = 1;
	pmove.physents[0].model = sv.worldmodel;
	pmove.cmd = *ucmd;
	pmove.dead = SVfloat (sv_player, health) <= 0;
	pmove.oldbuttons = host_client->oldbuttons;

	movevars.entgravity = host_client->entgravity;
	movevars.maxspeed = host_client->maxspeed;

	for (i = 0; i < 3; i++) {
		pmove_mins[i] = pmove.origin[i] - 256;
		pmove_maxs[i] = pmove.origin[i] + 256;
	}

#if 0
	AddAllEntsToPmove ();
#else
	AddLinksToPmove (sv_areanodes);
#endif

#if 0
	{
		int         before, after;

		before = PM_TestPlayerPosition (pmove.origin);
		PlayerMove ();
		after = PM_TestPlayerPosition (pmove.origin);

		if (SVfloat (sv_player, health) > 0 && before && !after)
			SV_Printf ("player %s got stuck in playermove!!!!\n",
						host_client->name);
	}
#else
	PlayerMove ();
#endif

	host_client->oldbuttons = pmove.oldbuttons;
	SVfloat (sv_player, teleport_time) = pmove.waterjumptime;
	SVfloat (sv_player, waterlevel) = waterlevel;
	SVfloat (sv_player, watertype) = watertype;
	if (onground != -1) {
		SVfloat (sv_player, flags) = (int) SVfloat (sv_player, flags) | FL_ONGROUND;
		SVentity (sv_player, groundentity) =
			EDICT_TO_PROG (&sv_pr_state, EDICT_NUM (&sv_pr_state, pmove.physents[onground].info));
	} else {
		SVfloat (sv_player, flags) = (int) SVfloat (sv_player, flags) & ~FL_ONGROUND;
	}
	for (i = 0; i < 3; i++)
		SVvector (sv_player, origin)[i] =
			pmove.origin[i] - (SVvector (sv_player, mins)[i] - player_mins[i]);

#if 0
	// truncate velocity the same way the net protocol will
	for (i = 0; i < 3; i++)
		SVvector (sv_player, velocity)[i] = (int) pmove.velocity[i];
#else
	VectorCopy (pmove.velocity, SVvector (sv_player, velocity));
#endif

	VectorCopy (pmove.angles, SVvector (sv_player, v_angle));

	if (!host_client->spectator) {
		// link into place and touch triggers
		SV_LinkEdict (sv_player, true);

		// touch other objects
		for (i = 0; i < pmove.numtouch; i++) {
			n = pmove.physents[pmove.touchindex[i]].info;
			ent = EDICT_NUM (&sv_pr_state, n);
			if (!SVfunc (ent, touch) || (playertouch[n / 8] & (1 << (n % 8))))
				continue;
			*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, ent);
			*sv_globals.other = EDICT_TO_PROG (&sv_pr_state, sv_player);
			PR_ExecuteProgram (&sv_pr_state, SVfunc (ent, touch));
			playertouch[n / 8] |= 1 << (n % 8);
		}
	}
}

/*
	SV_PostRunCmd

	Done after running a player command.
*/
void
SV_PostRunCmd (void)
{
	// run post-think

	if (!host_client->spectator) {
		*sv_globals.time = sv.time;
		*sv_globals.self = EDICT_TO_PROG (&sv_pr_state,
															sv_player);
		PR_ExecuteProgram (&sv_pr_state,
						   sv_funcs.PlayerPostThink);
		SV_RunNewmis ();
	} else if (SpectatorThink) {
		*sv_globals.time = sv.time;
		*sv_globals.self = EDICT_TO_PROG (&sv_pr_state,
															sv_player);
		PR_ExecuteProgram (&sv_pr_state, SpectatorThink);
	}
}

void
SV_ParseNOP ()
{
}

void
SV_ParseDelta ()
{
	net_clc_delta_t block;

	if (NET_CLC_Delta_Parse (&block, net_message)) {
		SV_Printf ("Parse Error\n");
		SV_DropClient (host_client);
		return;
	}

	host_client->delta_sequence = block.sequence;
}

void
SV_ParseMove ()
{
	net_clc_move_t block;

	block.seq_hash = host_client->netchan.incoming_sequence; // FIXME
	if (NET_CLC_Move_Parse (&block, net_message)) {
		SV_Printf ("Parse Error\n");
		SV_DropClient (host_client);
		return;
	}

	if (move_issued)
		return;				// someone is trying to cheat...

	move_issued = true;
	host_client->lossage = block.packetloss;

	if (host_client->state != cs_spawned)
		return;

	if (block.calculatedchecksum != block.checksum) {
		Con_DPrintf	("Failed command checksum for %s(%d) (%d != %d)\n",
			 host_client->name, host_client->netchan.incoming_sequence,
			 block.checksum, block.calculatedchecksum);
			return;
	}

	if (!sv.paused) {
		SV_PreRunCmd ();

		if (net_drop < 20) {
			while (net_drop > 2) {
				SV_RunCmd (&host_client->lastcmd, 0);
				net_drop--;
			}
			if (net_drop > 1)
				SV_RunCmd (&block.usercmd[0], 0);
			if (net_drop > 0)
				SV_RunCmd (&block.usercmd[1], 0);
		}
		SV_RunCmd (&block.usercmd[2], 0);

		SV_PostRunCmd ();
	}

	host_client->lastcmd = block.usercmd[2];
	host_client->lastcmd.buttons = 0;	// avoid multiple fires on lag
}

void
SV_ParseStringcmd ()
{
	net_clc_stringcmd_t block;

	if (NET_CLC_Stringcmd_Parse (&block, net_message)) {
		SV_Printf ("Parse Error\n");
		SV_DropClient (host_client);
		return;
	}

	SV_ExecuteUserCommand (block.command);
}

void
SV_ParseTMove ()
{
	net_clc_tmove_t block;

	if (NET_CLC_TMove_Parse (&block, net_message)) {
		SV_Printf ("Parse Error\n");
		SV_DropClient (host_client);
		return;
	}

	// only allowed by spectators
	if (host_client->spectator) {
		VectorCopy (block.origin, SVvector (sv_player, origin));
		SV_LinkEdict (sv_player, false);
	}
}


//typedef void (*sv_parse_t) (net_clc_any_t *block);
typedef void (*sv_parse_t) (void);
static sv_parse_t sv_parse_jumptable [] = {
	[clc_nop] = (sv_parse_t) SV_ParseNOP,
	[clc_move] = (sv_parse_t) SV_ParseMove,
	[clc_stringcmd] = (sv_parse_t) SV_ParseStringcmd,
	[clc_delta] = (sv_parse_t) SV_ParseDelta,
	[clc_tmove] = (sv_parse_t) SV_ParseTMove,
	[clc_upload] = (sv_parse_t) SV_ParseUpload,
};


/*
	SV_ExecuteClientMessage

	The current net_message is parsed for the given client
*/
void
SV_ExecuteClientMessage (client_t *cl)
{
	int         cmd;
	client_frame_t *frame;

	move_issued = false;	// only allow one move command

	// calc ping time
	frame = &cl->frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];
	frame->ping_time = realtime - frame->senttime;

	// make sure the reply sequence number matches the incoming
	// sequence number 
	if (cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence)
		cl->netchan.outgoing_sequence = cl->netchan.incoming_sequence;
	else
		cl->send_message = false;		// don't reply, sequences have
										// slipped      

	// save time for ping calculations
	cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime;
	cl->frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;

	host_client = cl;
	sv_player = host_client->edict;

	// mark time so clients will know how much to predict
	// other players
	cl->localtime = sv.time;
	cl->delta_sequence = -1;			// no delta unless requested
	while (1) {
		if (cl->state <= cs_zombie)
			break; // something disconnected them

		if (net_message->badread) {
			SV_Printf ("SV_ReadClientMessage: badread\n");
			SV_DropClient (cl);
			return;
		}

		cmd = MSG_ReadByte (net_message);
		if (cmd == -1)
			return;						// Ender: Patched :)

		if (cmd < 0 || cmd >= sizeof (sv_parse_jumptable) /
			sizeof (sv_parse_t) || !sv_parse_jumptable[cmd]) {
			SV_Printf ("SV_ExecuteClientMessage: Jumptable Mismatch\n");
			SV_DropClient (host_client);
		}

		sv_parse_jumptable[cmd] ();

	}
}

/*
	SV_UserInit
*/
void
SV_UserInit (void)
{
	qsort (ucmds, sizeof (ucmds) / sizeof (ucmds[0]), sizeof (ucmds[0]),
		   ucmds_compare);
	cl_rollspeed = Cvar_Get ("cl_rollspeed", "200", CVAR_NONE, NULL,
			"How quickly a player straightens out after strafing");
	cl_rollangle = Cvar_Get ("cl_rollangle", "2", CVAR_NONE, NULL,
			"How much a player's screen tilts when strafing");
	sv_spectalk = Cvar_Get ("sv_spectalk", "1", CVAR_NONE, NULL,
			"Toggles the ability of spectators to talk to players");
	sv_mapcheck = Cvar_Get ("sv_mapcheck", "1", CVAR_NONE, NULL, 
		"Toggle the use of map checksumming to check for players who edit maps to cheat");
	sv_kickfake = Cvar_Get ("sv_kickfake", "0", CVAR_NONE, NULL,
			"Kick users sending to send fake talk messages");
}