/*
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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// sv_user.c -- server code for moving users

#include "quakedef.h"

#ifndef CLIENTONLY
#include "pr_common.h"

#include <ctype.h>
#define Q2EDICT_NUM(i) (q2edict_t*)((char *)ge->edicts+i*ge->edict_size)
#define Q2NUM_FOR_EDICT(ent) (((char *)ent - (char *)ge->edicts) / ge->edict_size)
hull_t *SV_HullForEntity (edict_t *ent, int hullnum, vec3_t mins, vec3_t maxs, vec3_t offset);

edict_t	*sv_player;

usercmd_t	cmd;

#ifdef SERVERONLY
cvar_t	cl_rollspeed = SCVAR("cl_rollspeed", "200");
cvar_t	cl_rollangle = SCVAR("cl_rollangle", "2.0");
#else
extern cvar_t	cl_rollspeed;
extern cvar_t	cl_rollangle;
#endif
cvar_t	sv_spectalk	= SCVAR("sv_spectalk", "1");

cvar_t	sv_mapcheck	= SCVAR("sv_mapcheck", "1");

cvar_t	sv_fullredirect = CVARD("sv_fullredirect", "", "This is the ip:port to redirect players to when the server is full");
cvar_t	sv_antilag			= CVARFD("sv_antilag", "1", CVAR_SERVERINFO, "Attempt to backdate impacts to compensate for lag. 0=completely off. 1=mod-controlled. 2=forced, which might break certain uses of traceline.");
cvar_t	sv_antilag_frac		= CVARF("sv_antilag_frac", "1", CVAR_SERVERINFO);
cvar_t	sv_cheatpc				= CVAR("sv_cheatpc", "125");
cvar_t	sv_cheatspeedchecktime	= CVAR("sv_cheatspeedchecktime", "30");
cvar_t	sv_playermodelchecks	= CVAR("sv_playermodelchecks", "0");
cvar_t	sv_ping_ignorepl		= CVARD("sv_ping_ignorepl", "0", "If 1, ping times reported for players will ignore the effects of packetloss on ping times. 0 is slightly more honest, but less useful for connection diagnosis.");
cvar_t	sv_protocol_nq		= CVARD("sv_protocol_nq", "0", "Specifies the default protocol to use for new NQ clients. Supported values are\n0 = autodetect\n15 = vanilla\n666 = fitzquake\n999 = rmq protocol\nThe sv_bigcoords cvar forces upgrades as required.");

cvar_t	sv_cmdlikercon	= SCVAR("sv_cmdlikercon", "0");	//set to 1 to allow a password of username:password instead of the correct rcon password.
cvar_t cmd_allowaccess	= SCVAR("cmd_allowaccess", "0");	//set to 1 to allow cmd to execute console commands on the server.
cvar_t cmd_gamecodelevel	= SCVAR("cmd_gamecodelevel", "50");	//execution level which gamecode is told about (for unrecognised commands)

cvar_t	sv_pure	= CVARFD("sv_pure", "", CVAR_SERVERINFO, "The most evil cvar in the world.");
cvar_t	sv_nqplayerphysics	= CVARAD("sv_nqplayerphysics", "0", "sv_nomsec", "Disregard player prediction and run NQ-style player physics instead. This can be used for compatibility with mods that expect exact behaviour.");
cvar_t	sv_edgefriction	= CVARAF("sv_edgefriction", "2",
								 "edgefriction", 0);

cvar_t	sv_brokenmovetypes	= CVARD("sv_brokenmovetypes", "0", "Emulate vanilla quakeworld by forcing MOVETYPE_WALK on all players. Shouldn't be used for any games other than QuakeWorld.");

cvar_t	sv_chatfilter	= CVAR("sv_chatfilter", "0");

cvar_t	sv_floodprotect				= CVAR("sv_floodprotect", "1");
cvar_t	sv_floodprotect_messages	= CVAR("sv_floodprotect_messages", "4");
cvar_t	sv_floodprotect_interval	= CVAR("sv_floodprotect_interval", "4");
cvar_t  sv_floodprotect_silencetime	= CVAR("sv_floodprotect_silencetime", "10");
cvar_t	sv_floodprotect_suicide		= CVAR("sv_floodprotect_suicide", "1");
cvar_t	sv_floodprotect_sendmessage	= CVARAF("sv_floodprotect_sendmessage", "",
											 "floodprotmsg", 0);

cvar_t	votelevel	= SCVAR("votelevel", "0");
cvar_t	voteminimum	= SCVAR("voteminimum", "4");
cvar_t	votepercent	= SCVAR("votepercent", "-1");
cvar_t	votetime	= SCVAR("votetime", "10");

cvar_t	pr_allowbutton1 = SCVARF("pr_allowbutton1", "1", CVAR_LATCH);
extern cvar_t sv_minping;


extern cvar_t	pm_bunnyspeedcap;
extern cvar_t	pm_ktjump;
extern cvar_t	pm_slidefix;
extern cvar_t	pm_slidyslopes;
extern cvar_t	pm_airstep;
extern cvar_t	pm_walljump;
cvar_t sv_pushplayers = SCVAR("sv_pushplayers", "0");

//yes, realip cvars need to be fully initialised or realip will be disabled
cvar_t sv_getrealip = CVARD("sv_getrealip", "0", "Attempt to obtain a more reliable IP for clients, rather than just their proxy.");
cvar_t sv_realip_kick = SCVAR("sv_realip_kick", "0");
cvar_t sv_realiphostname_ipv4 = CVARD("sv_realiphostname_ipv4", "", "This is the server's public ip:port. This is needed for realip to work when the autodetected/local ip is not globally routable");
cvar_t sv_realiphostname_ipv6 = CVARD("sv_realiphostname_ipv6", "", "This is the server's public ip:port. This is needed for realip to work when the autodetected/local ip is not globally routable");
cvar_t sv_realip_timeout = SCVAR("sv_realip_timeout", "10");

#ifdef VOICECHAT
cvar_t sv_voip = CVARD("sv_voip", "1", "Enable reception of voice packets.");
cvar_t sv_voip_record = CVARD("sv_voip_record", "0", "Record voicechat into mvds. Requires player support. 0=noone, 1=everyone, 2=spectators only");
cvar_t sv_voip_echo = CVARD("sv_voip_echo", "0", "Echo voice packets back to their sender, a debug/test setting.");
#endif

char sv_votinggroup[] = "server voting";


extern char cvargroup_serverpermissions[];
extern char cvargroup_serverinfo[];
extern char cvargroup_serverphysics[];
extern char cvargroup_servercontrol[];

extern	vec3_t	player_mins, player_maxs;

extern cvar_t pausable;


void SV_PreRunCmd(void);
void SV_RunCmd (usercmd_t *ucmd, qboolean recurse);
void SV_PostRunCmd(void);

/*
============================================================

USER STRINGCMD EXECUTION

host_client and sv_player will be valid.
============================================================
*/

qboolean SV_CheckRealIP(client_t *client, qboolean force)
{
	//returns true if they have a real ip
	char *serverip;
	char *msg;

	if (sv_getrealip.value <= client->realip_status || sv_getrealip.value > 3)
		return true;

	if (client->netchan.remote_address.type == NA_LOOPBACK)
		return true;	//the loopback client doesn't have to pass realip checks

	if (client->realip_status == 3)
		return true;	//we know that the ip is authentic
	if (client->realip_status == 2)
	{
		ClientReliableWrite_Begin(client, svc_print, 256);
		ClientReliableWrite_Byte(client, PRINT_HIGH);
		ClientReliableWrite_String(client, "Couldn't verify your real ip\n");
		return true;	//client doesn't support certainty.
	}
	if (client->realip_status == -1)
		return true;	//this client timed out.

	if (realtime - client->connection_started > sv_realip_timeout.value)
	{
		ClientReliableWrite_Begin(client, svc_print, 256);
		ClientReliableWrite_Byte(client, PRINT_HIGH);
		if (client->realip_status > 0)
			ClientReliableWrite_String(client, "Couldn't verify your real ip\n");
		else
			ClientReliableWrite_String(client, "Couldn't determine your real ip\n");
		if (sv_realip_kick.value > host_client->realip_status)
		{
			client->drop = true;
			return false;
		}
		if (!client->realip_status)
			client->realip_status = -1;
		return true;
	}


	if (client->realip_status == 1)
	{
		msg = va("\xff\xff\xff\xff%c %i", A2A_PING, client->realip_ping);
		NET_SendPacket(NS_SERVER, strlen(msg), msg, client->realip);
	}
	else
	{
		if (client->netchan.remote_address.type == NA_IPV6)
		{
			serverip = sv_realiphostname_ipv6.string;
//			serverip = NET_AdrToString (net_local_sv_ip6adr);
		}
		else
		{
			serverip = sv_realiphostname_ipv4.string;
//			serverip = NET_AdrToString (net_local_sv_ipadr);
		}

		if (!*serverip)
		{
			Con_Printf("realip not fully configured\n");
			client->realip_status = -1;
			return true;
		}

		ClientReliableWrite_Begin(client, svc_stufftext, 256);
		ClientReliableWrite_String(client, va("packet %s \"realip %i %i\"\n", serverip, (int)(client-svs.clients), client->realip_num));
	}
	return false;
}

/*
================
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)
{
	char		*gamedir;
	int			playernum;
	int splitnum;
	client_t *split;

	host_client->prespawn_stage = PRESPAWN_INVALID;
	host_client->prespawn_idx = 0;

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

	if (host_client->redirect)
	{
		char *msg = va("connect \"%s\"\n", sv_fullredirect.string);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));
		ClientReliableWrite_String (host_client, msg);
		return;
	}

/*	splitt delay
	host_client->state = cs_connected;
	host_client->connection_started = realtime;
#ifdef SVRANKING
	host_client->stats_started = realtime;
#endif*/

	// 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] || !strcmp(gamedir, "fte"))
	{
		if (ISQWCLIENT(host_client))
			gamedir = "qw";
		else
			gamedir = "";
	}

//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)
	{
		Con_TPrintf(STL_BACKBUFSET, host_client->name, host_client->netchan.message.cursize);
		host_client->num_backbuf = 0;
		SZ_Clear(&host_client->netchan.message);
	}
*/
	if (svs.netprim.coordsize > 2 && !(host_client->fteprotocolextensions & PEXT_FLOATCOORDS))
	{
		SV_ClientPrintf(host_client, 2, "\n\n\n\nSorry, but your client does not appear to support FTE's bigcoords\nFTE users will need to set cl_nopext to 0 and then reconnect, or to upgrade\n");
		Con_Printf("%s does not support bigcoords\n", host_client->name);
		return;
	}

	ClientReliableCheckBlock(host_client, 800);	//okay, so it might be longer, but I'm too lazy to work out the real size.

	// send the serverdata
	ClientReliableWrite_Byte (host_client, ISQ2CLIENT(host_client)?svcq2_serverdata:svc_serverdata);
#ifdef PROTOCOL_VERSION_FTE
	if (host_client->fteprotocolextensions)//let the client know
	{
		ClientReliableWrite_Long (host_client, PROTOCOL_VERSION_FTE);
		if (svs.netprim.coordsize == 2)	//we're not using float orgs on this level.
			ClientReliableWrite_Long (host_client, host_client->fteprotocolextensions&~PEXT_FLOATCOORDS);
		else
			ClientReliableWrite_Long (host_client, host_client->fteprotocolextensions|PEXT_FLOATCOORDS);
	}
	if (host_client->fteprotocolextensions2)//let the client know
	{
		ClientReliableWrite_Long (host_client, PROTOCOL_VERSION_FTE2);
		ClientReliableWrite_Long (host_client, host_client->fteprotocolextensions2);
	}
#endif
	ClientReliableWrite_Long (host_client, ISQ2CLIENT(host_client)?PROTOCOL_VERSION_Q2:PROTOCOL_VERSION_QW);
	ClientReliableWrite_Long (host_client, svs.spawncount);
	if (ISQ2CLIENT(host_client))
		ClientReliableWrite_Byte (host_client, 0);
	ClientReliableWrite_String (host_client, gamedir);

	if (host_client->fteprotocolextensions2 & PEXT2_MAXPLAYERS)
	{
		/*is this a sane way to do it? or should we split the spectator thing off entirely?*/
		ClientReliableWrite_Byte (host_client, sv.allocated_client_slots);
		splitnum = 0;
		for (split = host_client, splitnum = 0; split; split = split->controlled)
			splitnum++;
		ClientReliableWrite_Byte (host_client, (host_client->spectator?128:0) | splitnum); //read each player's userinfo to see if its a spectator or not. this hint is merely a cheat.
		for (split = host_client; split; split = split->controlled)
		{
			playernum = NUM_FOR_EDICT(svprogfuncs, split->edict)-1;
			if (sv.state == ss_cinematic)
				playernum = -1;
			ClientReliableWrite_Byte (host_client, playernum);

			split->state = cs_connected;
			split->connection_started = realtime;
		#ifdef SVRANKING
			split->stats_started = realtime;
		#endif
			splitnum++;
		}
	}
	else
	{
		splitnum = 0;
		for (split = host_client; split; split = split->controlled)
		{
			switch(svs.gametype)
			{
	#ifdef HLSERVER
			case GT_HALFLIFE:
				playernum = split - svs.clients;
				break;
	#endif
	#ifdef Q2SERVER
			case GT_QUAKE2:
				playernum = Q2NUM_FOR_EDICT(split->q2edict)-1;
				break;
	#endif
			default:
				playernum = NUM_FOR_EDICT(svprogfuncs, split->edict)-1;
			}
	#ifdef SERVER_DEMO_PLAYBACK
			if (sv.demostate)
			{
				playernum = (MAX_CLIENTS-1-splitnum)|128;
			}
			else
	#endif
				if (split->spectator)
				playernum |= 128;

			if (sv.state == ss_cinematic)
				playernum = -1;

			if (ISQ2CLIENT(host_client))
				ClientReliableWrite_Short (host_client, playernum);
			else
				ClientReliableWrite_Byte (host_client, playernum);

			split->state = cs_connected;
			split->connection_started = realtime;
		#ifdef SVRANKING
			split->stats_started = realtime;
		#endif
			splitnum++;
		}

		if (host_client->fteprotocolextensions & PEXT_SPLITSCREEN)
			ClientReliableWrite_Byte (host_client, 128);
	}

	// send full levelname
#ifdef SERVER_DEMO_PLAYBACK
	if (sv.demostatevalid)
		ClientReliableWrite_String (host_client, sv.demfullmapname);
	else
#endif
		ClientReliableWrite_String (host_client, sv.mapname);

	//
	// game server
	//
#ifdef Q2SERVER
	if (ISQ2CLIENT(host_client))
	{
		if (sv.state != ss_cinematic)
		{
//			host_client->q2edict = Q2NUM_FOR_EDICT(split->q2edict)-1;
			memset (&host_client->lastcmd, 0, sizeof(host_client->lastcmd));

			// begin fetching configstrings
			ClientReliableWrite_Byte (host_client, svcq2_stufftext);
			ClientReliableWrite_String (host_client, va("cmd configstrings %i 0\n",svs.spawncount) );
		}
		return;
	}
#endif
	// send the movevars
	ClientReliableWrite_Float(host_client, movevars.gravity);
	ClientReliableWrite_Float(host_client, movevars.stopspeed);
	ClientReliableWrite_Float(host_client, movevars.maxspeed);
	ClientReliableWrite_Float(host_client, movevars.spectatormaxspeed);
	ClientReliableWrite_Float(host_client, movevars.accelerate);
	ClientReliableWrite_Float(host_client, movevars.airaccelerate);
	ClientReliableWrite_Float(host_client, movevars.wateraccelerate);
	ClientReliableWrite_Float(host_client, movevars.friction);
	ClientReliableWrite_Float(host_client, movevars.waterfriction);
	ClientReliableWrite_Float(host_client, movevars.entgravity);

	host_client->csqcactive = false;

	host_client->realip_num = rand()+(host_client->challenge<<16);
	SV_CheckRealIP(host_client, false);

	SV_LogPlayer(host_client, "new (QW)");

	host_client->prespawn_stage = PRESPAWN_SERVERINFO;
	host_client->prespawn_idx = 0;
}

void SVNQ_New_f (void)
{
	extern cvar_t coop;
	char			message[2048];
	int i;
	int maxplayers = 0;
	int op;
	unsigned int protext1 = 0, protext2 = 0, protmain = 0, protfl = 0;

	host_client->send_message = true;
	if (host_client->redirect)
	{
		char *msg = va("connect \"%s\"\n", sv_fullredirect.string);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));
		ClientReliableWrite_String (host_client, msg);
		return;
	}

	if (!host_client->pextknown)
	{
		char *msg = va("cmd pext\n");
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));
		ClientReliableWrite_String (host_client, msg);
		return;
	}

	MSG_WriteByte (&host_client->netchan.message, svc_print);
	Q_snprintfz (message, sizeof(message), "%c\n%s server\n", 2, version_string());
	MSG_WriteString (&host_client->netchan.message,message);


	Z_Free(host_client->csqcentversions);
	host_client->csqcentversions = NULL;
	Z_Free(host_client->csqcentsequence);
	host_client->csqcentsequence = NULL;
	host_client->csqcactive = false;
	if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7)
	{
		extern cvar_t allow_download;
		char *f;

		if (allow_download.value)
		{
			MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
			MSG_WriteString (&host_client->netchan.message, "cl_serverextension_download 1\n");
		}

		f = COM_LoadTempFile("csprogs.dat");
		if (f)
		{
			MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
			MSG_WriteString (&host_client->netchan.message, va("csqc_progname %s\n", "csprogs.dat"));
			MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
			MSG_WriteString (&host_client->netchan.message, va("csqc_progsize %i\n", com_filesize));
			MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
			MSG_WriteString (&host_client->netchan.message, va("csqc_progcrc %i\n", QCRC_Block(f, com_filesize)));

			MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
			MSG_WriteString (&host_client->netchan.message, "cmd enablecsqc\n");
		}
	}

	protext1 = host_client->fteprotocolextensions;
	protext2 = host_client->fteprotocolextensions2;
	protmain = NQ_PROTOCOL_VERSION;
	protfl = 0;
	//force floatcoords as required.
	if (sv.nqdatagram.prim.coordsize >= 4)
		protext1 |= PEXT_FLOATCOORDS;
	else
		protext1 &= ~PEXT_FLOATCOORDS;

	op = host_client->protocol;
	switch(host_client->protocol)
	{
#ifdef NQPROT
	case SCP_NETQUAKE:
	case SCP_PROQUAKE:
	case SCP_FITZ666:
		SV_LogPlayer(host_client, "new (NQ)");
		if (sv.nqdatagram.prim.anglesize != 1 || sv.nqdatagram.prim.coordsize != 2 || host_client->protocol == SCP_FITZ666)
		{
			protfl =
					((sv.nqdatagram.prim.coordsize==4)?RMQFL_FLOATCOORD:0) |
					((sv.nqdatagram.prim.anglesize==2)?RMQFL_SHORTANGLE:0);
			host_client->protocol = SCP_FITZ666; /*mneh, close enough, the rmq stuff is just modifiers*/

			if (protfl)
			{
				protext1 &= ~PEXT_FLOATCOORDS;		//never report floatcoords when using rmq protocol, as the base protocol allows us to be more specific anyway.
				protmain = RMQ_PROTOCOL_VERSION;
			}
			else
				protmain = FITZ_PROTOCOL_VERSION;
		}
		else
		{
			host_client->protocol = (host_client->protocol==SCP_PROQUAKE)?SCP_PROQUAKE:SCP_NETQUAKE;	//identical other than the client->server angles
			protmain = NQ_PROTOCOL_VERSION;
		}
		maxplayers = 16;
		break;
	case SCP_DARKPLACES6:
		SV_LogPlayer(host_client, "new (DP6)");
		protmain = DP6_PROTOCOL_VERSION;
		maxplayers = 255;
		break;
	case SCP_DARKPLACES7:
		SV_LogPlayer(host_client, "new (DP7)");
		protmain = DP7_PROTOCOL_VERSION;
		maxplayers = 255;
		break;
#endif
	default:
		host_client->drop = true;
		break;
	}

	MSG_WriteByte (&host_client->netchan.message, svc_serverdata);
	if (protext1)
	{
		MSG_WriteLong (&host_client->netchan.message, PROTOCOL_VERSION_FTE);
		MSG_WriteLong (&host_client->netchan.message, protext1);
	}
	if (protext2)
	{
		MSG_WriteLong (&host_client->netchan.message, PROTOCOL_VERSION_FTE2);
		MSG_WriteLong (&host_client->netchan.message, protext2);
	}
	MSG_WriteLong (&host_client->netchan.message, protmain);
	if (protmain == RMQ_PROTOCOL_VERSION)
		MSG_WriteLong (&host_client->netchan.message, protfl);
	MSG_WriteByte (&host_client->netchan.message, (sv.allocated_client_slots>maxplayers)?maxplayers:sv.allocated_client_slots);

	if (op != host_client->protocol)
		SV_ClientProtocolExtensionsChanged(host_client);

	if (!coop.value && deathmatch.value)
		MSG_WriteByte (&host_client->netchan.message, GAME_DEATHMATCH);
	else
		MSG_WriteByte (&host_client->netchan.message, GAME_COOP);

	strcpy (message, sv.mapname);

	MSG_WriteString (&host_client->netchan.message,message);

	for (i = 1; sv.strings.model_precache[i] ; i++)
		MSG_WriteString (&host_client->netchan.message, sv.strings.model_precache[i]);
	MSG_WriteByte (&host_client->netchan.message, 0);

	for (i = 1; *sv.strings.sound_precache[i] ; i++)
		MSG_WriteString (&host_client->netchan.message, sv.strings.sound_precache[i]);
	MSG_WriteByte (&host_client->netchan.message, 0);

// send music
	MSG_WriteByte (&host_client->netchan.message, svc_cdtrack);
	if (progstype == PROG_H2)
	{
		MSG_WriteByte (&host_client->netchan.message, sv.h2cdtrack);
		MSG_WriteByte (&host_client->netchan.message, sv.h2cdtrack);
	}
	else
	{
		MSG_WriteByte (&host_client->netchan.message, ((edict_t*)sv.world.edicts)->v->sounds);
		MSG_WriteByte (&host_client->netchan.message, ((edict_t*)sv.world.edicts)->v->sounds);
	}

// set view
	MSG_WriteByte (&host_client->netchan.message, svc_setview);
	MSG_WriteEntity (&host_client->netchan.message, NUM_FOR_EDICT(svprogfuncs, host_client->edict));

	MSG_WriteByte (&host_client->netchan.message, svc_signonnum);
	MSG_WriteByte (&host_client->netchan.message, 1);

	MSG_WriteByte (&host_client->netchan.message, svc_setpause);
	MSG_WriteByte (&host_client->netchan.message, sv.paused);

//	host_client->sendsignon = true;
//	host_client->spawned = false;		// need prespawn, spawn, etc

	host_client->prespawn_stage = PRESPAWN_MAPCHECK;
	host_client->prespawn_idx = 0;
}





#ifdef Q2SERVER
void SVQ2_ConfigStrings_f (void)
{
	int			start;
	char *str;

	Con_DPrintf ("Configstrings() from %s\n", host_client->name);

	if (host_client->state != cs_connected)
	{
		Con_DPrintf ("configstrings 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 )
	{
		Con_DPrintf ("SV_Configstrings_f from different level\n");
		SV_New_f ();
		return;
	}

	start = atoi(Cmd_Argv(2));

	if (start < 0)
	{
		Con_Printf ("SV_Configstrings_f: %s tried crashing us\n", host_client->name);
		host_client->drop = true;
		return;
	}

	// write a packet full of data

	while ( host_client->netchan.message.cursize < host_client->netchan.message.maxsize/2
		&& start < Q2MAX_CONFIGSTRINGS)
	{
		str = sv.strings.configstring[start];
		if (*str)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			MSG_WriteString (&host_client->netchan.message, str);
		}
			/*
		//choose range to grab from.
		if (start < Q2CS_CDTRACK)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			MSG_WriteString (&host_client->netchan.message, sv.name);
		}
		else if (start < Q2CS_SKY)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			if (svprogfuncs)
				MSG_WriteString (&host_client->netchan.message, va("%i", sv.edicts->v->sounds));
			else
				MSG_WriteString (&host_client->netchan.message, "0");
		}
		else if (start < Q2CS_SKYAXIS)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			MSG_WriteString (&host_client->netchan.message, "unit1_");
		}
		else if (start < Q2CS_SKYROTATE)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			MSG_WriteString (&host_client->netchan.message, "0");
		}
		else if (start < Q2CS_STATUSBAR)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			MSG_WriteString (&host_client->netchan.message, "0");
		}
		else if (start < Q2CS_AIRACCEL)
		{//show status bar
			if (start == Q2CS_STATUSBAR)
			{
				MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
				MSG_WriteShort (&host_client->netchan.message, start);
				MSG_WriteString (&host_client->netchan.message, sv.statusbar);
			}
		}
		else if (start < Q2CS_MAXCLIENTS)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			MSG_WriteString (&host_client->netchan.message, va("%i", (int)1));
		}
		else if (start < Q2CS_MAPCHECKSUM)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			MSG_WriteString (&host_client->netchan.message, va("%i", (int)32));
		}
		else if (start < Q2CS_MODELS)
		{
			extern int map_checksum;
			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
			MSG_WriteShort (&host_client->netchan.message, start);
			MSG_WriteString (&host_client->netchan.message, va("%i", map_checksum));
		}
		else if (start < Q2CS_SOUNDS)
		{
			if (*sv.model_precache[start-Q2CS_MODELS])
			{
				MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
				MSG_WriteShort (&host_client->netchan.message, start);
				MSG_WriteString (&host_client->netchan.message, sv.model_precache[start-Q2CS_MODELS]);
			}
		}
		else if (start < Q2CS_IMAGES)
		{
			if (*sv.sound_precache[start-Q2CS_SOUNDS])
			{
				MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
				MSG_WriteShort (&host_client->netchan.message, start);
				MSG_WriteString (&host_client->netchan.message, sv.sound_precache[start-Q2CS_SOUNDS]);
			}
		}
		else if (start < Q2CS_LIGHTS)
		{
			if (*sv.image_precache[start-Q2CS_IMAGES])
			{
				MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
				MSG_WriteShort (&host_client->netchan.message, start);
				MSG_WriteString (&host_client->netchan.message, sv.image_precache[start-Q2CS_IMAGES]);
			}
		}
		else if (start < Q2CS_ITEMS)
		{
			if (start-Q2CS_LIGHTS < MAX_LIGHTSTYLES && sv.lightstyles[start-Q2CS_LIGHTS])
			{
				MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
				MSG_WriteShort (&host_client->netchan.message, start);
				MSG_WriteString (&host_client->netchan.message, sv.lightstyles[start-Q2CS_LIGHTS]);
			}
		}
		else if (start < Q2CS_PLAYERSKINS)
		{
//			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
//			MSG_WriteShort (&host_client->netchan.message, start);
//			MSG_WriteString (&host_client->netchan.message, sv.configstrings[start-Q2CS_ITEMS]);
		}
		else if (start < Q2CS_GENERAL)
		{
//			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
//			MSG_WriteShort (&host_client->netchan.message, start);
//			MSG_WriteString (&host_client->netchan.message, sv.configstrings[start]);
		}
		else
		{
//			MSG_WriteByte (&host_client->netchan.message, svcq2_configstring);
//			MSG_WriteShort (&host_client->netchan.message, start);
//			MSG_WriteString (&host_client->netchan.message, sv.configstrings[start]);
		}
		*/
		start++;
	}

	// send next command

	if (start == Q2MAX_CONFIGSTRINGS)
	{
		MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
		MSG_WriteString (&host_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
	}
	else
	{
		MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
		MSG_WriteString (&host_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
	}
}
#endif

#ifdef Q2SERVER
void SVQ2_BaseLines_f (void)
{
	int		start;
	q2entity_state_t	nullstate;
	q2entity_state_t	*base;

	extern q2entity_state_t	sv_baselines[Q2MAX_EDICTS];

	Con_DPrintf ("Baselines() from %s\n", host_client->name);

	if (host_client->state != cs_connected)
	{
		Con_Printf ("baselines 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 )
	{
		Con_Printf ("SV_Baselines_f from different level\n");
		SV_New_f ();
		return;
	}

	start = atoi(Cmd_Argv(2));

	memset (&nullstate, 0, sizeof(nullstate));

	// write a packet full of data

	while ( host_client->netchan.message.cursize <  host_client->netchan.message.maxsize/2
		&& start < Q2MAX_EDICTS)
	{
		base = &sv_baselines[start];
		if (base->modelindex || base->sound || base->effects)
		{
			MSG_WriteByte (&host_client->netchan.message, svcq2_spawnbaseline);
			MSGQ2_WriteDeltaEntity (&nullstate, base, &host_client->netchan.message, true, true);
		}
		start++;
	}

	// send next command

	if (start == Q2MAX_EDICTS)
	{
		MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
		MSG_WriteString (&host_client->netchan.message, va("precache %i\n", svs.spawncount) );
	}
	else
	{
		MSG_WriteByte (&host_client->netchan.message, svcq2_stufftext);
		MSG_WriteString (&host_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
	}
}

void SVQ2_NextServer_f (void)
{
	if (!*sv.modelname && atoi(Cmd_Argv(1)) == svs.spawncount)
	{
		cvar_t *nsv = Cvar_FindVar("nextserver");
		if (!nsv || !*nsv->string)
			return;

		svs.spawncount++;	// make sure another doesn't sneak in

		Cbuf_AddText(nsv->string, RESTRICT_LOCAL);
		Cbuf_AddText("\n", RESTRICT_LOCAL);
		Cvar_Set(nsv, "");
	}
}
#endif

/*
==================
SV_Soundlist_f
==================
*/
void SVQW_Soundlist_f (void)
{
	if (host_client->prespawn_stage == PRESPAWN_SOUNDLIST)
		host_client->prespawn_idx &= ~0x80000000;
}

/*
==================
SV_Modellist_f
==================
*/
void SVQW_Modellist_f (void)
{
	if (host_client->prespawn_stage == PRESPAWN_MODELLIST)
		host_client->prespawn_idx &= ~0x80000000;
}

void SV_SendClientPrespawnInfo(client_t *client)
{
	qboolean started;
	int i;
	entity_state_t *state;
	edict_t *ent;
	svcustomtents_t *ctent;
	//much of this function is written to fill packets enough to overflow them (assuming max packet sizes are large enough), but some bits are lazy and just backbuffer as needed.

	if (client->num_backbuf)
	{
		//don't spam too much.
		return;
	}

	if (client->prespawn_stage == PRESPAWN_SERVERINFO)
	{
		char buffer[1024];

		while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
		{
			if (client->prespawn_idx == 0)
			{
				ClientReliableWrite_Begin(client, svc_stufftext, 20 + strlen(svs.info));
				ClientReliableWrite_String (client, va("fullserverinfo \"%s\"\n", svs.info) );
			}
			else if (client->prespawn_idx == 1)
			{
				ClientReliableWrite_Begin(client, svc_cdtrack, 2);
				if (progstype == PROG_H2)
					ClientReliableWrite_Byte (client, sv.h2cdtrack);
				else if (svprogfuncs)
					ClientReliableWrite_Byte (client, ((edict_t*)sv.world.edicts)->v->sounds);
				else
					ClientReliableWrite_Byte (client, 0);
			}
			else if (client->prespawn_idx == 2)
			{
				FS_GetPackNames(buffer, sizeof(buffer), 2, true); /*retain extensions, or we'd have to assume pk3*/
				ClientReliableWrite_Begin(client, svc_stufftext, 1+11+strlen(buffer)+1+1);
				ClientReliableWrite_SZ(client, "//paknames ", 11);
				ClientReliableWrite_SZ(client, buffer, strlen(buffer));
				ClientReliableWrite_String(client, "\n");
			}
			else if (client->prespawn_idx == 3)
			{
				FS_GetPackHashes(buffer, sizeof(buffer), false);
				ClientReliableWrite_Begin(client, svc_stufftext, 1+7+strlen(buffer)+1+1);
				ClientReliableWrite_SZ(client, "//paks ", 7);
				ClientReliableWrite_SZ(client, buffer, strlen(buffer));
				ClientReliableWrite_String(client, "\n");
			}
			else
			{
				client->prespawn_stage++;
				client->prespawn_idx = 0;
				break;
			}
			client->prespawn_idx++;
		}
	}

	if (client->prespawn_stage == PRESPAWN_SOUNDLIST)
	{
		int maxclientsupportedsounds = 256;
#ifdef PEXT_SOUNDDBL
		if (client->fteprotocolextensions & PEXT_SOUNDDBL)
			maxclientsupportedsounds = MAX_SOUNDS;
#endif
		started = false;

		//allows stalling for the soundlist command, for compat.
		if (client->prespawn_idx & 0x80000000)
			return;

		while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
		{
			if (!started)
			{
				started = true;
#ifdef PEXT_SOUNDDBL
				if (client->prespawn_idx > 255)
				{
					MSG_WriteByte (&client->netchan.message, svcfte_soundlistshort);
					MSG_WriteShort (&client->netchan.message, client->prespawn_idx);
				}
				else
#endif
				{
					MSG_WriteByte (&client->netchan.message, svc_soundlist);
					MSG_WriteByte (&client->netchan.message, client->prespawn_idx);
				}
			}
			client->prespawn_idx++;

			if (client->prespawn_idx >= maxclientsupportedsounds || !*sv.strings.sound_precache[client->prespawn_idx])
			{
				//write final-end-of-list
				MSG_WriteByte (&client->netchan.message, 0);
				MSG_WriteByte (&client->netchan.message, 0);
				started = 0;
				client->prespawn_stage++;
				client->prespawn_idx = 0;
				break;
			}
			else
				MSG_WriteString (&client->netchan.message, sv.strings.sound_precache[client->prespawn_idx]);
		}
		if (started)
		{
			//write end-of-packet
			MSG_WriteByte (&client->netchan.message, 0);
			MSG_WriteByte (&client->netchan.message, (client->prespawn_idx&0xff)?client->prespawn_idx:0xff);

			if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS))
				client->prespawn_idx |= 0x80000000;
		}
	}

	if (client->prespawn_stage == PRESPAWN_MODELLIST)
	{
		started = false;

		//allows stalling for the soundlist command, for compat.
		if (client->prespawn_idx & 0x80000000)
			return;

		while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
		{
			if (!started)
			{
				started = true;
#ifdef PEXT_SOUNDDBL
				if (client->prespawn_idx > 255)
				{
					MSG_WriteByte (&client->netchan.message, svcfte_modellistshort);
					MSG_WriteShort (&client->netchan.message, client->prespawn_idx);
				}
				else
#endif
				{
					MSG_WriteByte (&client->netchan.message, svc_modellist);
					MSG_WriteByte (&client->netchan.message, client->prespawn_idx);
				}
			}
			client->prespawn_idx++;

			if (client->prespawn_idx >= client->maxmodels || !sv.strings.model_precache[client->prespawn_idx])
			{
				//write final-end-of-list
				MSG_WriteByte (&client->netchan.message, 0);
				MSG_WriteByte (&client->netchan.message, 0);
				started = 0;

				if (client->zquake_extensions & Z_EXT_VWEP)
				{
					char mname[MAX_QPATH];
					char vweaplist[1024] = "//vwep";

					for (i = 0; sv.strings.vw_model_precache[i]; i++)
					{
						//grab the model name... without a progs/ prefix if it has one
						if (!strncmp(sv.strings.vw_model_precache[i], "progs/", 6))
							Q_strncpy(mname, sv.strings.vw_model_precache[i]+6, sizeof(mname));
						else
							Q_strncpy(mname, sv.strings.vw_model_precache[i], sizeof(mname));

						//strip .mdl extensions, for compat with ezquake
						if (!strcmp(COM_FileExtension(mname), "mdl"))
							COM_StripExtension(mname, mname, sizeof(mname));

						//add it to the vweap command, taking care of any remaining spaces in names.
						if (strchr(mname, ' ') || !*mname)
							Q_strncatz(vweaplist, va(" \"%s\"", mname), sizeof(vweaplist));
						else
							Q_strncatz(vweaplist, va(" %s", mname), sizeof(vweaplist));
					}

					if (strlen(vweaplist) <= sizeof(vweaplist)-2)
					{
						Q_strncatz(vweaplist, "\n", sizeof(vweaplist));

						ClientReliableWrite_Begin(client, svc_stufftext, 2+strlen(vweaplist));
						ClientReliableWrite_String(client, vweaplist);
					}
				}

				client->prespawn_stage++;
				client->prespawn_idx = 0;
				break;
			}
			else
				MSG_WriteString (&client->netchan.message, sv.strings.model_precache[client->prespawn_idx]);
		}
		if (started)
		{
			//write end-of-packet
			MSG_WriteByte (&client->netchan.message, 0);
			MSG_WriteByte (&client->netchan.message, (client->prespawn_idx&0xff)?client->prespawn_idx:0xff);

			if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS))
				client->prespawn_idx |= 0x80000000;
		}
	}

	if (client->prespawn_stage == PRESPAWN_MAPCHECK)
	{
		//can't progress beyond this as we're waiting for the client.
		host_client->prespawn_idx = client->prespawn_idx;
		return;
	}

	if (client->prespawn_stage == PRESPAWN_PARTICLES)
	{
		while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
		{
			if (client->prespawn_idx >= MAX_SSPARTICLESPRE)
			{
				client->prespawn_stage++;
				client->prespawn_idx = 0;
				break;
			}

			if (*sv.strings.particle_precache[client->prespawn_idx])
			{
				ClientReliableWrite_Begin (client, ISNQCLIENT(client)?svcdp_precache:svcfte_precache, 4 + strlen(sv.strings.particle_precache[client->prespawn_idx]));
				ClientReliableWrite_Short (client, client->prespawn_idx | PC_PARTICLE);
				ClientReliableWrite_String (client, sv.strings.particle_precache[client->prespawn_idx]);
			}
			client->prespawn_idx++;
		}
	}

	if (client->prespawn_stage == PRESPAWN_CUSTOMTENTS)
	{
		while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
		{
			if (client->prespawn_idx >= 255)
			{
				client->prespawn_stage++;
				client->prespawn_idx = 0;
				break;
			}

			ctent = &sv.customtents[client->prespawn_idx];

			if (*ctent->particleeffecttype)
			{
				if (client->fteprotocolextensions & PEXT_CUSTOMTEMPEFFECTS)
				{
					MSG_WriteByte(&client->netchan.message, svcfte_customtempent);
					MSG_WriteByte(&client->netchan.message, 255);
					MSG_WriteByte(&client->netchan.message, client->prespawn_idx);
					MSG_WriteByte(&client->netchan.message, ctent->netstyle);
					MSG_WriteString(&client->netchan.message, ctent->particleeffecttype);
					if (ctent->netstyle & CTE_STAINS)
					{
						MSG_WriteChar(&client->netchan.message, ctent->stain[0]);
						MSG_WriteChar(&client->netchan.message, ctent->stain[0]);
						MSG_WriteChar(&client->netchan.message, ctent->stain[0]);
						MSG_WriteByte(&client->netchan.message, ctent->radius);
					}
					if (ctent->netstyle & CTE_GLOWS)
					{
						MSG_WriteByte(&client->netchan.message, ctent->dlightrgb[0]);
						MSG_WriteByte(&client->netchan.message, ctent->dlightrgb[1]);
						MSG_WriteByte(&client->netchan.message, ctent->dlightrgb[2]);
						MSG_WriteByte(&client->netchan.message, ctent->dlightradius);
						MSG_WriteByte(&client->netchan.message, ctent->dlighttime);
					}
				}
			}
			client->prespawn_idx++;
		}
	}

	if (client->prespawn_stage == PRESPAWN_SIGNON_BUF)
	{
		while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))
		{
			if (client->prespawn_idx >= sv.num_signon_buffers)
			{
				client->prespawn_stage++;
				client->prespawn_idx = 0;
				break;
			}

			if (client->netchan.message.cursize+sv.signon_buffer_size[client->prespawn_idx]+30 < client->netchan.message.maxsize)
			{
				SZ_Write (&client->netchan.message,
					sv.signon_buffers[client->prespawn_idx],
					sv.signon_buffer_size[client->prespawn_idx]);
			}
			else
				break;
			client->prespawn_idx++;
		}
	}

	if (client->prespawn_stage == PRESPAWN_SPAWNSTATIC)
	{
		while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))	//static entities
		{
			if (client->prespawn_idx >= sv.num_static_entities)
			{
				client->prespawn_stage++;
				client->prespawn_idx = 0;
				break;
			}

			state = &sv_staticentities[client->prespawn_idx];
			client->prespawn_idx++;

			if (client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
			{
				MSG_WriteByte(&client->netchan.message, svcfte_spawnstatic2);
				SVFTE_EmitBaseline(state, false, &client->netchan.message);
				continue;
			}
			if (client->fteprotocolextensions & PEXT_SPAWNSTATIC2)
			{
				/*if it uses some new feature, use the updated spawnstatic*/
				if (state->hexen2flags || state->trans || state->modelindex >= 256 || state->frame > 255 || state->scale || state->abslight)
				{
					MSG_WriteByte(&client->netchan.message, svcfte_spawnstatic2);
					SVQW_WriteDelta(&nullentitystate, state, &client->netchan.message, true, client->fteprotocolextensions);
					continue;
				}
			}
			/*couldn't use protocol extensions?
			  use the fallback, unless the model is invalid as that's silly*/
			if (state->modelindex < 256)
			{
				MSG_WriteByte(&client->netchan.message, svc_spawnstatic);

				MSG_WriteByte (&client->netchan.message, state->modelindex);

				MSG_WriteByte (&client->netchan.message, state->frame);
				MSG_WriteByte (&client->netchan.message, (int)state->colormap);
				MSG_WriteByte (&client->netchan.message, (int)state->skinnum);
				for (i=0 ; i<3 ; i++)
				{
					MSG_WriteCoord(&client->netchan.message, state->origin[i]);
					MSG_WriteAngle(&client->netchan.message, state->angles[i]);
				}
				continue;
			}
		}
	}

	if (client->prespawn_stage == PRESPAWN_BASELINES)
	{
		while (client->netchan.message.cursize < (client->netchan.message.maxsize/2))	//baselines
		{
			if (client->prespawn_idx >= sv.world.num_edicts)
			{
				client->prespawn_stage++;
				client->prespawn_idx = 0;
				break;
			}

			ent = EDICT_NUM(svprogfuncs, client->prespawn_idx);

			if (!ent)
				state = &nullentitystate;
			else
				state = &ent->baseline;

			if (!state->number || !state->modelindex)
			{	//ent doesn't have a baseline
				client->prespawn_idx++;
				continue;
			}

			if (state->number >= client->max_net_ents || state->modelindex >= client->maxmodels)
			{
				/*can't send this ent*/
			}
			else if (client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
			{
				MSG_WriteByte(&client->netchan.message, svcfte_spawnbaseline2);
				SVFTE_EmitBaseline(state, true, &client->netchan.message);
			}
			else if (client->fteprotocolextensions & PEXT_SPAWNSTATIC2)
			{
				MSG_WriteByte(&client->netchan.message, svcfte_spawnbaseline2);
				SVQW_WriteDelta(&nullentitystate, state, &client->netchan.message, true, client->fteprotocolextensions);
			}
			else if (ISDPCLIENT(client) && (state->modelindex > 255 || state->frame > 255))
			{
				MSG_WriteByte(&client->netchan.message, svcdp_spawnbaseline2);
				MSG_WriteEntity (&client->netchan.message, state->number);

				MSG_WriteShort (&client->netchan.message, state->modelindex);
				MSG_WriteShort (&client->netchan.message, state->frame);
				MSG_WriteByte (&client->netchan.message, (int)state->colormap);
				MSG_WriteByte (&client->netchan.message, (int)state->skinnum);
				for (i=0 ; i<3 ; i++)
				{
					MSG_WriteCoord(&client->netchan.message, state->origin[i]);
					MSG_WriteAngle(&client->netchan.message, state->angles[i]);
				}
			}
			else if (client->protocol == SCP_FITZ666 && (state->modelindex > 255 || state->frame > 255 || state->trans != 255 || state->scale != 16))
			{
				int fl = 0;
				if (state->modelindex > 255)	fl |= FITZ_B_LARGEMODEL;
				if (state->frame > 255)			fl |= FITZ_B_LARGEFRAME;
				if (state->trans != 255)		fl |= FITZ_B_ALPHA;
				if (state->scale != 16)			fl |= RMQFITZ_B_SCALE;

				MSG_WriteByte(&client->netchan.message, svcfitz_spawnbaseline2);
				MSG_WriteEntity (&client->netchan.message, state->number);
				MSG_WriteByte(&client->netchan.message, fl);
				if (fl & FITZ_B_LARGEMODEL)
					MSG_WriteShort (&client->netchan.message, state->modelindex);
				else
					MSG_WriteByte (&client->netchan.message, state->modelindex);
				if (fl & FITZ_B_LARGEFRAME)
					MSG_WriteShort (&client->netchan.message, state->frame);
				else
					MSG_WriteByte (&client->netchan.message, state->frame);
				MSG_WriteByte (&client->netchan.message, state->colormap);
				MSG_WriteByte (&client->netchan.message, state->skinnum);
				for (i=0 ; i<3 ; i++)
				{
					MSG_WriteCoord(&client->netchan.message, state->origin[i]);
					MSG_WriteAngle(&client->netchan.message, state->angles[i]);
				}
				if (fl & FITZ_B_ALPHA)
					MSG_WriteByte (&client->netchan.message, state->trans);
				if (fl & RMQFITZ_B_SCALE)
					MSG_WriteByte (&client->netchan.message, state->scale);
			}
			else if (state->modelindex)
			{
				MSG_WriteByte(&client->netchan.message, svc_spawnbaseline);
				MSG_WriteEntity (&client->netchan.message, state->number);

				if (state->modelindex > 255)
					MSG_WriteByte (&client->netchan.message, 0);	//invalid modelindex. at least try to give something
				else
					MSG_WriteByte (&client->netchan.message, state->modelindex);
				MSG_WriteByte (&client->netchan.message, state->frame&255);
				MSG_WriteByte (&client->netchan.message, (int)state->colormap);
				MSG_WriteByte (&client->netchan.message, (int)state->skinnum);
				for (i=0 ; i<3 ; i++)
				{
					MSG_WriteCoord(&client->netchan.message, state->origin[i]);
					MSG_WriteAngle(&client->netchan.message, state->angles[i]);
				}
			}

			client->prespawn_idx++;
		}
	}
	if (client->prespawn_stage == PRESPAWN_DONE)
	{
		if (!client->prespawn_idx)
		{
			//we'll spawn the client and then send all the updating stuff only when we know the channel is clear, by pinging the client for it.
			if (ISNQCLIENT(client))
			{
				//effectively a cmd spawn... but also causes the client to actually send the player's name too.
				ClientReliableWrite_Begin (client, svc_signonnum, 2);
				ClientReliableWrite_Byte (client, 2);
			}
			else
			{
				char *cmd = va("cmd spawn %i\n",svs.spawncount);
				ClientReliableWrite_Begin(client, svc_stufftext, 2+strlen(cmd));
				ClientReliableWrite_String(client, cmd);
			}
			client->prespawn_idx++;
		}
	}
}

/*
==================
SV_PreSpawn_f
==================
*/
void SVQW_PreSpawn_f (void)
{
	unsigned	check;

	if (host_client->state != cs_connected)
	{
		Con_Printf ("prespawn not valid -- already spawned\n");
		return;
	}
	if (host_client->prespawn_stage != PRESPAWN_MAPCHECK)
	{
		Con_Printf("Wrong stage for prespawn command\n");
		return;
	}

	// handle the case of a level changing while a client was connecting
	if ( atoi(Cmd_Argv(1)) != svs.spawncount && !sv.msgfromdemo)
	{
		Con_Printf ("SV_PreSpawn_f from different level\n");
		//FIXME: we shouldn't need the following line.
		SV_New_f ();
		return;
	}

	if (host_client->prespawn_stage == PRESPAWN_MAPCHECK)
	{
		check = COM_RemapMapChecksum(atoi(Cmd_Argv(3)));

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

		if (sv_mapcheck.value && check != sv.world.worldmodel->checksum &&
			check != COM_RemapMapChecksum(LittleLong(sv.world.worldmodel->checksum2)))
#ifdef SERVER_DEMO_PLAYBACK
		if (!sv.demofile || (sv.demofile && !sv.democausesreconnect))	//demo playing causes no check. If it's the return level, check anyway to avoid that loophole.
#endif
		{
			char *msg;
			SV_ClientTPrintf (host_client, PRINT_HIGH,
				STL_MAPCHEAT,
				sv.modelname, check, sv.world.worldmodel->checksum, sv.world.worldmodel->checksum2);


			msg = va("\n//kickfile \"%s\"\n", sv.modelname);
			ClientReliableWrite_Begin (host_client, svc_stufftext, 3+strlen(msg));
			ClientReliableWrite_String (host_client, msg);
			SV_DropClient (host_client);
			return;
		}
		host_client->checksum = check;
		host_client->prespawn_stage = PRESPAWN_MAPCHECK+1;
		host_client->prespawn_idx = 0;
		return;
	}
}

/*
==================
SV_Spawn_f
==================
*/
void SVQW_Spawn_f (void)
{
	int		i;
	client_t	*client, *split;
	edict_t	*ent;

	int secret_total, secret_found, monsters_total, monsters_found;

	if (host_client->state != cs_connected)
	{
		Con_Printf ("Spawn not valid -- already spawned\n");
		return;
	}
	if (host_client->prespawn_stage != PRESPAWN_DONE)
	{
		Con_Printf ("client sent spawn without prespawn!\n");
		SV_New_f ();
		return;
	}

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

// 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=0, client = svs.clients ; i<MAX_CLIENTS ; i++, client++)
		SV_FullClientUpdate(client, host_client);
	SV_MVD_FullClientUpdate(NULL, host_client);

// send all current light styles
	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
	{
#ifdef SERVER_DEMO_PLAYBACK
		if (sv.democausesreconnect)
		{
			if (i >= MAX_STANDARDLIGHTSTYLES)
				continue;
			ClientReliableWrite_Begin (host_client, svc_lightstyle,
				3 + (sv.demolightstyles[i] ? strlen(sv.demolightstyles[i]) : 1));
			ClientReliableWrite_Byte (host_client, (char)i);
			ClientReliableWrite_String (host_client, sv.demolightstyles[i]);
		}
		else
#endif
		{
			if (i >= MAX_STANDARDLIGHTSTYLES)
				if (!sv.strings.lightstyles[i])
					continue;
#ifdef PEXT_LIGHTSTYLECOL
			if ((host_client->fteprotocolextensions & PEXT_LIGHTSTYLECOL) && sv.strings.lightstylecolours[i]!=7 && sv.strings.lightstyles[i])
			{
				ClientReliableWrite_Begin (host_client, svcfte_lightstylecol,
					3 + (sv.strings.lightstyles[i] ? strlen(sv.strings.lightstyles[i]) : 1));
				ClientReliableWrite_Byte (host_client, (char)i);
				ClientReliableWrite_Char (host_client, sv.strings.lightstylecolours[i]);
				ClientReliableWrite_String (host_client, sv.strings.lightstyles[i]);
			}
			else
#endif
			{
				ClientReliableWrite_Begin (host_client, svc_lightstyle,
					3 + (sv.strings.lightstyles[i] ? strlen(sv.strings.lightstyles[i]) : 1));
				ClientReliableWrite_Byte (host_client, (char)i);
				ClientReliableWrite_String (host_client, sv.strings.lightstyles[i]);
			}
		}
	}

#ifdef HLSERVER
	if (svs.gametype == GT_HALFLIFE)
	{
		for (split = host_client; split; split = split->controlled)
		{
			split->entgravity = 1;
			split->maxspeed = 320;

			SVHL_PutClientInServer(split);
		}

		secret_total = 0;
		secret_found = 0;
		monsters_total = 0;
		monsters_found = 0;
	}
	else
#endif
	{
		// set up the edict
		for (split = host_client; split; split = split->controlled)
		{
			ent = split->edict;

			if (split->istobeloaded)	//minimal setup
			{
				split->entgravity = ent->xv->gravity;
				split->maxspeed = ent->xv->maxspeed;
				split->playerclass = ent->xv->playerclass;
			}
			else
			{
				SV_SetUpClientEdict(split, ent);
			}

		//
		// force stats to be updated
		//
			memset (split->statsi, 0, sizeof(split->statsi));
			memset (split->statsf, 0, sizeof(split->statsf));
			memset (split->statss, 0, sizeof(split->statss));
		}

		secret_total = pr_global_struct->total_secrets;
		secret_found = pr_global_struct->found_secrets;
		monsters_total = pr_global_struct->total_monsters;
		monsters_found = pr_global_struct->killed_monsters;
	}
	ClientReliableWrite_Begin (host_client, svcqw_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
	ClientReliableWrite_Long (host_client, secret_total);

	ClientReliableWrite_Begin (host_client, svcqw_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_TOTALMONSTERS);
	ClientReliableWrite_Long (host_client, monsters_total);

	ClientReliableWrite_Begin (host_client, svcqw_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_SECRETS);
	ClientReliableWrite_Long (host_client, secret_found);

	ClientReliableWrite_Begin (host_client, svcqw_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_MONSTERS);
	ClientReliableWrite_Long (host_client, monsters_found);
	// get the client to check and download skins
	// when that is completed, a begin command will be issued
	ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
	ClientReliableWrite_String (host_client, "skins\n" );

}

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

	VectorClear (sv_player->v->origin);
	VectorClear (sv_player->v->view_ofs);
	sv_player->v->view_ofs[2] = DEFAULT_VIEWHEIGHT;
	sv_player->v->movetype = MOVETYPE_NOCLIP;

	// search for an info_playerstart to spawn the spectator at
	//this is only useful when a mod doesn't nativly support spectators. old qw on nq mods.

	for (i=MAX_CLIENTS+1 ; i<sv.world.num_edicts ; i++)
	{
		e = EDICT_NUM(svprogfuncs, i);
		if (!strcmp(PR_GetString(svprogfuncs, e->v->classname), "info_player_start"))
		{
			VectorCopy (e->v->origin, sv_player->v->origin);
			return;
		}
	}

}

void SV_Begin_Core(client_t *split)
{	//this is the client-protocol-independant core, for q1/q2 gamecode

	client_t	*oh;
	int		i;
	if (progstype == PROG_H2 && split->playerclass)
		split->edict->xv->playerclass = split->playerclass;	//make sure it's set the same as the userinfo

#ifdef Q2SERVER
	if (ge)
	{
		ge->ClientUserinfoChanged (split->q2edict, split->userinfo);	//tell the gamecode
		SV_ExtractFromUserinfo(split, true);	//let the server routines know

		ge->ClientBegin(split->q2edict);
		split->istobeloaded = false;
		sv.spawned_client_slots++;
	}
	else
#endif
	if (split->istobeloaded)
	{
		func_t f;
		split->istobeloaded = false;

		f = PR_FindFunction(svprogfuncs, "RestoreGame", PR_ANY);
		if (f)
		{
			pr_global_struct->time = sv.world.physicstime;
			pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
			PR_ExecuteProgram (svprogfuncs, f);
		}
	}
	else
	{
		if (split->spectator)
		{
			SV_SpawnSpectator ();

			if (SpectatorConnect)
			{
				//keep the spectator tracking the player from the previous map
				if (split->spec_track > 0)
					split->edict->v->goalentity = EDICT_TO_PROG(svprogfuncs, svs.clients[split->spec_track-1].edict);
				else
					split->edict->v->goalentity = 0;


				// copy spawn parms out of the client_t
				for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
				{
					if (pr_global_ptrs->spawnparamglobals[i])
						*pr_global_ptrs->spawnparamglobals[i] = split->spawn_parms[i];
				}

				// call the spawn function
				pr_global_struct->time = sv.world.physicstime;
				pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
				PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
			}
			sv.spawned_observer_slots++;
		}
		else
		{
			sv.spawned_client_slots++;

			if (svprogfuncs)
			{
				eval_t *eval, *eval2;
				eval = PR_FindGlobal(svprogfuncs, "ClientReEnter", 0, NULL);
				if (eval && split->spawninfo)
				{
					globalvars_t *pr_globals = PR_globals(svprogfuncs, PR_CURRENT);
					int j;
					edict_t *ent;
					ent = split->edict;
					j = strlen(split->spawninfo);
					World_UnlinkEdict((wedict_t*)ent);
					svprogfuncs->restoreent(svprogfuncs, split->spawninfo, &j, ent);

					eval2 = svprogfuncs->GetEdictFieldValue(svprogfuncs, ent, "stats_restored", NULL);
					if (eval2)
						eval2->_float = 1;
					for (j=0 ; j< NUM_SPAWN_PARMS ; j++)
					{
						if (pr_global_ptrs->spawnparamglobals[j])
							*pr_global_ptrs->spawnparamglobals[j] = split->spawn_parms[j];
					}
					pr_global_struct->time = sv.world.physicstime;
					pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, ent);
					G_FLOAT(OFS_PARM0) = sv.time - split->spawninfotime;
					PR_ExecuteProgram(svprogfuncs, eval->function);
				}
				else
				{
					// copy spawn parms out of the client_t
					for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
					{
						if (pr_global_ptrs->spawnparamglobals[i])
							*pr_global_ptrs->spawnparamglobals[i] = split->spawn_parms[i];
					}

					// call the spawn function
#ifdef VM_Q1
					if (svs.gametype == GT_Q1QVM)
						Q1QVM_ClientConnect(split);
					else
#endif
					{
						globalvars_t *pr_globals = PR_globals(svprogfuncs, PR_CURRENT);

						pr_global_struct->time = sv.world.physicstime;
						pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
						if (pr_globals)
							G_FLOAT(OFS_PARM0) = split->csqcactive;	//this arg is part of EXT_CSQC_1, but doesn't have to be supported by the mod
						if (pr_global_ptrs->ClientConnect)
							PR_ExecuteProgram (svprogfuncs, *pr_global_ptrs->ClientConnect);

						// actually spawn the player
						pr_global_struct->time = sv.world.physicstime;
						pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
						if (pr_global_ptrs->PutClientInServer)
							PR_ExecuteProgram (svprogfuncs, *pr_global_ptrs->PutClientInServer);
						else
						{
							split->edict->v->health = 100;
							split->edict->v->mins[0] = -16;
							split->edict->v->mins[1] = -16;
							split->edict->v->mins[2] = -24;
							split->edict->v->maxs[0] = 16;
							split->edict->v->maxs[1] = 16;
							split->edict->v->maxs[2] = 32;
							split->edict->v->movetype = MOVETYPE_NOCLIP;
						}
					}

					oh = host_client;
					host_client = split;
					sv_player = host_client->edict;
					SV_PreRunCmd();
					{
						usercmd_t cmd;
						memset(&cmd, 0, sizeof(cmd));
						cmd.msec = 0;
#define ANGLE2SHORT(x) (x) * (65536/360.0)
						cmd.angles[0] = ANGLE2SHORT(split->edict->v->v_angle[0]);
						cmd.angles[1] = ANGLE2SHORT(split->edict->v->v_angle[1]);
						cmd.angles[2] = ANGLE2SHORT(split->edict->v->v_angle[2]);
						SV_RunCmd(&cmd, false);
					}
					SV_PostRunCmd();
					host_client = oh;
					sv_player = host_client->edict;
				}
			}
		}
	}
}

/*
==================
SV_Begin_f
==================
*/
void SV_Begin_f (void)
{
	client_t	*split;
	unsigned pmodel = 0, emodel = 0;
	qboolean sendangles=false;

	if (!SV_CheckRealIP(host_client, true))
	{
		if (host_client->protocol == SCP_QUAKE2)
			ClientReliableWrite_Begin (host_client, svcq2_stufftext, 13+strlen(Cmd_Args()));
		else
			ClientReliableWrite_Begin (host_client, svc_stufftext, 13+strlen(Cmd_Args()));
		ClientReliableWrite_String (host_client, va("cmd begin %s\n", Cmd_Args()));
		return;
	}

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

	for (split = host_client; split; split = split->controlled)
		split->state = cs_spawned;

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

	if (host_client->istobeloaded)
		sendangles = true;
	if (host_client->protocol == SCP_QUAKE2)
		sendangles = false;


	for (split = host_client; split; split = split->controlled)
	{	//tell the gamecode they're ready
		SV_Begin_Core(split);
	}

	// clear the net statistics, because connecting gives a bogus picture
	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
	if (progstype == PROG_QW)
	{
		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_BroadcastTPrintf (PRINT_HIGH, STL_POSSIBLEMODELCHEAT, host_client->name);
	}

	// if we are paused, tell the client
	if (sv.paused)
	{
		if (!ISQ2CLIENT(host_client))
		{
			ClientReliableWrite_Begin (host_client, svc_setpause, 2);
			ClientReliableWrite_Byte (host_client, sv.paused!=0);
		}
		if (sv.paused&~4)
			SV_ClientTPrintf(host_client, PRINT_HIGH, STL_SERVERPAUSED);
	}

	if (sendangles)
	{
//
// 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
		MSG_WriteByte (&host_client->netchan.message, svc_setangle);
		MSG_WriteAngle (&host_client->netchan.message, 0 );
		MSG_WriteAngle (&host_client->netchan.message, host_client->edict->v->angles[1] );
		MSG_WriteAngle (&host_client->netchan.message, 0 );
	}
}

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

//dp downloads are a 2-stream system
//the server->client stream is as you'd expect. except that its unreliable rather than reliable
//the client->server stream contains no actual data.
//when c2s has a hole, the s2c stream is reset to the last-known 'good' position.
//eventually the client is left acking packets with no data in them, the server then tells the client that the download is complete.
//the client does no checks to see if there's a hole, other than the crc

//so any single lost packet (even client->server) means that the entire stream will be set back by your ping time
void SV_DarkPlacesDownloadChunk(client_t *cl, sizebuf_t *msg)
{
#define MAXDPDOWNLOADCHUNK 1024
	char buffer[MAXDPDOWNLOADCHUNK];

	int size, start;

	if (!ISNQCLIENT(cl))
		return;
	if (!cl->download)
		return;

	if (!cl->downloadstarted)
		return;

	if (cl->num_backbuf)
		return;

	size = 1024;	//fixme

	if (size > cl->datagram.maxsize - cl->datagram.cursize)
		size = cl->datagram.maxsize - cl->datagram.cursize - 16;

	if (size > MAXDPDOWNLOADCHUNK)	//don't clog it too much
		size = MAXDPDOWNLOADCHUNK;

	start = VFS_TELL(cl->download);
	if (start+size > cl->downloadsize)	//clamp to the size of the file.
		size = cl->downloadsize - start;

	size = VFS_READ(cl->download, buffer, size);
	if (size < 0)
		size = 0;

	MSG_WriteByte(msg, svcdp_downloaddata);
	MSG_WriteLong (msg, start);
	MSG_WriteShort (msg, size);
	SZ_Write(msg, buffer, size);
}

void SVDP_StartDownload_f(void)
{
	if (host_client->protocol != SCP_DARKPLACES7)
		return;
	if (!host_client->download)
		return;
	host_client->downloadstarted = true;
}

void SV_DarkPlacesDownloadAck(client_t *cl)
{
	int start = MSG_ReadLong();
	int size = (unsigned short)MSG_ReadShort();

	if (!cl->download)
		return;

	if (start != cl->downloadacked)
	{
		//packetloss
		VFS_SEEK(cl->download, cl->downloadacked);
	}
	else if (size != 0)
	{
		cl->downloadacked += size;	//successful packet
		cl->downloadcount = cl->downloadacked;
	}
	else
	{
		char *s;
		unsigned short crc;
		int pos=0, csize;
		qbyte chunk[1024];
		QCRC_Init(&crc);
		VFS_SEEK(host_client->download, 0);
		while (pos < host_client->downloadsize)
		{
			csize = sizeof(chunk);
			if (pos + csize > host_client->downloadsize)
				csize = host_client->downloadsize - pos;
			VFS_READ(host_client->download, chunk, csize);
			QCRC_AddBlock(&crc, chunk, csize);
			pos += csize;
		}

		s = va("\ncl_downloadfinished %i %i \"\"\n", host_client->downloadsize, crc);
		ClientReliableWrite_Begin (cl, svc_stufftext, 2+strlen(s));
		ClientReliableWrite_String(cl, s);

		VFS_CLOSE(host_client->download);
		host_client->download = NULL;
		host_client->downloadsize = 0;
	}
}

void SV_NextChunkedDownload(unsigned int chunknum, int ezpercent, int ezfilenum)
{
#define CHUNKSIZE 1024
	char buffer[CHUNKSIZE];
	qbyte oobdata[1+ (sizeof("\\chunk")-1) + 4 + 1 + 4 + CHUNKSIZE];
	sizebuf_t *msg, msg_oob;
	int i;
	int error = false;

	msg = &host_client->datagram;

	if (chunknum*CHUNKSIZE > host_client->downloadsize)
	{
		SV_ClientPrintf (host_client, PRINT_HIGH, "Invalid file chunk requested %u to %u of %u.\n", chunknum*CHUNKSIZE, (chunknum+1)*CHUNKSIZE, host_client->downloadsize);
		error = 2;
	}

	if (!error && VFS_SEEK (host_client->download, chunknum*CHUNKSIZE) == false)
		error = true;
	else
	{
		if (host_client->downloadcount < chunknum*CHUNKSIZE)
			host_client->downloadcount = chunknum*CHUNKSIZE;
	}

	if ((host_client->datagram.cursize + CHUNKSIZE+5+50 > host_client->datagram.maxsize) || (host_client->datagram.cursize + CHUNKSIZE+5 > 1400))
	{
		//would overflow the packet, or result in (ethernet) fragmentation and high packet loss.
		msg = &msg_oob;

		if (!ezfilenum)
			return;

		if (host_client->waschoked)
			return;	//don't let chunked downloads flood out the standard packets.

		if (!Netchan_CanPacket(&host_client->netchan, SV_RateForClient(host_client)))
			return;
	}

	if (error)
		i = 0;
	else
		i = VFS_READ (host_client->download, buffer, CHUNKSIZE);

	if (i > 0)
	{
		if (msg == &msg_oob)
		{
			msg = &msg_oob;
			msg->cursize = 0;
			msg->maxsize = sizeof(oobdata);
			msg->currentbit = 0;
			msg->packing = SZ_RAWBYTES;
			msg->allowoverflow = 0;
			msg->overflowed = 0;
			msg->data = oobdata;
			MSG_WriteByte(msg, A2C_PRINT);
			SZ_Write(msg, "\\chunk", 6);
			MSG_WriteLong(msg, ezfilenum);	//echoing the file num is used so the packets don't go out of sync.
		}

		if (i != CHUNKSIZE)
			memset(buffer+i, 0, CHUNKSIZE-i);

		MSG_WriteByte(msg, svc_download);
		MSG_WriteLong(msg, chunknum);
		SZ_Write(msg, buffer, CHUNKSIZE);

		if (msg == &msg_oob)
		{
			Netchan_OutOfBand(NS_SERVER, host_client->netchan.remote_address, msg_oob.cursize, msg_oob.data);
			Netchan_Block(&host_client->netchan, msg_oob.cursize, SV_RateForClient(host_client));
		}
	}
	else if (i < 0)
		error = true;

	if (error)
	{
		VFS_CLOSE (host_client->download);
		host_client->download = NULL;

		if (error != 2)
		{/*work around for ezquake*/
			ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(host_client->downloadfn));
			ClientReliableWrite_Long (host_client, -1);
			ClientReliableWrite_Long (host_client, -3);
			ClientReliableWrite_String (host_client, host_client->downloadfn);
		}


		host_client->downloadstarted = false;
	}
}

/*
==================
SV_NextDownload_f
==================
*/
void SV_NextDownload_f (void)
{
	qbyte	buffer[1024];
	int		r;
	int		percent;
	int		size;

	if (!host_client->download)
		return;

#ifdef PEXT_CHUNKEDDOWNLOADS
	if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
	{
		if (Cmd_Argc() < 2)
			SV_NextChunkedDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)), atoi(Cmd_Argv(3)));
		else
			SV_NextChunkedDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)), atoi(Cmd_Argv(3)));
		return;
	}
#endif

	r = host_client->downloadsize - host_client->downloadcount;
	/*
#ifdef PEXT_ZLIBDL
	if (host_client->protocolextensions & PEXT_ZLIBDL)
	{
		if (r>1024)	//expect a little more.
			r=1024;
	}
	else
#endif
		*/
	if (r > 768)
		r = 768;
	r = VFS_READ (host_client->download, buffer, r);
	ClientReliableWrite_Begin (host_client, ISQ2CLIENT(host_client)?svcq2_download:svc_download, 6+r);
	ClientReliableWrite_Short (host_client, r);

	host_client->downloadcount += r;
	size = host_client->downloadsize;

	if (host_client->downloadcount < size)
	{
		if (!size)
			size = 1;

		percent = (double)host_client->downloadcount*100.0/size;
		percent = bound(0, percent, 99);
	}
	else
		percent = 100;

#ifdef PEXT_ZLIBDL
	if (host_client->fteprotocolextensions & PEXT_ZLIBDL)
	{
		ClientReliableWrite_Byte (host_client, percent+101);
		ClientReliableWrite_ZLib (host_client, buffer, r);
	}
	else
#endif
	{
		ClientReliableWrite_Byte (host_client, percent);
		ClientReliableWrite_SZ (host_client, buffer, r);
	}

	if (host_client->downloadcount < host_client->downloadsize)
		return;

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

}

void VARGS 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 (NS_SERVER, strlen(send)+1, send, where);
}

/*
==================
SV_NextUpload
==================
*/
void SV_NextUpload (void)
{
	int		percent;
	int		size;

	if (!*host_client->uploadfn)
	{
		SV_ClientTPrintf(host_client, PRINT_HIGH, STL_UPLOADDENIED);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
		ClientReliableWrite_String (host_client, "stopul\n");

		// suck out rest of packet
		size = MSG_ReadShort ();	MSG_ReadByte ();
		msg_readcount += size;
		return;
	}

	size = MSG_ReadShort ();
	percent = MSG_ReadByte ();

	if (!host_client->upload)
	{
		FS_CreatePath(host_client->uploadfn, FS_GAMEONLY);
		host_client->upload = FS_OpenVFS(host_client->uploadfn, "wb", FS_GAMEONLY);
		if (!host_client->upload)
		{
			Sys_Printf("Can't create %s\n", host_client->uploadfn);
			ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
			ClientReliableWrite_String (host_client, "stopul\n");
			*host_client->uploadfn = 0;
			return;
		}
		Con_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);
	}

	VFS_WRITE (host_client->upload, net_message.data + msg_readcount, size);
	msg_readcount += size;

	if (percent != 100)
	{
		ClientReliableWrite_Begin (host_client, svc_stufftext, 8);
		ClientReliableWrite_String (host_client, "nextul\n");
	}
	else
	{
		VFS_CLOSE (host_client->upload);
		host_client->upload = NULL;

		Con_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:\ndownload %s\n",
				host_client->uploadfn, p);
		}
		*host_client->uploadfn = 0;	//don't let it get overwritten again
	}
}

#ifdef VOICECHAT
/*
Pivicy issues:
By sending voice chat to a server, you are unsure who might be listening.
Voice can be recorded to an mvd, potentially including voice.
Spectators tracvking you are able to hear team chat of your team.
You're never quite sure if anyone might join the server and your team before you finish saying a sentance.
You run the risk of sounds around you being recorded by quake, including but not limited to: TV channels, loved ones, phones, YouTube videos featuring certain moans.
Default on non-team games is to broadcast.
*/

#define VOICE_RING_SIZE 512 /*POT*/
struct
{
	struct voice_ring_s
	{
			unsigned int sender;
			unsigned char receiver[MAX_CLIENTS/8];
			unsigned char gen;
			unsigned char seq;
			unsigned int datalen;
			unsigned char data[1024];
	} ring[VOICE_RING_SIZE];
	unsigned int write;
} voice;
void SV_VoiceReadPacket(void)
{
	unsigned int vt = host_client->voice_target;
	unsigned int j;
	struct voice_ring_s *ring;
	unsigned short bytes;
	client_t *cl;
	unsigned char gen = MSG_ReadByte();
	unsigned char seq = MSG_ReadByte();
	/*read the data from the client*/
	bytes = MSG_ReadShort();
	ring = &voice.ring[voice.write & (VOICE_RING_SIZE-1)];
	if (bytes > sizeof(ring->data) || host_client->ismuted || !sv_voip.ival)
	{
		MSG_ReadSkip(bytes);
		return;
	}
	else
	{
		voice.write++;
		MSG_ReadData(ring->data, bytes);
	}

	ring->datalen = bytes;
	ring->sender = host_client - svs.clients;
	ring->gen = gen;
	ring->seq = seq;

	/*broadcast it its to their team, and its not teamplay*/
	if (vt == VT_TEAM && !teamplay.ival)
		vt = VT_ALL;

	/*figure out which team members are meant to receive it*/
	for (j = 0; j < MAX_CLIENTS/8; j++)
		ring->receiver[j] = 0;
	for (j = 0, cl = svs.clients; j < sv.allocated_client_slots; j++, cl++)
	{
		if (cl->state != cs_spawned && cl->state != cs_connected)
			continue;
		/*spectators may only talk to spectators*/
		if (host_client->spectator && !sv_spectalk.ival)
			if (!cl->spectator)
				continue;

		if (vt == VT_TEAM)
		{
			// the spectator team
			if (host_client->spectator)
			{
				if (!cl->spectator)
					continue;
			}
			else
			{
				if (strcmp(cl->team, host_client->team) || cl->spectator)
					continue;	// on different teams
			}
		}
		else if (vt == VT_NONMUTED)
		{
			if (host_client->voice_mute[j>>3] & (1<<(j&3)))
				continue;
		}
		else if (vt >= VT_PLAYERSLOT0)
		{
			if (j != vt - VT_PLAYERSLOT0)
				continue;
		}

		ring->receiver[j>>3] |= 1<<(j&3);
	}

	if (sv.mvdrecording && sv_voip_record.ival && !(sv_voip_record.ival == 2 && !host_client->spectator))
	{
		sizebuf_t *msg;
		// non-team messages should be seen always, even if not tracking any player
		if (vt == VT_ALL && (!host_client->spectator || sv_spectalk.ival))
		{
			msg = MVDWrite_Begin (dem_all, 0, ring->datalen+6);
		}
		else
		{
			unsigned int cls;
			cls = ring->receiver[0] |
				(ring->receiver[1]<<8) |
				(ring->receiver[2]<<16) |
				(ring->receiver[3]<<24);
			msg = MVDWrite_Begin (dem_multiple, cls, ring->datalen+6);
		}

		MSG_WriteByte(msg, svcfte_voicechat);
		MSG_WriteByte(msg, ring->sender);
		MSG_WriteByte(msg, ring->gen);
		MSG_WriteByte(msg, ring->seq);
		MSG_WriteShort(msg, ring->datalen);
		SZ_Write(msg, ring->data, ring->datalen);
	}
}
void SV_VoiceInitClient(client_t *client)
{
	client->voice_target = VT_TEAM;
	client->voice_active = false;
	client->voice_read = voice.write;
	memset(client->voice_mute, 0, sizeof(client->voice_mute));
}
void SV_VoiceSendPacket(client_t *client, sizebuf_t *buf)
{
	unsigned int clno;
	qboolean send;
	struct voice_ring_s *ring;

	if (client->controller)
		client = client->controller;
	clno = client - svs.clients;

	if (!(client->fteprotocolextensions2 & PEXT2_VOICECHAT))
		return;
	if (!client->voice_active || client->num_backbuf)
	{
		client->voice_read = voice.write;
		return;
	}

	while(client->voice_read < voice.write)
	{
		/*they might be too far behind*/
		if (client->voice_read+VOICE_RING_SIZE < voice.write)
			client->voice_read = voice.write - VOICE_RING_SIZE;

		ring = &voice.ring[(client->voice_read) & (VOICE_RING_SIZE-1)];

		/*figure out if it was for us*/
		send = false;
		if (ring->receiver[clno>>3] & (1<<(clno&3)))
			send = true;

		/*if you're spectating, you can hear whatever your tracked player can hear*/
		if (host_client->spectator && host_client->spec_track)
			if (ring->receiver[(host_client->spec_track-1)>>3] & (1<<((host_client->spec_track-1)&3)))
				send = true;


		if (client->voice_mute[ring->sender>>3] & (1<<(ring->sender&3)))
			send = false;

		if (ring->sender == clno && !sv_voip_echo.ival)
			send = false;

		/*additional ways to block voice*/
		if (client->download)
			send = false;

		if (send)
		{
			if (buf->maxsize - buf->cursize < ring->datalen+5)
				break;
			MSG_WriteByte(buf, svcfte_voicechat);
			MSG_WriteByte(buf, ring->sender);
			MSG_WriteByte(buf, ring->gen);
			MSG_WriteByte(buf, ring->seq);
			MSG_WriteShort(buf, ring->datalen);
			SZ_Write(buf, ring->data, ring->datalen);
		}
		client->voice_read++;
	}
}

void SV_Voice_Ignore_f(void)
{
	unsigned int other;
	int type = 0;

	if (Cmd_Argc() < 2)
	{
		/*only a name = toggle*/
		type = 0;
	}
	else
	{
		/*mute if 1, unmute if 0*/
		if (atoi(Cmd_Argv(2)))
			type = 1;
		else
			type = -1;
	}
	other = atoi(Cmd_Argv(1));
	if (other >= MAX_CLIENTS)
		return;

	switch(type)
	{
	case -1:
		host_client->voice_mute[other>>3] &= ~(1<<(other&3));
		break;
	case 0:
		host_client->voice_mute[other>>3] ^= (1<<(other&3));
		break;
	case 1:
		host_client->voice_mute[other>>3] |= (1<<(other&3));
	}
}
void SV_Voice_Target_f(void)
{
	unsigned int other;
	char *t = Cmd_Argv(1);
	if (!strcmp(t, "team"))
		host_client->voice_target = VT_TEAM;
	else if (!strcmp(t, "all"))
		host_client->voice_target = VT_ALL;
	else if (!strcmp(t, "nonmuted"))
		host_client->voice_target = VT_NONMUTED;
	else if (*t >= '0' && *t <= '9')
	{
		other = atoi(t);
		if (other >= MAX_CLIENTS)
			return;
		host_client->voice_target = VT_PLAYERSLOT0 + other;
	}
	else
	{
		/*don't know who you mean, futureproofing*/
		host_client->voice_target = VT_TEAM;
	}
}
void SV_Voice_MuteAll_f(void)
{
	host_client->voice_active = false;
}
void SV_Voice_UnmuteAll_f(void)
{
	host_client->voice_active = true;
}
#endif

//Use of this function is on name only.
//Be aware that the maps directory should be restricted based on weather the file was from a pack file
//this is to preserve copyright - please do not breach due to a bug.
qboolean SV_AllowDownload (const char *name)
{
	extern	cvar_t	allow_download;
	extern	cvar_t	allow_download_skins;
	extern	cvar_t	allow_download_models;
	extern	cvar_t	allow_download_sounds;
	extern	cvar_t	allow_download_demos;
	extern	cvar_t	allow_download_maps;
	extern	cvar_t	allow_download_textures;
	extern	cvar_t	allow_download_packages;
	extern	cvar_t	allow_download_wads;
	extern	cvar_t	allow_download_root;
	extern	cvar_t	allow_download_configs;
	extern	cvar_t	allow_download_copyrighted;
	char cleanname[MAX_QPATH];
	int i=0;
	if (strlen(name) >= MAX_QPATH)
		return false;
	do
	{
		cleanname[i++] = *name;
	} while(*name++);
	name = cleanname;

	//allowed at all?
	if (!allow_download.ival)
		return false;

	//no subdirs?
	if (strstr (name, ".."))	//no under paths.
		return false;
	if (*name == '.')	//relative is pointless
		return false;
	if (*name == '/')	//no absolute.
		return false;
	if (strchr(name, '\\'))	//no windows paths - grow up lame windows users.
		return false;

	if (!strncmp(name, "package/", 8))
	{
		if (!strcmp("pk4", COM_FileExtension(name)) || !strcmp("pk3", COM_FileExtension(name)) || !strcmp("pak", COM_FileExtension(name)))
		{
			if (!allow_download_packages.ival)
				return false;
			/*do not permit 'id1/pak1.pak' or 'baseq3/pak0.pk3' or any similarly named packages. such packages would violate copyright, and must be obtained through other means (like buying the damn game)*/
			if (!allow_download_copyrighted.ival && !FS_GetPackageDownloadable(name+8))
				return false;

			return true;
		}
		return false;
	}

	if (strncmp(name,	"maps/", 5) == 0)
		return !!allow_download_maps.value;

	//skins?
	if (strncmp(name,	"skins/", 6) == 0)
		return !!allow_download_skins.value;
	//models
	if ((strncmp(name,	"progs/", 6) == 0) ||
		(strncmp(name,	"models/", 7) == 0))
		return !!allow_download_models.value;
	//sound
	if (strncmp(name,	"sound/", 6) == 0)
		return !!allow_download_sounds.value;
	//demos
	if (strncmp(name,	"demos/", 6) == 0)
		return !!allow_download_demos.value;

	//textures
	if (strncmp(name,	"textures/", 9) == 0)
		return !!allow_download_textures.value;

	//wads
	if (strncmp(name,	"wads/", 5) == 0)
		return !!allow_download_wads.value;
	if (!strcmp("wad", COM_FileExtension(name)))
		return !!allow_download_wads.value;

	//pak/pk3s.
	if (!strcmp("pk4", COM_FileExtension(name)) || !strcmp("pk3", COM_FileExtension(name)) || !strcmp("pak", COM_FileExtension(name)))
	{
		if (strnicmp(name, "pak", 3))	//don't give out core pak/pk3 files. This matches q3 logic.
			return !!allow_download_packages.value;
		else
			return !!allow_download_copyrighted.value;
	}

	if (!strcmp("cfg", COM_FileExtension(name)))
		return !!allow_download_configs.value;

	//root of gamedir
	if (!strchr(name, '/') && !allow_download_root.value)
	{
		if (strcmp(name, "csprogs.dat"))	//we always allow csprogs.dat to be downloaded (if downloads are permitted).
			return false;
	}

	//any other subdirs are allowed
	return true;
}

static int SV_LocateDownload(char *name, flocation_t *loc, char **replacementname, qboolean redirectpaks)
{
	extern	cvar_t	allow_download_anymap, allow_download_pakcontents;
	extern cvar_t sv_demoDir;
	qboolean protectedpak;
	qboolean found;

	if (replacementname)
		*replacementname = NULL;

	//mangle the name by making it lower case.
	{
		char *p;

		for (p = name; *p; p++)
			*p = (char)tolower((unsigned char)*p);
	}

	

	if (!SV_AllowDownload(name))
	{
		Sys_Printf ("%s denied download of %s due to path/name rules\n", host_client->name, name);
		return -2;	//not permitted (even if it exists).
	}

	//mvdsv demo downloading support demonum/ -> demos/XXXX (sets up the client paths)
	if (!strncmp(name, "demonum/", 8))
	{
		if (replacementname)
		{
			char mvdnamebuffer[MAX_QPATH];
			char *mvdname = SV_MVDNum(mvdnamebuffer, sizeof(mvdnamebuffer), atoi(name+8));
			if (!mvdname)
			{
				SV_ClientPrintf (host_client, PRINT_HIGH, "%s is an invalid MVD demonum.\n", name+8);
				Sys_Printf ("%s requested invalid demonum %s\n", host_client->name, name+8);
				return -1;	//not found
			}
			*replacementname = va("demos/%s\n", mvdname);
			return -4;	//redirect
		}
	}

	//mvdsv demo downloading support. demos/ -> demodir (sets up the server paths)
	if (!strncmp(name, "demos/", 6))
		name = va("%s/%s", sv_demoDir.string, name+6);

	if (!strncmp(name, "package/", 8))
	{
		vfsfile_t *f = FS_OpenVFS(name+8, "rb", FS_ROOT);
		if (f)
		{
			VFS_CLOSE(f);
			return -5;	//found package
		}
		else
			return -1;	//not found/unable to open
	}
	else if (Terrain_LocateSection(name, loc))
	{
		found = true;
	}
	else
		found = FS_FLocateFile(name, FSLFRT_IFFOUND, loc);

	//nexuiz names certain files as .wav but they're really .ogg on disk.
	if (!found && replacementname)
	{
		if (!strcmp(COM_FileExtension(name), "wav"))
		{
			char tryogg[MAX_QPATH];
			COM_StripExtension(name, tryogg, sizeof(tryogg));
			COM_DefaultExtension(tryogg, ".ogg", sizeof(tryogg));

			found = FS_FLocateFile(tryogg, FSLFRT_IFFOUND, loc);
			if (found)
			{
				name = *replacementname = va("%s", tryogg);
			}
		}
	}
	//nexuiz also names files with absolute paths (yet sounds are meant to have an extra prefix)
	//this results in clients asking for sound/sound/blah.wav (or sound/sound/blah.ogg for nexuiz)
	if (!found && replacementname)
	{
		if (!strncmp(name, "sound/", 6))
		{
			int result;
			result = SV_LocateDownload(name+6, loc, replacementname, redirectpaks);
			if (!result)
			{	//if that was successful... redirect to it.
				result = -4;
				*replacementname = name+6;
			}
			return result;
		}
	}

	if (found)
	{
		protectedpak = com_file_copyprotected;

		// special check for maps, if it came from a pak file, don't allow download
		if (protectedpak)
		{
			if (!allow_download_anymap.value && !strncmp(name, "maps/", 5))
			{
				Sys_Printf ("%s denied download of %s - it is in a pak\n", host_client->name, name+8);
				return -2;
			}
		}

		if (replacementname)
		{
			char *pakname = FS_WhichPackForLocation(loc);
			if (pakname && SV_AllowDownload(pakname))
			{
				//return loc of the pak instead.
				if (FS_FLocateFile(name, FSLFRT_IFFOUND, loc))
				{
					//its inside a pak file, return the name of this file instead
					*replacementname = pakname;
					return -4;	//redirect
				}
				else
					Con_Printf("Failed to read %s\n", pakname);
			}
		}

		if (protectedpak)
		{	//if its in a pak file, don't allow downloads if we don't allow the contents of paks to be sent.
			if (!allow_download_pakcontents.value)
			{
				Sys_Printf ("%s denied download of %s - it is in a pak\n", host_client->name, name+8);
				return -2;
			}
		}

		if (replacementname && *replacementname)
			return -4;
		return 0;
	}
	return -1;	//not found
}

//this function is only meaningful for nq/qw
void SV_DownloadSize_f(void)
{
	flocation_t loc;
	char *name = Cmd_Argv(1);
	char *redirected = "";

	switch(SV_LocateDownload(name, &loc, &redirected, true))
	{
	case -4: /*redirect*/
		name = va("dlsize \"%s\" r \"%s\"\n", name, redirected);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
		ClientReliableWrite_String (host_client, name);
		break;
	default:
	case -1: /*not found*/
		name = va("dlsize \"%s\" e\n", name);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
		ClientReliableWrite_String (host_client, name);
		break;
	case -2: /*permission*/
		name = va("dlsize \"%s\" p\n", name);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
		ClientReliableWrite_String (host_client, name);
		break;
	case -5: /*package*/
	case 0: /*exists*/
		name = va("dlsize \"%s\" %u\n", name, loc.len);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
		ClientReliableWrite_String (host_client, name);
		break;
	case -6: /*heightmap section*/
		//don't reply.
		break;
	}
}

/*
==================
SV_BeginDownload_f
==================
*/
void SV_BeginDownload_f(void)
{
	char *name = Cmd_Argv(1);
	char *redirection = NULL;
	extern	cvar_t	allow_download_anymap, allow_download_pakcontents;
	extern cvar_t sv_demoDir;
	flocation_t loc;
	int result;

	if (ISNQCLIENT(host_client) && host_client->protocol != SCP_DARKPLACES7)
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Your client isn't meant to support downloads\n");
		return;
	}

	*host_client->downloadfn = 0;

	if (host_client->download)
	{
		VFS_CLOSE (host_client->download);
		host_client->download = NULL;
	}

	result = SV_LocateDownload(name, &loc, &redirection, false);

	if (result == -5)
	{
		//package download
		result = 0;
		host_client->download = FS_OpenVFS(name+8, "rb", FS_ROOT);
	}
	else
	{
		//redirection protocol-specific code goes here.
		if (result == -4)
		{
#ifdef PEXT_CHUNKEDDOWNLOADS
			if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
			{
				//redirect the client (before the message saying download failed)
				char *s = va("dlsize \"%s\" r \"%s\"\n", name, redirection);
				ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(s));
				ClientReliableWrite_String (host_client, s);
			}
#endif
		}

		if (result == 0)
		{	//if we are allowed and could find it
			host_client->download = FS_OpenReadLocation(&loc);
		}
	}

	if (!host_client->download)
		result = -1;	//this isn't likely, but hey.

	//handle errors
	if (result != 0)
	{	// don't allow anything with .. path
		char *error;
		switch(result)
		{
		default:
			error = "Download could not be found\n";
			break;
		case -2:
			error = "Download permission denied\n";
			break;
		case -4:
			result = -1;
			error = "Package contents not available individually\n";
			break;
		}
#ifdef PEXT_CHUNKEDDOWNLOADS
		if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
		{
			ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(name));
			ClientReliableWrite_Long (host_client, -1);
			ClientReliableWrite_Long (host_client, result);
			ClientReliableWrite_String (host_client, name);
		}
		else
#endif
		if (ISNQCLIENT(host_client))
		{
			SV_PrintToClient(host_client, PRINT_HIGH, error);

			ClientReliableWrite_Begin (host_client, svc_stufftext, 2+12);
			ClientReliableWrite_String (host_client, "\nstopdownload\n");
		}
		else
		{
			SV_PrintToClient(host_client, PRINT_HIGH, error);

			ClientReliableWrite_Begin (host_client, ISQ2CLIENT(host_client)?svcq2_download:svc_download, 4);
			ClientReliableWrite_Short (host_client, -1);
			ClientReliableWrite_Byte (host_client, 0);
		}
		return;
	}

	Q_strncpyz(host_client->downloadfn, name, sizeof(host_client->downloadfn));
	host_client->downloadcount = 0;

	host_client->downloadsize = VFS_GETLEN(host_client->download);

#ifdef PEXT_CHUNKEDDOWNLOADS
	if (host_client->fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS)
	{
		if (host_client->download->seekingisabadplan)
		{	//if seeking is a bad plan (for whatever reason - usually because of zip files)
			//create a temp file instead
			int i, len;
			char buffer[8192];
			vfsfile_t *tmp;
			tmp = FS_OpenTemp();

			for (i = 0; ; i+=len)
			{
				len = sizeof(buffer);
				if (len > host_client->downloadsize-i)
					len = host_client->downloadsize-i;
				if (len == 0)
					break;
				VFS_READ(host_client->download, buffer, len);
				VFS_WRITE(tmp, buffer, len);
			}
			VFS_CLOSE(host_client->download);
			host_client->download = tmp;
		}

		ClientReliableWrite_Begin (host_client, svc_download, 10+strlen(host_client->downloadfn));
		ClientReliableWrite_Long (host_client, -1);
		ClientReliableWrite_Long (host_client, host_client->downloadsize);
		ClientReliableWrite_String (host_client, host_client->downloadfn);
	}
	else
#endif

	if (ISNQCLIENT(host_client))
	{
		char *s = va("\ncl_downloadbegin %i %s\n", host_client->downloadsize, host_client->downloadfn);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(s));
		ClientReliableWrite_String (host_client, s);
	}
	else
		SV_NextDownload_f ();

	SV_EndRedirect();
	Con_Printf ("Downloading %s to %s\n", host_client->downloadfn, host_client->name);
}

void SV_StopDownload_f(void)
{
	//this doesn't mean the download failed or was canceled.
	if (host_client->download)
	{
		VFS_CLOSE (host_client->download);
		host_client->download = NULL;
	}
	else
		SV_ClientPrintf(host_client, PRINT_HIGH, "But you're not downloading anything\n");

	host_client->downloadstarted = false;
}

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


/*
==================
SV_SayOne_f
==================
*/
void SV_SayOne_f (void)
{
	char	text[1024];
	client_t	*to;
	int i;
	char *s, *s2;
	int clnum=-1;

	if (Cmd_Argc () < 3)
		return;

	while((to = SV_GetClientForString(Cmd_Argv(1), &clnum)))
	{
		if (host_client->spectator)
		{
			if (!sv_spectalk.value || to->spectator)
				Q_snprintfz (text, sizeof(text), "[SPEC] {%s}:", host_client->name);
			else
				continue;
		}
		else
			Q_snprintfz (text, sizeof(text), "{%s}:", host_client->name);

		if (host_client->ismuted)
		{
			SV_ClientTPrintf(host_client, PRINT_CHAT,
					STL_YOUAREMUTED);
			return;
		}


		for (i = 2; ; i++)
		{
			s = Cmd_Argv(i);
			if (!*s)
				break;

			if (strlen(text) + strlen(s) + 2 >= sizeof(text)-1)
				break;
			strcat(text, " ");
			strcat(text, s);
		}

	//filter out '\n' and '\r'
		s = text;
		s2 = text;
		while(*s2)
		{
			if (*s2 == '\r' || *s2 == '\n')
			{
				s2++;
				continue;
			}
			*s = *s2;
			s++;
			s2++;
		}
		*s = '\0';

		strcat(text, "\n");
		SV_ClientPrintf(to, PRINT_CHAT, "%s", text);
	}

	if (clnum==-1)	//none found
	{
		SV_ClientTPrintf(host_client, PRINT_CHAT, STL_NAMEDCLIENTDOESNTEXIST);
		return;
	}
}

float SV_CheckFloodProt(client_t *client)
{
	if (!sv_floodprotect.value)
		return 0;
	if (sv_floodprotect_messages.value <= 0 || sv_floodprotect_interval.value <= 0)
		return 0;
	if (sv.paused)
		return 0;
	if (realtime < client->lockedtill)
		return client->lockedtill - realtime;

	if (client->floodprotmessage > sv_floodprotect_messages.value)
	{
		client->lockedtill = realtime + sv_floodprotect_silencetime.value;
		client->floodprotmessage = 0.0;
		client->lastspoke = 0.0;
		if (sv_floodprotect_sendmessage.string[0])
			 SV_ClientPrintf(client, PRINT_CHAT, "FloodProt: %s\n", sv_floodprotect_sendmessage.string);
		return sv_floodprotect_silencetime.value;
	}

	return 0;
}

void SV_PushFloodProt(client_t *client)
{
	if (!sv_floodprotect.value)
		return;
	if (sv_floodprotect_messages.value <= 0 || sv_floodprotect_interval.value <= 0)
		return;
	if (sv.paused)
		return;

	if (client->lastspoke)
	{
		client->floodprotmessage -= (realtime - client->lastspoke)
			* sv_floodprotect_messages.value
			/ sv_floodprotect_interval.value;
		client->floodprotmessage = max(0, client->floodprotmessage);
		client->floodprotmessage++;
	}
	else
		client->floodprotmessage = 1.0;
	client->lastspoke = realtime;
}

/*
==================
SV_Say
==================
*/
void SV_Say (qboolean team)
{
	client_t *client;
	int		j;
	char	*p;
	char	text[1024];
	char	t1[32], *t2;
	int cls = 0;
	float floodtime;
	sizebuf_t *msg;

	qboolean sent[MAX_CLIENTS];	//so we don't send to the same splitscreen connection twice. (it's ugly)
	int cln;

	qboolean mvdrecording;

	char *s, *s2;

	if (Cmd_Argc () < 2)
		return;

	Sys_ServerActivity();

	memset(sent, 0, sizeof(sent));

	if (team)
	{
		Q_strncpyz (t1, Info_ValueForKey (host_client->userinfo, "team"), sizeof(t1));
	}

	if (host_client->spectator && (!sv_spectalk.value || team))
		Q_snprintfz (text, sizeof(text), "[SPEC] %s: ", host_client->name);
	else if (team)
		Q_snprintfz (text, sizeof(text), "(%s): ", host_client->name);
	else
		Q_snprintfz (text, sizeof(text), "%s: ", host_client->name);

	if (host_client->ismuted)
	{
		SV_ClientTPrintf(host_client, PRINT_CHAT,
			STL_MUTEDCHAT);
		return;
	}

#ifdef VM_Q1
	if (Q1QVM_ClientSay(sv_player, team))
		return;
#endif

	if ((floodtime=SV_CheckFloodProt(host_client)))
	{
		SV_ClientTPrintf(host_client, PRINT_CHAT,
				STL_FLOODPROTTIME,
					(int) (floodtime));
		return;
	}
	SV_PushFloodProt(host_client);

	p = Cmd_Args();

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

	if (strlen(text)+strlen(p)+2 >= sizeof(text)-10)
	{
		SV_ClientTPrintf(host_client, PRINT_CHAT, STL_BUFFERPROTECTION);
		return;
	}
	if (svprogfuncs)
		if (PR_QCChat(p, team))	//true if handled.
			return;

	Q_strcat(text, p);

	//filter out '\n' and '\r'
	if (sv_chatfilter.value)
	{
		s = text;
		s2 = text;
		while(*s2)
		{
			if (*s2 == '\r' || *s2 == '\n')
			{
				s2++;
				continue;
			}
			*s = *s2;
			s++;
			s2++;
		}
		*s = '\0';
	}

	Q_strcat(text, "\n");

	Sys_Printf ("%s", text);

	mvdrecording = sv.mvdrecording;
	sv.mvdrecording = false;	//so that the SV_ClientPrintf doesn't send to all players.
	for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
	{
		if (client->state != cs_spawned && client->state != cs_connected)
			continue;
		if (host_client->spectator && !sv_spectalk.value)
			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
			}
		}

		cls |= 1 << j;

//make sure we don't send the say to the same client 20 times due to splitscreen
		if (client->controller)
			cln = client->controller - svs.clients;
		else
			cln = client - svs.clients;
		if (sent[cln])
			continue;
		else
			sent[cln] = true;

		SV_ClientPrintf(client, PRINT_CHAT, "%s", text);
	}
	sv.mvdrecording = mvdrecording;

	if (!sv.mvdrecording || !cls)
		return;

	// non-team messages should be seen always, even if not tracking any player
	if (!team && ((host_client->spectator && sv_spectalk.value) || !host_client->spectator))
		msg = MVDWrite_Begin (dem_all, 0, strlen(text)+3);
	else
		msg = MVDWrite_Begin (dem_multiple, cls, strlen(text)+3);

	MSG_WriteByte (msg, svc_print);
	MSG_WriteByte (msg, PRINT_CHAT);
	MSG_WriteString (msg, 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;

#ifdef SERVER_DEMO_PLAYBACK
	if (sv.demofile)
	{
		for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
		{
			if (!*sv.recordedplayer[j].userinfo)
				continue;
			ClientReliableWrite_Begin (host_client, svc_updateping, 4);
			ClientReliableWrite_Byte (host_client, j);
			ClientReliableWrite_Short (host_client, sv.recordedplayer[j].ping);
			ClientReliableWrite_Begin (host_client, svc_updatepl, 4);
			ClientReliableWrite_Byte (host_client, j);
			ClientReliableWrite_Byte (host_client, sv.recordedplayer[j].pl);
		}
		return;
	}
#endif
	if (ISNQCLIENT(host_client))
	{
		char *s;
		ClientReliableWrite_Begin(host_client, svc_stufftext, 15+10*MAX_CLIENTS);
		ClientReliableWrite_SZ(host_client, "pingplreport", 12);
		for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
		{
			s = va(" %i %i", SV_CalcPing(client, false), client->lossage);
			ClientReliableWrite_SZ(host_client, s, strlen(s));
		}
		ClientReliableWrite_Byte (host_client, '\n');
		ClientReliableWrite_Byte (host_client, '\0');

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

			ClientReliableWrite_Begin (host_client, svc_updateping, 4);
			ClientReliableWrite_Byte (host_client, j);
			ClientReliableWrite_Short (host_client, SV_CalcPing(client, false));
			ClientReliableWrite_Begin (host_client, svc_updatepl, 4);
			ClientReliableWrite_Byte (host_client, j);
			ClientReliableWrite_Byte (host_client, client->lossage);
		}
	}
}



/*
==================
SV_Kill_f
==================
*/
void SV_Kill_f (void)
{
	float floodtime;

#ifdef HLSERVER
	if (svs.gametype == GT_HALFLIFE)
	{
		HLSV_ClientCommand(host_client);
		return;
	}
#endif

#ifdef VM_Q1
	if (svs.gametype == GT_Q1QVM)
	{
		pr_global_struct->time = sv.world.physicstime;
		pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
		Q1QVM_ClientCommand();
		return;
	}
#endif
	if (svs.gametype != GT_PROGS)
		return;

	if (sv_player->v->health <= 0)
	{
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_NOSUICIDEWHENDEAD);
		return;
	}

	if (sv_floodprotect_suicide.value)
	{
		if ((floodtime = SV_CheckFloodProt(host_client)))
		{
			SV_ClientPrintf (host_client, PRINT_HIGH, "You can't suicide for %i seconds\n", (int)floodtime);
			return;
		}
		SV_PushFloodProt(host_client);
	}

	pr_global_struct->time = sv.world.physicstime;
	pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);

	PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientKill);
}

/*
==================
SV_TogglePause
==================
*/
qboolean SV_TogglePause (client_t *initiator)
{
	int newv;

	newv = sv.paused^1;

	if (!PR_ShouldTogglePause(initiator, newv))
		return false;

	sv.paused = newv;

	sv.pausedstart = Sys_DoubleTime();

	return true;
}


/*
==================
SV_Pause_f
==================
*/
void SV_Pause_f (void)
{
	if (!pausable.value)
	{
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_CANTPAUSE);
		return;
	}

	if (host_client->spectator && !svs.demoplayback)
	{
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_CANTPAUSESPEC);
		return;
	}

	if (SV_TogglePause(host_client))
	{
		if (sv.paused & 1)
			SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTPAUSED, host_client->name);
		else
			SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTUNPAUSED, host_client->name);
	}

}


/*
=================
SV_Drop_f

The client is going to disconnect, so remove the connection immediately
=================
*/
void SV_Drop_f (void)
{
	extern cvar_t sv_fullredirect;

	SV_EndRedirect ();
	if (!host_client->drop)
	{
		if (host_client->redirect)
			SV_BroadcastPrintf (PRINT_HIGH, "%s redirected to %s\n", host_client->name, sv_fullredirect.string);
		else
		{
			if (!host_client->spectator)
				SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTDROPPED, host_client->name);
		}
		host_client->drop = true;
	}
}

/*
=================
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
#ifdef SERVER_DEMO_PLAYBACK
		&& !sv.demofile
#endif
		)
		return;

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

	i = atoi(Cmd_Argv(1));
#ifdef SERVER_DEMO_PLAYBACK
	if (*sv.recordedplayer[i].userinfo)
	{
		host_client->spec_track = i+1;
		return;
	}
#endif

	if (i < 0 || i >= MAX_CLIENTS || svs.clients[i].state != cs_spawned ||
		svs.clients[i].spectator)
	{
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_INVALIDTRACKCLIENT);
		host_client->spec_track = 0;
		ent = EDICT_NUM(svprogfuncs, host_client - svs.clients + 1);
		tent = EDICT_NUM(svprogfuncs, 0);
		ent->v->goalentity = EDICT_TO_PROG(svprogfuncs, tent);
		return;
	}
	host_client->spec_track = i + 1; // now tracking

	ent = EDICT_NUM(svprogfuncs, host_client - svs.clients + 1);
	tent = EDICT_NUM(svprogfuncs, i + 1);
	ent->v->goalentity = EDICT_TO_PROG(svprogfuncs, tent);
}


/*
=================
SV_Rate_f

Change the bandwidth estimate for a client
=================
*/
void SV_Rate_f (void)
{
	if (Cmd_Argc() != 2)
	{
		int rate = SV_RateForClient(host_client);
		if (!rate)
			SV_ClientPrintf (host_client, PRINT_HIGH, "Effective rate is unlimited\n");
		else
			SV_ClientPrintf (host_client, PRINT_HIGH, "Effective rate %i\n", rate);
		return;
	}

	Info_SetValueForKey (host_client->userinfo, "rate", Cmd_Argv(1), sizeof(host_client->userinfo));
	SV_ExtractFromUserinfo (host_client, true);

	SV_ClientTPrintf (host_client, PRINT_HIGH, STL_RATESETTO, SV_RateForClient(host_client));
}


/*
=================
SV_Msg_f

Change the message level for a client
=================
*/
void SV_Msg_f (void)
{
	if (Cmd_Argc() != 2)
	{
		SV_ClientTPrintf (host_client, PRINT_HIGH, STL_CURRENTMSGLEVEL,
			host_client->messagelevel);
		return;
	}

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

	SV_ClientTPrintf (host_client, PRINT_HIGH, STL_MSGLEVELSET, host_client->messagelevel);
}

qboolean SV_UserInfoIsBasic(char *infoname)
{
	int i;
	char *basicinfos[] = {

		"name",
		"team",
		"skin",
		"topcolor",
		"bottomcolor",

		NULL};

	for (i = 0; basicinfos[i]; i++)
	{
		if (*infoname == '*' || !strcmp(infoname, basicinfos[i]))
			return true;
	}
	return false;
}

/*
==================
SV_SetInfo_f

Allow clients to change userinfo
==================
*/
void SV_SetInfo_f (void)
{
	int i, j;
	char oldval[MAX_INFO_STRING];
	char *key, *val;
	qboolean basic;	//infos that we send to any old qw client.
	client_t *client;


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

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

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

	if (strstr(Cmd_Argv(1), "\\") || strstr(Cmd_Argv(2), "\\"))
		return;		// illegal char

	Q_strncpyz(oldval, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)), MAX_INFO_STRING);

#ifdef VM_Q1
	if (Q1QVM_UserInfoChanged(sv_player))
		return;
#endif

	Info_SetValueForKey (host_client->userinfo, Cmd_Argv(1), Cmd_Argv(2), sizeof(host_client->userinfo));
// name is extracted below in ExtractFromUserInfo
//	strncpy (host_client->name, Info_ValueForKey (host_client->userinfo, "name")
//		, sizeof(host_client->name)-1);
//	SV_FullClientUpdate (host_client, &sv.reliable_datagram);
//	host_client->sendinfo = true;

	if (!strcmp(Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)), oldval))
		return; // key hasn't changed

	// process any changed values
	SV_ExtractFromUserinfo (host_client, true);

	if (progstype != PROG_QW && !strcmp(Cmd_Argv(1), "bottomcolor"))
	{	//team fortress has a nasty habit of booting people without this
		sv_player->v->team = atoi(Cmd_Argv(2))+1;
	}

	if (*Cmd_Argv(1) != '_')
	{
		i = host_client - svs.clients;
		key = Cmd_Argv(1);
		val = Info_ValueForKey(host_client->userinfo, key);

		basic = SV_UserInfoIsBasic(key);

		if (basic)
			Info_SetValueForKey (host_client->userinfobasic, key, val, sizeof(host_client->userinfobasic));
		for (j = 0; j < MAX_CLIENTS; j++)
		{
			client = svs.clients+j;
			if (client->state < cs_connected)
				continue;	// reliables go to all connected or spawned
			if (client->controller)
				continue;	//splitscreen

			if (client->protocol == SCP_BAD)
				continue;	//botclient

			if (ISQWCLIENT(client))
			{
				if (basic || (client->fteprotocolextensions & PEXT_BIGUSERINFOS))
				{
					ClientReliableWrite_Begin(client, svc_setinfo, 1+1+strlen(key)+1+strlen(val)+1);
					ClientReliableWrite_Byte(client, i);
					ClientReliableWrite_String(client, key);
					ClientReliableWrite_String(client, val);
				}
			}
			if (ISNQCLIENT(client))
			{
				if (!strcmp(key, "name"))
				{
					ClientReliableWrite_Begin(client, svc_updatename, 1+1+strlen(val)+1);
					ClientReliableWrite_Byte(client, i);
					ClientReliableWrite_String(client, val);
				}
			}
		}

		if (sv.mvdrecording)
		{
			sizebuf_t *msg = MVDWrite_Begin (dem_all, 0, strlen(key)+strlen(val)+4);
			MSG_WriteByte (msg, svc_setinfo);
			MSG_WriteByte (msg, i);
			MSG_WriteString (msg, key);
			MSG_WriteString (msg, val);
		}
	}

	SV_LogPlayer(host_client, "userinfo changed");

	PR_ClientUserInfoChanged(Cmd_Argv(1), oldval, Info_ValueForKey(host_client->userinfo, Cmd_Argv(1)));
}

/*
==================
SV_ShowServerinfo_f

Dumps the serverinfo info string
==================
*/
void SV_ShowServerinfo_f (void)
{
	SV_BeginRedirect(RD_CLIENT, host_client->language);
	Info_Print (svs.info);
	SV_EndRedirect();
}

void SV_NoSnap_f(void)
{
	SV_LogPlayer(host_client, "refused snap");

	if (*host_client->uploadfn)
	{
		*host_client->uploadfn = 0;
		SV_BroadcastTPrintf (PRINT_HIGH, STL_SNAPREFUSED, host_client->name);
	}
}

//3 votes per player.
typedef struct voteinfo_s {
	struct voteinfo_s *next;
	float timeout;
	int clientid;
	char command[1];
} voteinfo_t;
voteinfo_t *voteinfo;


void VoteAdd (char *cmd, int id)
{
	voteinfo_t *vote;
	vote = Z_Malloc(sizeof(voteinfo_t)+strlen(cmd));	//null term is part of voteinfo_t
	strcpy(vote->command, cmd);
	vote->clientid = id;
	vote->timeout = realtime+votetime.value*60;
	vote->next = voteinfo;
	voteinfo = vote;
}

void VoteRemoveCommands(char *command, int id)	//all of one command
{
	voteinfo_t *vote, *prev;
	prev = NULL;
	for (vote = voteinfo; vote; vote = vote->next)
	{
		if ((!command || !strcmp(vote->command, command)) && (vote->clientid == id || id == -1))
		{
			if (prev)
				prev->next = vote->next;
			else
				voteinfo = vote->next;

			Z_Free(vote);
			VoteRemoveCommands(command, id);
			return;
		}
		else
			prev = vote;
	}
}
void VoteFlushAll(void)
{
	VoteRemoveCommands(NULL, -1);
}

int VoteCount(char *command, int id)	//all of one command
{
	voteinfo_t *vote;
	int num=0;
	for (vote = voteinfo; vote; vote = vote->next)
	{
		if (!command || !strcmp(vote->command, command))
		{
			if (vote->clientid == id || id == -1)
				num++;
		}
	}

	return num;
}

void VoteCheckTimes(void)
{
	voteinfo_t *vote, *prev;
	prev = NULL;
	for (vote = voteinfo; vote; )
	{
		if (vote->timeout < realtime)
		{
			if (prev)
				prev->next = vote->next;
			else
				voteinfo = vote->next;

			Z_Free(vote);

			if (prev)
				vote = prev;
			else
				vote = voteinfo;
		}
		else
			prev = vote;

		vote = vote->next;
	}
}

void SV_Vote_f (void)
{
	char *command = Cmd_Args();
	char *base;
	int id = host_client->userid;
	int num;
	int totalusers = 0;
	qboolean passes;

	if (!votelevel.value)
	{
		SV_ClientTPrintf(host_client, PRINT_HIGH, STL_NOVOTING);
		return;
	}
	if (host_client->ismuted)
	{
		SV_ClientTPrintf(host_client, PRINT_HIGH, STL_MUTEDVOTE);
		return;
	}

	Cmd_ExecLevel = votelevel.value;
	base = command;
	while(*base>' ')
		base++;
	if (*base)
		*base = '\0';
	else
		base = NULL;
	if (strchr(command, ';') || !strcmp(command, "if"))
	{
		SV_ClientTPrintf(host_client, PRINT_HIGH, STL_BADVOTE);
		return;
	}
	num = Cmd_Level(command);
	if (base)
		*base = ' ';
	if (num != Cmd_ExecLevel)
	{
		SV_ClientTPrintf(host_client, PRINT_HIGH, STL_BADVOTE);
		return;
	}


	VoteCheckTimes();

	for (num = 0; num < sv.allocated_client_slots; num++)
		if (svs.clients[num].state == cs_spawned)
			totalusers++;

	if (VoteCount(command, id))
	{
		VoteRemoveCommands(command, id);
		SV_ClientTPrintf(host_client, PRINT_HIGH, STL_OLDVOTEREMOVED);
		return;
	}
	if (VoteCount(NULL, id)>=3)
	{
		VoteRemoveCommands(NULL, id);
		SV_ClientTPrintf(host_client, PRINT_HIGH, STL_VOTESREMOVED);
	}

	num = VoteCount(command, -1)+1;

	passes = true;
	if (votepercent.value < 0 && (float)(totalusers-num) >= 0.5*totalusers)
		passes = false;
	if (votepercent.value >= 0 && num <= totalusers*votepercent.value/100)
		passes = false;
	if (num < voteminimum.value)
		passes = false;

	if (passes)	//>min number of votes, and meets the percent required
	{
		SV_BroadcastTPrintf(PRINT_HIGH, STL_FINALVOTE, host_client->name, command);

		VoteRemoveCommands(command, -1);
		Cbuf_AddText(command, votelevel.value);
		Cbuf_AddText("\n", votelevel.value);
		//Cmd_ExecuteString (command, votelevel.value);
		return;
	}
	else	//otherwise, try later.
	{
		SV_BroadcastTPrintf(PRINT_HIGH, STL_VOTE, host_client->name, command);

		VoteAdd(command, id);
	}
}

void Cmd_Notarget_f (void)
{
	if (!SV_MayCheat())
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
		return;
	}

	SV_LogPlayer(host_client, "notarget cheat");
	if ((int) (sv_player->v->flags = (int) sv_player->v->flags ^ FL_NOTARGET) & FL_NOTARGET)
		SV_ClientPrintf (host_client, PRINT_HIGH, "notarget ON\n");
	else
		SV_ClientPrintf (host_client, PRINT_HIGH, "notarget OFF\n");
}

//Sets client to godmode
void Cmd_God_f (void)
{
	if (!SV_MayCheat())
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
		return;
	}

	SV_LogPlayer(host_client, "god cheat");
	if ((int) (sv_player->v->flags = (int) sv_player->v->flags ^ FL_GODMODE) & FL_GODMODE)
		SV_ClientPrintf (host_client, PRINT_HIGH, "godmode ON\n");
	else
		SV_ClientPrintf (host_client, PRINT_HIGH, "godmode OFF\n");
}


void Cmd_Give_f (void)
{
	char *t;
	int v;

#ifdef HLSERVER
	if (svs.gametype == GT_HALFLIFE)
	{
		HLSV_ClientCommand(host_client);
		return;
	}
#endif

	if (!SV_MayCheat())
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
		return;
	}

	t = Cmd_Argv(1);
	v = atoi (Cmd_Argv(2));

	SV_LogPlayer(host_client, "give cheat");
	if (strlen(t) == 1 && (Cmd_Argc() == 3 || (*t>='0' && *t <= '9')))
	{
		switch (t[0])
		{
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			sv_player->v->items = (int) sv_player->v->items | IT_SHOTGUN<< (t[0] - '2');
			break;

		case 's':
			sv_player->v->ammo_shells = v;
			break;
		case 'n':
			sv_player->v->ammo_nails = v;
			break;
		case 'r':
			sv_player->v->ammo_rockets = v;
			break;
		case 'h':
			sv_player->v->health = v;
			break;
		case 'c':
			sv_player->v->ammo_cells = v;
			break;
		default:
			Con_Printf("give: unknown item\n");
		}
	}
	else
	{
		if (developer.value < 2 && host_client->netchan.remote_address.type != NA_LOOPBACK)	//we don't want clients doing nasty things... like setting movetype 3123
		{
			SV_PrintToClient(host_client, PRINT_HIGH, "'give' debugging command requires developer 2 set on the server before you may use it\n");
		}
		else
		{
			int oldself;
			oldself = pr_global_struct->self;
			pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
			SV_ClientPrintf(host_client, PRINT_HIGH, "Result: %s\n", svprogfuncs->EvaluateDebugString(svprogfuncs, Cmd_Args()));
			pr_global_struct->self = oldself;
		}
	}
}

void Cmd_Noclip_f (void)
{
	if (!SV_MayCheat())
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
		return;
	}

	SV_LogPlayer(host_client, "noclip cheat");
	if (sv_player->v->movetype != MOVETYPE_NOCLIP)
	{
		sv_player->v->movetype = MOVETYPE_NOCLIP;
		sv_player->v->solid = SOLID_TRIGGER;
		SV_ClientPrintf (host_client, PRINT_HIGH, "noclip ON\n");
	}
	else
	{
		sv_player->v->movetype = MOVETYPE_WALK;
		if (sv_player->v->health > 0)
			sv_player->v->solid = SOLID_SLIDEBOX;
		else
			sv_player->v->solid = SOLID_NOT;
		SV_ClientPrintf (host_client, PRINT_HIGH, "noclip OFF\n");
	}
}

void Cmd_Fly_f (void)
{
	if (!SV_MayCheat())
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
		return;
	}

	SV_LogPlayer(host_client, "fly cheat");
	if (sv_player->v->movetype != MOVETYPE_FLY)
	{
		sv_player->v->movetype = MOVETYPE_FLY;
		SV_ClientPrintf (host_client, PRINT_HIGH, "flymode ON\n");
	}
	else
	{
		sv_player->v->movetype = MOVETYPE_WALK;
		if (sv_player->v->health > 0)
			sv_player->v->solid = SOLID_SLIDEBOX;
		else
			sv_player->v->solid = SOLID_NOT;
		SV_ClientPrintf (host_client, PRINT_HIGH, "flymode OFF\n");
	}
}

/*
====================
Host_SetPos_f  UDC
By Alex Shadowalker (and added to fte because he kept winging)
====================
*/
void Cmd_SetPos_f(void)
{
	if (!SV_MayCheat())
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Cheats are not allowed on this server\n");
		return;
	}

	if (Cmd_Argc() != 4)
	{
		SV_ClientPrintf(host_client, PRINT_HIGH, "setpos %f %f %f\n", sv_player->v->origin[0], sv_player->v->origin[1], sv_player->v->origin[2]);
		return;
	}
	SV_LogPlayer(host_client, "setpos cheat");
	if (sv_player->v->movetype != MOVETYPE_NOCLIP)
	{
		sv_player->v->movetype = MOVETYPE_NOCLIP;
		SV_PrintToClient(host_client, PRINT_HIGH, "noclip on\n");
	}

	sv_player->v->origin[0] = atof(Cmd_Argv(1));
	sv_player->v->origin[1] = atof(Cmd_Argv(2));
	sv_player->v->origin[2] = atof(Cmd_Argv(3));
	World_LinkEdict (&sv.world, (wedict_t*)sv_player, false);
}

void SV_SetUpClientEdict (client_t *cl, edict_t *ent)
{
	extern int pr_teamfield;
#ifdef VM_Q1
	if (svs.gametype == GT_Q1QVM)
	{
		string_t preserve;
		preserve = ent->v->netname;
		Q1QVMED_ClearEdict(ent, true);
		ent->v->netname = preserve;
	}
	else
#endif
	{
		if (progstype != PROG_NQ)	//allow frikbots to work in NQ mods (but not qw!)
			ED_Clear(svprogfuncs, ent);
		ent->v->netname = PR_SetString(svprogfuncs, cl->name);
	}
	ED_Spawned(ent, false);
	ent->isfree = false;

	ent->v->colormap = NUM_FOR_EDICT(svprogfuncs, ent);

	if (pr_teamfield)
		((string_t *)ent->v)[pr_teamfield] = (string_t)(cl->team-svprogfuncs->stringtable);

	{
		int tc = atoi(Info_ValueForKey(cl->userinfo, "topcolor"));
		int bc = atoi(Info_ValueForKey(cl->userinfo, "bottomcolor"));
		if (tc < 0 || tc > 13)
			tc = 0;
		if (bc < 0 || bc > 13)
			bc = 0;
		ent->xv->clientcolors = 16*tc + bc;
	}


	ent->xv->gravity = cl->entgravity = 1.0;
	ent->xv->maxspeed = cl->maxspeed = sv_maxspeed.value;
	ent->v->movetype = MOVETYPE_NOCLIP;

	ent->v->frags = 0;
	cl->connection_started = realtime;
}
/*
==================
Cmd_Join_f

Set client to player mode without reconnecting
==================
*/
void Cmd_Join_f (void)
{
	int		i;
	client_t	*cl;
	int		numclients;
	extern cvar_t	maxclients;

	if (host_client->state != cs_spawned)
		return;
	if (!host_client->spectator)
		return;		// already a player

	if (svs.gametype != GT_PROGS)
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Sorry, not implemented in this gamecode type. Try moaning at the dev team\n");
		return;
	}

	if (!(host_client->zquake_extensions & Z_EXT_JOIN_OBSERVE))
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Your QW client doesn't support this command\n");
		return;
	}

	if (password.string[0] && stricmp(password.string, "none"))
	{
		SV_ClientPrintf(host_client, PRINT_HIGH, "This server requires a %s password. Please disconnect, set the password and reconnect as %s.\n", "player", "player");
		return;
	}

	// count players already on server
	numclients = 0;
	for (i=0,cl=svs.clients ; i<sv.allocated_client_slots ; i++,cl++)
	{
		if (cl->state != cs_free && !cl->spectator)
			numclients++;
	}
	if (numclients >= maxclients.value)
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Can't join, all player slots full\n");
		return;
	}

	// call the prog function for removing a client
	// this will set the body to a dead frame, among other things
	pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
	if (SpectatorDisconnect)
		PR_ExecuteProgram (svprogfuncs, SpectatorDisconnect);
	sv.spawned_observer_slots--;

	SV_SetUpClientEdict (host_client, host_client->edict);

	// turn the spectator into a player
	host_client->spectator = false;
	Info_RemoveKey (host_client->userinfo, "*spectator");
	Info_RemoveKey (host_client->userinfobasic, "*spectator");

	// FIXME, bump the client's userid?

	// call the progs to get default spawn parms for the new client
	if (pr_global_ptrs->SetNewParms)
		PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetNewParms);
	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
	{
		if (pr_global_ptrs->spawnparamglobals[i])
			host_client->spawn_parms[i] = *pr_global_ptrs->spawnparamglobals[i];
		else
			host_client->spawn_parms[i] = 0;
	}

	// call the spawn function
	pr_global_struct->time = sv.world.physicstime;
	pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
	PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientConnect);

	// actually spawn the player
	pr_global_struct->time = sv.world.physicstime;
	pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
	PR_ExecuteProgram (svprogfuncs, pr_global_struct->PutClientInServer);
	sv.spawned_client_slots++;

	// send notification to all clients
	host_client->old_frags = host_client->edict->v->frags;
	host_client->sendinfo = true;

	SV_LogPlayer(host_client, "joined");
}


/*
==================
Cmd_Observe_f

Set client to spectator mode without reconnecting
==================
*/
void Cmd_Observe_f (void)
{
	int		i;
	client_t	*cl;
	int		numspectators;
	extern cvar_t	maxspectators, spectator_password;

	if (host_client->state != cs_spawned)
		return;
	if (host_client->spectator)
		return;		// already a spectator

	if (svs.gametype != GT_PROGS)
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Sorry, not implemented in this gamecode type. Try moaning at the dev team\n");
		return;
	}

	if (!(host_client->zquake_extensions & Z_EXT_JOIN_OBSERVE))
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Your QW client doesn't support this command\n");
		return;
	}

	if (spectator_password.string[0] && stricmp(spectator_password.string, "none"))
	{
		SV_ClientPrintf(host_client, PRINT_HIGH, "This server requires a %s password. Please disconnect, set the password and reconnect as %s.\n", "spectator", "spectator");
		return;
	}

	// count spectators already on server
	numspectators = 0;
	for (i=0,cl=svs.clients ; i<sv.allocated_client_slots ; i++,cl++) {
		if (cl->state != cs_free && cl->spectator)
			numspectators++;
	}
	if (numspectators >= maxspectators.value)
	{
		SV_PrintToClient(host_client, PRINT_HIGH, "Can't join, all spectator slots full\n");
		return;
	}

	// call the prog function for removing a client
	// this will set the body to a dead frame, among other things
	pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
	PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientDisconnect);
	sv.spawned_client_slots--;

	SV_SetUpClientEdict (host_client, host_client->edict);

	// turn the player into a spectator
	host_client->spectator = true;
	Info_SetValueForStarKey (host_client->userinfo, "*spectator", "1", sizeof(host_client->userinfo));
	Info_SetValueForStarKey (host_client->userinfobasic, "*spectator", "1", sizeof(host_client->userinfobasic));

	// FIXME, bump the client's userid?

	// call the progs to get default spawn parms for the new client
	if (pr_global_ptrs->SetNewParms)
		PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetNewParms);
	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
	{
		if (pr_global_ptrs->spawnparamglobals[i])
			host_client->spawn_parms[i] = *pr_global_ptrs->spawnparamglobals[i];
		else
			host_client->spawn_parms[i] = 0;
	}

	SV_SpawnSpectator ();

	// call the spawn function
	if (SpectatorConnect)
	{
		pr_global_struct->time = sv.world.physicstime;
		pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
		PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
	}
	else
		sv_player->v->movetype = MOVETYPE_NOCLIP;
	sv.spawned_observer_slots++;

	// send notification to all clients
	host_client->old_frags = host_client->edict->v->frags;
	host_client->sendinfo = true;

	SV_LogPlayer(host_client, "observing");
}

void Cmd_FPSList_f(void)
{
	client_t *cl;
	int c;
	int f;
	double minf = 1000, maxf = 0, this;
	double ftime;
	int frames;
	int inbytes;
	int outbytes;
	float msecs;


	for (c = 0; c < sv.allocated_client_slots; c++)
	{
		cl = &svs.clients[c];
		ftime = 0;
		frames = 0;
		inbytes = 0;
		outbytes = 0;
		msecs = 0;

		if (!cl->state)
			continue;

		if (ISQWCLIENT(cl) || ISNQCLIENT(cl))
		{
			if (cl->frameunion.frames)
			{
				for (f = 0; f < UPDATE_BACKUP; f++)
				{
					if (cl->frameunion.frames[f].move_msecs >= 0)
					{
						if (!cl->frameunion.frames[f].move_msecs)
						{
							this = 1001;
							msecs+=1;
						}
						else
						{
							this = 1000.0f/cl->frameunion.frames[f].move_msecs;
							msecs += cl->frameunion.frames[f].move_msecs;
						}
						ftime += this;
						if (minf > this)
							minf = this;
						if (maxf < this)
							maxf = this;
						frames++;

						inbytes += cl->frameunion.frames[f].packetsizein;
						outbytes += cl->frameunion.frames[f].packetsizeout;
					}
				}
			}
		}

		if (frames)
			SV_ClientPrintf(host_client, PRINT_HIGH, "%s: %gfps (min%g max %g), c2s: %ibps, s2c: %ibps\n", cl->name, ftime/frames, minf, maxf, (int)cl->inrate, (int)cl->outrate);
		else
			SV_ClientPrintf(host_client, PRINT_HIGH, "%s: unknown framerate, c2s: %ibps, s2c: %ibps\n", cl->name, (int)cl->inrate, (int)cl->outrate);
	}
}

void SV_EnableClientsCSQC(void)
{
#ifdef PEXT_CSQC
//	if ((host_client->fteprotocolextensions & PEXT_CSQC) || atoi(Cmd_Argv(1)))
	{
		host_client->csqcactive = true;
		if (!host_client->csqcentversions)
			host_client->csqcentversions = Z_Malloc(sizeof(*host_client->csqcentversions) * host_client->max_net_ents);
		if (!host_client->csqcentsequence)
			host_client->csqcentsequence = Z_Malloc(sizeof(*host_client->csqcentsequence) * host_client->max_net_ents);
	}
//	else
//		SV_ClientPrintf(host_client, PRINT_HIGH, "CSQC entities not enabled - no support from network protocol\n");
#endif
}
void SV_DisableClientsCSQC(void)
{
#ifdef PEXT_CSQC
	host_client->csqcactive = false;
#endif
}

void SV_UserCmdMVDList_f (void);
void SV_STFU_f(void)
{
	char *msg;
	SV_ClientPrintf(host_client, 255, "stfu\n");
	msg = "cl_antilag 0\n";
	ClientReliableWrite_Begin(host_client, svc_stufftext, 2+strlen(msg));
	ClientReliableWrite_String(host_client, msg);
}

#ifdef NQPROT
void SVNQ_Spawn_f (void)
{
	extern cvar_t sv_gravity;
	int		i;
	client_t	*client;
	edict_t	*ent;

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

// 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=0, client = svs.clients; i<sv.allocated_client_slots ; i++, client++)
		SV_FullClientUpdate(client, host_client);
	SV_MVD_FullClientUpdate(NULL, host_client);

// send all current light styles
	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
	{
		if (i >= MAX_STANDARDLIGHTSTYLES && host_client->protocol != SCP_DARKPLACES7)
			break;	//dp7 clients support more lightstyles.

		ClientReliableWrite_Begin (host_client, svc_lightstyle,
			3 + (sv.strings.lightstyles[i] ? strlen(sv.strings.lightstyles[i]) : 1));
		ClientReliableWrite_Byte (host_client, (char)i);
		ClientReliableWrite_String (host_client, sv.strings.lightstyles[i]);
	}

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

	if (host_client->istobeloaded)	//minimal setup
	{
		host_client->entgravity = ent->xv->gravity*sv_gravity.value;
		host_client->maxspeed = ent->xv->maxspeed;
	}
	else
	{
		ED_Clear(svprogfuncs, ent);
		ED_Spawned(ent, false);

		ent->v->colormap = NUM_FOR_EDICT(svprogfuncs, ent);
		ent->v->team = 0;	// FIXME
		ent->v->netname = PR_SetString(svprogfuncs, host_client->name);

		host_client->entgravity = ent->xv->gravity = 1.0;
		host_client->entgravity*=sv_gravity.value;
		host_client->maxspeed = ent->xv->maxspeed = sv_maxspeed.value;
	}

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

	ClientReliableWrite_Begin (host_client, svcnq_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
	ClientReliableWrite_Long (host_client, pr_global_struct->total_secrets);

	ClientReliableWrite_Begin (host_client, svcnq_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_TOTALMONSTERS);
	ClientReliableWrite_Long (host_client, pr_global_struct->total_monsters);

	ClientReliableWrite_Begin (host_client, svcnq_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_SECRETS);
	ClientReliableWrite_Long (host_client, pr_global_struct->found_secrets);

	ClientReliableWrite_Begin (host_client, svcnq_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_MONSTERS);
	ClientReliableWrite_Long (host_client, pr_global_struct->killed_monsters);

	MSG_WriteByte (&host_client->netchan.message, svc_signonnum);
	MSG_WriteByte (&host_client->netchan.message, 3);


	host_client->send_message = true;

}
void SVNQ_Begin_f (void)
{
	unsigned pmodel = 0, emodel = 0;
	int		i;
	qboolean sendangles=false;

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

	host_client->state = cs_spawned;

	if (host_client->istobeloaded)
	{
		sendangles = true;
		host_client->istobeloaded = false;
	}
	else
	{
		if (host_client->spectator)
		{
			SV_SpawnSpectator ();

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

				// call the spawn function
				pr_global_struct->time = sv.world.physicstime;
				pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
				PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
			}
			sv.spawned_observer_slots++;
		}
		else
		{
			sv.spawned_client_slots++;

			// copy spawn parms out of the client_t
			for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
			{
				if (pr_global_ptrs->spawnparamglobals[i])
					*pr_global_ptrs->spawnparamglobals[i] = host_client->spawn_parms[i];
			}

			// call the spawn function
			pr_global_struct->time = sv.world.physicstime;
			pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
			PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientConnect);

			// actually spawn the player
			pr_global_struct->time = sv.world.physicstime;
			pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
			PR_ExecuteProgram (svprogfuncs, pr_global_struct->PutClientInServer);
		}
	}

	// clear the net statistics, because connecting gives a bogus picture
	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

	if (sv_playermodelchecks.value)
	{
		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, "warning: %s eyes or player model not verified\n", host_client->name);
	}


	// if we are paused, tell the client
	if (sv.paused)
	{
		if (!ISQ2CLIENT(host_client))
		{
			ClientReliableWrite_Begin (host_client, svc_setpause, 2);
			ClientReliableWrite_Byte (host_client, sv.paused!=0);
		}
		if (sv.paused&~4)
			SV_ClientTPrintf(host_client, PRINT_HIGH, STL_SERVERPAUSED!=0);
	}

	if (sendangles)
	{
//
// 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
		MSG_WriteByte (&host_client->netchan.message, svc_setangle);
		MSG_WriteAngle (&host_client->netchan.message, 0 );
		MSG_WriteAngle (&host_client->netchan.message, host_client->edict->v->angles[1] );
		MSG_WriteAngle (&host_client->netchan.message, 0 );
	}

//	MSG_WriteByte (&host_client->netchan.message, svc_signonnum);
//	MSG_WriteByte (&host_client->netchan.message, 4);


	host_client->send_message = true;
}
void SVNQ_PreSpawn_f (void)
{
	if (host_client->prespawn_stage == PRESPAWN_MAPCHECK)
	{
		host_client->checksum = ~0u;
		host_client->prespawn_stage = PRESPAWN_MAPCHECK+1;
		host_client->prespawn_idx = 0;
	}

	host_client->send_message = true;
}
void SVNQ_NQInfo_f (void)
{
	Cmd_TokenizeString(va("setinfo \"%s\" \"%s\"\n", Cmd_Argv(0), Cmd_Argv(1)), false, false);
	SV_SetInfo_f();
}

void SVNQ_NQColour_f (void)
{
	int top;
	int bottom;

	int playercolor;

	if (Cmd_Argc() == 2)
		top = bottom = atoi(Cmd_Argv(1));
	else
	{
		top = atoi(Cmd_Argv(1));
		bottom = atoi(Cmd_Argv(2));
	}

	top &= 15;
	if (top > 13)
		top = 13;
	bottom &= 15;
	if (bottom > 13)
		bottom = 13;

	playercolor = top*16 + bottom;

	if (progstype != PROG_QW)
		host_client->edict->v->team = bottom + 1;

	Info_SetValueForKey(host_client->userinfo, "topcolor", va("%i", top), sizeof(host_client->userinfo));
	Info_SetValueForKey(host_client->userinfo, "bottomcolor", va("%i", bottom), sizeof(host_client->userinfo));
	switch(bottom)
	{
	case 4:
		Info_SetValueForKey(host_client->userinfo, "team", "red", sizeof(host_client->userinfo));
		break;
	case 14:
		Info_SetValueForKey(host_client->userinfo, "team", "blue", sizeof(host_client->userinfo));
		break;
	default:
		Info_SetValueForKey(host_client->userinfo, "team", va("t%i", bottom+1), sizeof(host_client->userinfo));
		break;
	}

	SV_ExtractFromUserinfo (host_client, true);

	MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
	MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
	MSG_WriteString (&sv.reliable_datagram, "topcolor");
	MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "topcolor"));

	MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
	MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
	MSG_WriteString (&sv.reliable_datagram, "bottomcolor");
	MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "bottomcolor"));

	MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
	MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
	MSG_WriteString (&sv.reliable_datagram, "team");
	MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "team"));

	MSG_WriteByte (&sv.nqreliable_datagram, svc_updatecolors);
	MSG_WriteByte (&sv.nqreliable_datagram, host_client - svs.clients);
	MSG_WriteByte (&sv.nqreliable_datagram, playercolor);
}

void SVNQ_Ping_f(void)
{
	int i;
	client_t *cl;

	SV_PrintToClient(host_client, PRINT_HIGH, "Client ping times:\n");
	for (i=0,cl=svs.clients ; i<MAX_CLIENTS ; i++,cl++)
	{
		if (!cl->state)
			continue;

		SV_PrintToClient(host_client, PRINT_HIGH, va("%3i %s\n", SV_CalcPing (cl, false), cl->name));
	}
}

void SV_Pext_f(void)
{
	int i;
	char *tag;
	char *val;

	if (host_client->pextknown)
		return;
	host_client->pextknown = true;

	for (i = 1; i < Cmd_Argc(); )
	{
		tag = Cmd_Argv(i++);
		val = Cmd_Argv(i++);
		switch(strtoul(tag, NULL, 0))
		{
		case PROTOCOL_VERSION_FTE:
			host_client->fteprotocolextensions = strtoul(val, NULL, 0) & Net_PextMask(1, ISNQCLIENT(host_client));
			break;
		case PROTOCOL_VERSION_FTE2:
			host_client->fteprotocolextensions2 = strtoul(val, NULL, 0) & Net_PextMask(2, ISNQCLIENT(host_client));
			break;
		}
	}

	Con_DPrintf("%s now using pext: %x, %x\n", host_client->name, host_client->fteprotocolextensions, host_client->fteprotocolextensions2);

	SV_ClientProtocolExtensionsChanged(host_client);

	if (ISNQCLIENT(host_client))
		SVNQ_New_f();
	else
		SV_New_f();
}

/*
void SVNQ_ExecuteUserCommand (char *s)
{
	client_t *oldhost = host_client;
	ucmd_t	*u;

	Cmd_TokenizeString (s, false, false);
	sv_player = host_client->edict;

	Cmd_ExecLevel=1;

	for (u=nqucmds ; u->name ; u++)
	{
		if (!strcmp (Cmd_Argv(0), u->name) )
		{
			if (/ *!fromQC && * /!u->noqchandling)
				if (PR_UserCmd(s))
				{
					host_client = oldhost;
					return;
				}

			if (!u->func)
			{
				SV_BeginRedirect (RD_CLIENT, host_client->language);
				Con_Printf("Command was disabled\n");
				SV_EndRedirect ();
			}
			else
			{
				SV_BeginRedirect (RD_CLIENT, host_client->language);
				u->func ();
				SV_EndRedirect ();
			}

			host_client = oldhost;
			return;
		}
	}

	if (!u->name)
		Con_Printf("%s tried to \"%s\"\n", host_client->name, s);
}
*/
#endif


void SV_MVDList_f (void);
void SV_MVDInfo_f (void);
typedef struct
{
	char	*name;
	void	(*func) (void);
	qboolean	noqchandling;
} ucmd_t;

ucmd_t ucmds[] =
{
	/*connection process*/
	{"new", SV_New_f, true},
	{"modellist", SVQW_Modellist_f, true},
	{"soundlist", SVQW_Soundlist_f, true},
	{"prespawn", SVQW_PreSpawn_f, true},
	{"spawn", SVQW_Spawn_f, true},
	{"begin", SV_Begin_f, true},

	/*ezquake warning*/
	{"al", SV_STFU_f, true},

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

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

	{"sayone", SV_SayOne_f},
	{"say", SV_Say_f},
	{"say_team", SV_Say_Team_f},

	{"setinfo", SV_SetInfo_f},
	{"serverinfo", SV_ShowServerinfo_f},

	/*demo/download commands*/
	{"stopdownload", SV_StopDownload_f},
	{"demolist", SV_UserCmdMVDList_f},
	{"demoinfo", SV_MVDInfo_f},
	{"dlsize", SV_DownloadSize_f},
	{"download", SV_BeginDownload_f},
	{"nextdl", SV_NextDownload_f},

	/*quakeworld specific things*/
	{"join", Cmd_Join_f},
	{"observe", Cmd_Observe_f},
	{"snap", SV_NoSnap_f},
	{"ptrack", SV_PTrack_f}, //ZOID - used with autocam

	{"enablecsqc", SV_EnableClientsCSQC},
	{"disablecsqc", SV_DisableClientsCSQC},

	{"vote", SV_Vote_f},

#ifdef SVRANKING
	{"topten", Rank_ListTop10_f},
#endif

	{"efpslist", Cmd_FPSList_f},	//don't conflict with the ktpro one
	{"god", Cmd_God_f},
	{"give", Cmd_Give_f},
	{"noclip", Cmd_Noclip_f},
	{"fly", Cmd_Fly_f},
	{"notarget", Cmd_Notarget_f},
	{"setpos", Cmd_SetPos_f},

#ifdef NQPROT
	{"name",		SVNQ_NQInfo_f},
#endif

#ifdef VOICECHAT
	{"voicetarg", SV_Voice_Target_f},
	{"vignore", SV_Voice_Ignore_f},	/*ignore/mute specific player*/
	{"muteall", SV_Voice_MuteAll_f},	/*disables*/
	{"unmuteall", SV_Voice_UnmuteAll_f}, /*reenables*/
#endif

	{NULL, NULL}
};

#ifdef Q2SERVER
ucmd_t ucmdsq2[] = {
	{"new", SV_New_f, true},
	{"configstrings", SVQ2_ConfigStrings_f, true},
	{"baselines", SVQ2_BaseLines_f, true},
	{"begin", SV_Begin_f, true},

//	{"setinfo", SV_SetInfo_f, true},

	{"serverinfo", SV_ShowServerinfo_f, true},
	{"info", SV_ShowServerinfo_f, true},

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

	{"nextserver", SVQ2_NextServer_f, true},

	{"vote", SV_Vote_f, true},

//#ifdef SVRANKING
//	{"topten", Rank_ListTop10_f, true},
//#endif

	{"drop", SV_Drop_f, true},
	{"disconnect", SV_Drop_f, true},

	{NULL, NULL}
};
#endif

#ifdef NQPROT
ucmd_t nqucmds[] =
{
	{"new",			SVNQ_New_f, true},
	{"spawn",		SVNQ_Spawn_f, true},
	{"begin",		SVNQ_Begin_f, true},
	{"prespawn",	SVNQ_PreSpawn_f, true},

	{"status",		NULL},


	{"god",			Cmd_God_f},
	{"give",		Cmd_Give_f},
	{"notarget",	Cmd_Notarget_f},
	{"fly",			Cmd_Fly_f},
	{"noclip",		Cmd_Noclip_f},

	{"say",			SV_Say_f},
	{"say_team",	SV_Say_Team_f},
	{"tell",		SV_SayOne_f},
	{"efpslist",	Cmd_FPSList_f},	//don't conflict with the ktpro one

	{"pings",		SV_Pings_f},
	{"ping",		SVNQ_Ping_f},

	{"kill",		SV_Kill_f},
	{"pause",		SV_Pause_f},
	{"kick",		NULL},
	{"ban",			NULL},
	{"vote",		SV_Vote_f},

	/*DP download protocol*/
	{"dlsize",		SV_DownloadSize_f},
	{"download",	SV_BeginDownload_f},
	{"sv_startdownload",	SVDP_StartDownload_f},

	/*userinfo stuff*/
	{"setinfo",		SV_SetInfo_f},
	{"name",		SVNQ_NQInfo_f},
	{"color",		SVNQ_NQColour_f},
	{"playermodel",	NULL},
	{"playerskin",	NULL},
	{"rate",		SV_Rate_f},

#ifdef SVRANKING
	{"topten",		Rank_ListTop10_f},
#endif

	/*various misc extensions*/
	{"pext",		SV_Pext_f},
	{"enablecsqc",	SV_EnableClientsCSQC},
	{"disablecsqc",	SV_DisableClientsCSQC},
	{"challengeconnect", NULL},

#ifdef VOICECHAT
	{"voicetarg",	SV_Voice_Target_f},
	{"vignore",		SV_Voice_Ignore_f},	/*ignore/mute specific player*/
	{"muteall",		SV_Voice_MuteAll_f},	/*disables*/
	{"unmuteall",	SV_Voice_UnmuteAll_f}, /*reenables*/
#endif

	{NULL, NULL}
};
#endif

/*
==================
SV_ExecuteUserCommand
==================
*/
void SV_ExecuteUserCommand (char *s, qboolean fromQC)
{
	ucmd_t	*u;
	client_t *oldhost = host_client;
	char adr[MAX_ADR_SIZE];

	Con_DPrintf("Client command: %s\n", s);

	Cmd_TokenizeString (s, false, false);
	sv_player = host_client->edict;

	Cmd_ExecLevel=1;

	if (host_client->controlled && atoi(Cmd_Argv(0))>0)	//now see if it's meant to be from a slave client
	{
		int pnum = atoi(Cmd_Argv(0));
		client_t *s;
		for (s = host_client; s; s = s->controlled)
		{
			if (!--pnum)
			{
				host_client = s;
				break;
			}
		}
		sv_player = host_client->edict;
		Cmd_ShiftArgs(1, false);
	}

#ifdef Q2SERVER
	if (ISQ2CLIENT(host_client))
		u = ucmdsq2;
	else
#endif
#ifdef NQPROT
	if (ISNQCLIENT(host_client))
		u = nqucmds;
	else
#endif
		u=ucmds;

	for ( ; u->name ; u++)
		if (!strcmp (Cmd_Argv(0), u->name) )
		{
			if (!fromQC && !u->noqchandling)
				if (PR_KrimzonParseCommand(s))	//KRIMZON_SV_PARSECLIENTCOMMAND has the opertunity to parse out certain commands.
				{
					host_client = oldhost;
					return;
				}
//			SV_BeginRedirect (RD_CLIENT, host_client->language);
			if (u->func)
				u->func ();
			host_client = oldhost;
//			SV_EndRedirect ();
			return;
		}

	if (!u->name)
	{
#ifdef HLSERVER
		if (HLSV_ClientCommand(host_client))
		{
			host_client = oldhost;
			return;
		}
#endif

		if (!fromQC)
			if (PR_UserCmd(s))			//Q2 and MVDSV command handling only happens if the engine didn't recognise it.
			{
				host_client = oldhost;
				return;
			}
#ifdef SVRANKING
		if (sv_cmdlikercon.value && host_client->rankid)
		{
			char remaining[1024];
			int i;
			rankstats_t stats;

			if (!Rank_GetPlayerStats(host_client->rankid, &stats))
			{
				host_client = oldhost;
				return;
			}

			Con_Printf ("cmd from %s:\n%s\n"
				, host_client->name, net_message.data+4);

			SV_BeginRedirect (RD_CLIENT, host_client->language);

			remaining[0] = 0;

			for (i=0 ; i<Cmd_Argc() ; i++)
			{
				if (strlen(remaining)+strlen(Cmd_Argv(i))>=sizeof(remaining)-1)
				{
					Con_Printf("cmd was too long\n");
					host_client = oldhost;
					SV_EndRedirect ();
					Con_Printf ("cmd from %s:\n%s\n"
						, NET_AdrToString (adr, sizeof(adr), net_from), "Was too long - possible buffer overflow attempt");
					return;
				}
				strcat (remaining, Cmd_Argv(i) );
				strcat (remaining, " ");
			}

			Cmd_ExecuteString (remaining, stats.trustlevel);
			host_client = oldhost;
			SV_EndRedirect ();
			return;
		}
#endif
		Con_Printf ("Bad user command: %s\n", Cmd_Argv(0));
	}

	host_client = oldhost;
	SV_EndRedirect ();
}

int implevels[256];
qboolean SV_FilterImpulse(int imp, int level)
{
	if (imp < 0 || imp > 255)
		return true;	//erm

	if (implevels[imp] > level)
		return false;

	return true;
}
void SV_FilterImpulseInit(void)
{
	char buffer[1024];
	char *s;
	int lev;
	int imp;
	memset(implevels, 0, sizeof(implevels));

	s = COM_LoadStackFile("impfiltr.cfg", buffer, sizeof(buffer));
	if (!s)
		Con_DPrintf("impfiltr.cfg not found. Impulse filters are disabled\n");

	while(s)
	{
		s = COM_Parse(s);
		if (!s)
			return;
		imp = atoi(com_token);
		s = COM_Parse(s);
		if (!s)
		{
			Con_Printf("Unexpected eof in impfiltr.cfg\n");
			return;
		}
		lev = atoi(com_token);
		if (imp > 255 || imp < 0 || lev < 0 || lev > RESTRICT_MAX)
			Con_Printf("impfiltr.cfg - bad paramters\n");
		else
			implevels[imp] = lev;
	}
}
/*
===========================================================================

USER CMD EXECUTION

===========================================================================
*/
#ifdef SERVERONLY
/*
===============
V_CalcRoll

Used by view and sv_user
===============
*/
float V_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;

}
#endif



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

vec3_t	pmove_mins, pmove_maxs;

/*
====================
AddLinksToPmove

====================
*/
void AddLinksToPmove ( edict_t *player, areanode_t *node )
{
	int Q1_HullPointContents (hull_t *hull, int num, vec3_t p);
	link_t		*l, *next;
	edict_t		*check;
	int			pl;
	int			i;
	physent_t	*pe;

	model_t *model;

	pl = EDICT_TO_PROG(svprogfuncs, player);

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

		if (check->v->owner == pl)
			continue;		// player's own missile
		if (check == player)
			continue;
		if ((check->v->solid == SOLID_TRIGGER && check->v->skin < 0) || check->v->solid == SOLID_BSP
			|| check->v->solid == SOLID_BBOX
			|| check->v->solid == SOLID_SLIDEBOX
			//|| (check->v->solid == SOLID_PHASEH2 && progstype == PROG_H2) //logically matches hexen2, but I hate it
			)
		{

			for (i=0 ; i<3 ; i++)
				if (check->v->absmin[i] > pmove_maxs[i]
				|| check->v->absmax[i] < pmove_mins[i])
					break;
			if (i != 3)
				continue;

			if (pmove.numphysent == MAX_PHYSENTS)
				break;
			pe = &pmove.physents[pmove.numphysent];
			pe->notouch = !((int)player->xv->dimension_solid & (int)check->xv->dimension_hit);
			if (!((int)player->xv->dimension_hit & (int)check->xv->dimension_solid))
				continue;
			if (!check->v->size[0])	//points are not meant to be solid
				continue;
			pmove.numphysent++;

			VectorCopy (check->v->origin, pe->origin);
			pe->info = NUM_FOR_EDICT(svprogfuncs, check);
			pe->nonsolid = check->v->solid == SOLID_TRIGGER;
			switch((int)check->v->skin)
			{
			case Q1CONTENTS_WATER:
				pe->forcecontentsmask = FTECONTENTS_WATER;
				break;
			case Q1CONTENTS_LAVA:
				pe->forcecontentsmask = FTECONTENTS_LAVA;
				break;
			case Q1CONTENTS_SLIME:
				pe->forcecontentsmask = FTECONTENTS_SLIME;
				break;
			case Q1CONTENTS_SKY:
				pe->forcecontentsmask = FTECONTENTS_SKY;
				break;
			case -16:
				pe->forcecontentsmask = FTECONTENTS_LADDER;
				break;
			default:
				pe->forcecontentsmask = 0;
				break;
			}
			if (check->v->solid == SOLID_BSP)
			{
				if(progstype != PROG_H2)
					pe->angles[0]*=-1;	//quake is wierd. I guess someone fixed it hexen2... or my code is buggy or something...
				pe->model = sv.models[(int)(check->v->modelindex)];
				VectorCopy (check->v->angles, pe->angles);
			}
			else
			{
				pe->model = NULL;
				VectorCopy (check->v->mins, pe->mins);
				VectorCopy (check->v->maxs, pe->maxs);
				VectorClear (pe->angles);
			}
		}
	}
	if (player->v->mins[2] != 24)	//crouching/dead
	for (l = node->edicts.next ; l != &node->edicts ; l = next)
	{
		next = l->next;
		check = (edict_t*)EDICT_FROM_AREA(l);

		if (check->v->owner == pl)
			continue;		// player's own missile
		if (check->v->solid == SOLID_LADDER)
		{
			for (i=0 ; i<3 ; i++)
				if (check->v->absmin[i] > pmove_maxs[i]
				|| check->v->absmax[i] < pmove_mins[i])
					break;
			if (i != 3)
				continue;

			if (!((int)player->xv->dimension_hit & (int)check->xv->dimension_solid))
				continue;

			model = sv.models[(int)check->v->modelindex];
			if (model)
			{
				vec3_t axis[3];
				AngleVectors(check->v->angles, axis[0], axis[1], axis[2]);
				VectorNegate(axis[1], axis[1]);
	// test the point
				if (model->funcs.PointContents (model, axis, player->v->origin) == FTECONTENTS_SOLID)
					player->xv->pmove_flags = (int)player->xv->pmove_flags | PMF_LADDER;	//touch that ladder!
			}
		}
	}

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

	if (pmove_maxs[node->axis] > node->dist)
		AddLinksToPmove (player, node->children[0]);
	if (pmove_mins[node->axis] < node->dist)
		AddLinksToPmove (player, 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(svprogfuncs, sv_player);
	for (e=1 ; e<sv.world.num_edicts ; e++)
	{
		check = EDICT_NUM(svprogfuncs, e);
		if (check->isfree)
			continue;
		if (check->v->owner == pl)
			continue;
		if (check->v->solid == SOLID_BSP
			|| check->v->solid == SOLID_BBOX
			|| check->v->solid == SOLID_SLIDEBOX)
		{
			if (check == sv_player)
				continue;

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

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

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

int SV_PMTypeForClient (client_t *cl)
{
#ifdef SERVER_DEMO_PLAYBACK
	if (sv.demostatevalid)
	{	//force noclip... This does create problems for closing demos.
		if (cl->zquake_extensions & Z_EXT_PM_TYPE_NEW)
			return PM_SPECTATOR;
		return PM_OLD_SPECTATOR;
	}
#endif

	if (sv_brokenmovetypes.value)	//this is to mimic standard qw servers, which don't support movetypes other than MOVETYPE_FLY.
	{								//it prevents bugs from being visible in unsuspecting mods.
		if (cl->spectator)
		{
			if (cl->zquake_extensions & Z_EXT_PM_TYPE_NEW)
				return PM_SPECTATOR;
			return PM_OLD_SPECTATOR;
		}

		if (cl->edict->v->health <= 0)
			return PM_DEAD;
		return PM_NORMAL;
	}

	switch((int)cl->edict->v->movetype)
	{
	case MOVETYPE_NOCLIP:
		/*older/vanilla clients have a b0rked spectator mode that we don't want to break*/
		if (cl->zquake_extensions & Z_EXT_PM_TYPE_NEW)
			return PM_SPECTATOR;
		return PM_OLD_SPECTATOR;

	case MOVETYPE_WALLWALK:
		return PM_WALLWALK;

	case MOVETYPE_FLY:
		return PM_FLY;

	case MOVETYPE_NONE:
		return PM_NONE;

	case MOVETYPE_WALK:
	default:
		if (cl->edict->v->health <= 0)
			return PM_DEAD;

		return PM_NORMAL;
	}
}


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

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

/*
===========
SV_RunCmd
===========
*/
void SV_RunCmd (usercmd_t *ucmd, qboolean recurse)
{
	edict_t		*ent;
	int			i, n;
	int			oldmsec;
	double  tmp_time;
	qboolean jumpable;
	char adr[MAX_ADR_SIZE];
	vec3_t new_vel;
	vec3_t old_vel;

	// DMW copied this KK hack copied from QuakeForge anti-cheat
	// (also extra inside parm on all SV_RunCmds that follow)

	// To prevent a infinite loop
	if (!recurse)
	{
		if (!host_client->last_check)
		{
			host_client->msecs = 0;
		    host_client->last_check = realtime;
		}
		host_client->msecs += ucmd->msec;

		if ((tmp_time = realtime - host_client->last_check) >= sv_cheatspeedchecktime.value)
		{
			tmp_time = tmp_time * 1000.0 * sv_cheatpc.value/100.0;
		    if (host_client->msecs > tmp_time)
			{
				host_client->msec_cheating++;
				SV_BroadcastTPrintf(PRINT_HIGH,
						STL_SPEEDCHEATPOSSIBLE,
							host_client->msecs, tmp_time,
							host_client->msec_cheating, host_client->name);

				if (host_client->msec_cheating >= 2)
				{
					SV_BroadcastTPrintf(PRINT_HIGH,
							STL_SPEEDCHEATKICKED,
								host_client->name, NET_AdrToString(adr, sizeof(adr), host_client->netchan.remote_address));
					host_client->drop = true;	//drop later
				}
		    }

		    host_client->msecs = 0;
		    host_client->last_check = realtime;
		}
	}
	// end KK hack copied from QuakeForge anti-cheat
	//it's amazing how code get's copied around...

	if (SV_RunFullQCMovement(host_client, ucmd))
	{
		return;
	}


	cmd = *ucmd;

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

	host_frametime = ucmd->msec * 0.001;
	host_frametime *= sv.gamespeed;
	if (host_frametime > 0.1)
		host_frametime = 0.1;

#ifdef SERVER_DEMO_PLAYBACK
	if (sv.demostatevalid)
	{	//spectators watching MVDs do not affect the running progs.
		player_mins[0] = -16;
		player_mins[1] = -16;
		player_mins[2] = -24;

		player_maxs[0] = 16;
		player_maxs[1] = 16;
		player_maxs[2] = 32;

		pmove.angles[0] = SHORT2ANGLE(ucmd->angles[0]);
		pmove.angles[1] = SHORT2ANGLE(ucmd->angles[1]);
		pmove.angles[2] = SHORT2ANGLE(ucmd->angles[2]);

		VectorCopy (host_client->specorigin, pmove.origin);
		VectorCopy (host_client->specvelocity, pmove.velocity);

		if (host_client->zquake_extensions & Z_EXT_PM_TYPE_NEW)
			pmove.pm_type = PM_SPECTATOR;
		else
			pmove.pm_type = PM_OLD_SPECTATOR;
		pmove.jump_held = host_client->jump_held;
		pmove.jump_msec = 0;
		pmove.waterjumptime = 0;
		pmove.numphysent = 1;
		pmove.physents[0].model = sv.world.worldmodel;
		pmove.cmd = *ucmd;
		pmove.hullnum = SV_HullNumForPlayer(0, player_mins, player_maxs);

		movevars.entgravity = 0;
		movevars.maxspeed = 0;
		movevars.bunnyspeedcap = pm_bunnyspeedcap.value;
		movevars.ktjump = pm_ktjump.value;
		movevars.slidefix = (pm_slidefix.value != 0);
		movevars.airstep = (pm_airstep.value != 0);
		movevars.walljump = (pm_walljump.value);
		movevars.slidyslopes = (pm_slidyslopes.value!=0);

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

		PM_PlayerMove (sv.gamespeed);

		VectorCopy (pmove.origin, host_client->specorigin);
		VectorCopy (pmove.velocity, host_client->specvelocity);

		return;
	}
#endif

#ifdef SVCHAT
	if (SV_ChatMove(ucmd->impulse))
	{
		ucmd->buttons = 0;
		ucmd->impulse = 0;
		ucmd->forwardmove = ucmd->sidemove = ucmd->upmove = 0;
	}
#endif

	if (!sv_player->v->fixangle)
	{
		sv_player->v->v_angle[0] = SHORT2ANGLE(ucmd->angles[0]);
		sv_player->v->v_angle[1] = SHORT2ANGLE(ucmd->angles[1]);
		sv_player->v->v_angle[2] = SHORT2ANGLE(ucmd->angles[2]);
	}

	if (progstype == PROG_H2)
		sv_player->xv->light_level = 128;	//hmm... HACK!!!

	sv_player->v->button0 = ucmd->buttons & 1;
	sv_player->v->button2 = (ucmd->buttons >> 1) & 1;
	if (pr_allowbutton1.ival)	//many mods use button1 - it's just a wasted field to many mods. So only work it if the cvar allows.
		sv_player->v->button1 = ((ucmd->buttons >> 2) & 1);
// DP_INPUTBUTTONS
	sv_player->xv->button3 = ((ucmd->buttons >> 2) & 1);
	sv_player->xv->button4 = ((ucmd->buttons >> 3) & 1);
	sv_player->xv->button5 = ((ucmd->buttons >> 4) & 1);
	sv_player->xv->button6 = ((ucmd->buttons >> 5) & 1);
	sv_player->xv->button7 = ((ucmd->buttons >> 6) & 1);
	sv_player->xv->button8 = ((ucmd->buttons >> 7) & 1);
	if (ucmd->impulse && SV_FilterImpulse(ucmd->impulse, host_client->trustlevel))
		sv_player->v->impulse = ucmd->impulse;

	if (host_client->iscuffed)
	{
		sv_player->v->impulse = 0;
		sv_player->v->button0 = 0;
	}

	if (host_client->state && host_client->protocol != SCP_BAD)
	{
		sv_player->xv->movement[0] = ucmd->forwardmove * host_frametime;
		sv_player->xv->movement[1] = ucmd->sidemove * host_frametime;
		sv_player->xv->movement[2] = ucmd->upmove * host_frametime;
	}

	WPhys_CheckVelocity(&sv.world, (wedict_t*)sv_player);

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

	if (SV_PlayerPhysicsQC && !host_client->spectator)
	{	//player movement tweaks that fuck over player prediction.
		pr_global_struct->frametime = host_frametime;
		pr_global_struct->time = sv.time;
		WPhys_RunEntity(&sv.world, (wedict_t*)sv_player);
		return;
	}


	if (!host_client->spectator)
	{
		vec_t oldvz;
		pr_global_struct->frametime = host_frametime;

		pr_global_struct->time = sv.time;
		pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);

		oldvz = sv_player->v->velocity[2];
		if (progstype != PROG_QW)
		{
			jumpable = ((int)sv_player->v->flags & FL_JUMPRELEASED) && ((int)sv_player->v->flags & FL_ONGROUND);

			pmove.waterjumptime = sv_player->v->teleport_time;
			if (pmove.waterjumptime > sv.time)
				sv_player->v->flags = (int)sv_player->v->flags | FL_WATERJUMP;
		}
		else
			jumpable = false;

#ifdef VM_Q1
		if (svs.gametype == GT_Q1QVM)
			Q1QVM_PlayerPreThink();
		else
#endif
			if (pr_global_ptrs->PlayerPreThink)
				PR_ExecuteProgram (svprogfuncs, *pr_global_ptrs->PlayerPreThink);

		if (progstype != PROG_QW)
		{
			if (sv_player->v->velocity[2] == 225 && sv_player->v->teleport_time > sv.time)
			{
				sv_player->v->velocity[2] = oldvz;
		//		Con_Printf("Waterjump detected\n");
			}
			sv_player->v->flags = (int)sv_player->v->flags & ~FL_WATERJUMP;
			sv_player->v->teleport_time = pmove.waterjumptime;
			if (jumpable && !((int)sv_player->v->flags & FL_JUMPRELEASED))	//hmm... a jump was hit.
				sv_player->v->velocity[2] -= 270;
		}

		WPhys_RunThink (&sv.world, (wedict_t*)sv_player);

		if (host_client->state && host_client->protocol == SCP_BAD)
		{
			//botclients update their movement during prethink. make sure we use that stuff.
			ucmd->angles[0] = (int)(sv_player->v->v_angle[0] * (65535/360.0f));
			ucmd->angles[1] = (int)(sv_player->v->v_angle[1] * (65535/360.0f));
			ucmd->angles[2] = (int)(sv_player->v->v_angle[2] * (65535/360.0f));

			if (host_frametime)
			{
				ucmd->forwardmove = sv_player->xv->movement[0] / host_frametime;
				ucmd->sidemove = sv_player->xv->movement[1] / host_frametime;
				ucmd->upmove = sv_player->xv->movement[2] / host_frametime;
			}
			else
			{
				ucmd->forwardmove = 0;
				ucmd->sidemove = 0;
				ucmd->upmove = 0;
			}
		}
	}

//	memset(&pmove, 0, sizeof(pmove));
//	memset(&movevars, 0, sizeof(movevars));

	player_mins[0] = sv_player->v->mins[0];
	player_mins[1] = sv_player->v->mins[1];
	player_mins[2] = sv_player->v->mins[2];

	player_maxs[0] = sv_player->v->maxs[0];
	player_maxs[1] = sv_player->v->maxs[1];
	player_maxs[2] = sv_player->v->maxs[2];

	VectorCopy(sv_player->xv->gravitydir, pmove.gravitydir);

	for (i=0 ; i<3 ; i++)
		pmove.origin[i] = sv_player->v->origin[i];// + (sv_player->v->mins[i] - player_mins[i]);

	VectorCopy (sv_player->v->velocity, pmove.velocity);
	VectorCopy (sv_player->v->v_angle, pmove.angles);

	pmove.pm_type = SV_PMTypeForClient (host_client);
	pmove.jump_held = host_client->jump_held;
	pmove.jump_msec = 0;
	if (progstype != PROG_QW)	//this is just annoying.
		pmove.waterjumptime = sv_player->v->teleport_time - sv.time;
	else
		pmove.waterjumptime = sv_player->v->teleport_time;
	pmove.numphysent = 1;
	pmove.physents[0].model = sv.world.worldmodel;
	pmove.cmd = *ucmd;
	pmove.skipent = -1;

	movevars.entgravity = host_client->entgravity/movevars.gravity;
	movevars.maxspeed = host_client->maxspeed;
	movevars.bunnyspeedcap = pm_bunnyspeedcap.value;
	movevars.ktjump = pm_ktjump.value;
	movevars.slidefix = (pm_slidefix.value != 0);
	movevars.airstep = (pm_airstep.value != 0);
	movevars.walljump = (pm_walljump.value);
	movevars.slidyslopes = (pm_slidyslopes.value!=0);

	if (sv_player->xv->hasted)
		movevars.maxspeed*=sv_player->xv->hasted;

	for (i=0 ; i<3 ; i++)
	{
		pmove_mins[i] = pmove.origin[i] - 256;
		pmove_maxs[i] = pmove.origin[i] + 256;
	}
	sv_player->xv->pmove_flags = (int)sv_player->xv->pmove_flags & ~PMF_LADDER;	//assume not touching ladder trigger
#if 1
	AddLinksToPmove ( sv_player, sv.world.areanodes );
#else
	AddAllEntsToPmove ();
#endif

	if ((int)sv_player->xv->pmove_flags & PMF_LADDER)
		pmove.onladder = true;
	else
		pmove.onladder = false;

#if 0
{
	int before, after;

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

if (sv_player->v->health > 0 && before && !after )
	Con_Printf ("player %s got stuck in playermove!!!!\n", host_client->name);
}
#else
	PM_PlayerMove (sv.gamespeed);
#endif

	host_client->jump_held = pmove.jump_held;
	if (progstype != PROG_QW)	//this is just annoying.
	{
		if (pmove.waterjumptime)
			sv_player->v->teleport_time = pmove.waterjumptime + sv.time;
	}
	else
		sv_player->v->teleport_time = pmove.waterjumptime;
	sv_player->v->waterlevel = pmove.waterlevel;

	if (pmove.watertype & FTECONTENTS_SOLID)
		sv_player->v->watertype = Q1CONTENTS_SOLID;
	else if (pmove.watertype & FTECONTENTS_SKY)
		sv_player->v->watertype = Q1CONTENTS_SKY;
	else if (pmove.watertype & FTECONTENTS_LAVA)
		sv_player->v->watertype = Q1CONTENTS_LAVA;
	else if (pmove.watertype & FTECONTENTS_SLIME)
		sv_player->v->watertype = Q1CONTENTS_SLIME;
	else if (pmove.watertype & FTECONTENTS_WATER)
		sv_player->v->watertype = Q1CONTENTS_WATER;
	else
		sv_player->v->watertype = Q1CONTENTS_EMPTY;

	if (pmove.onground)
	{
		sv_player->v->flags = (int)sv_player->v->flags | FL_ONGROUND;
		sv_player->v->groundentity = EDICT_TO_PROG(svprogfuncs, EDICT_NUM(svprogfuncs, pmove.physents[pmove.groundent].info));
	}
	else
		sv_player->v->flags = (int)sv_player->v->flags & ~FL_ONGROUND;

	VectorCopy (pmove.origin, sv_player->v->origin);
	VectorCopy (pmove.angles, sv_player->v->v_angle);

	if (pmove.gravitydir[0] || pmove.gravitydir[1] || pmove.gravitydir[2] != -1)
	{
		if (!sv_player->v->fixangle)
		{
			//FIXME: bound to pmove.gravitydir
			vec3_t view[3];
			vec3_t surf[3];
			vec3_t fwd, up;
			AngleVectors(sv_player->v->v_angle, view[0], view[1], view[2]);
			/*calculate the surface axis with up from the pmove code and right/forwards relative to the player's directions*/
			VectorNegate(pmove.gravitydir, surf[2]);
			CrossProduct(view[0], surf[2], surf[1]);
			VectorNormalize(surf[1]);
			CrossProduct(surf[2], surf[1], surf[0]);
			/*interpolate the forward direction to be 1/3rd the player, and 2/3rds the surface forward*/
			VectorInterpolate(surf[0], 0.333, view[0], fwd);
			CrossProduct(surf[1], fwd, up);
			/*we have our player's new axis*/
			VectorAngles(fwd, up, sv_player->v->angles);
		}
	}

	player_mins[0] = -16;
	player_mins[1] = -16;
	player_mins[2] = -24;

	player_maxs[0] = 16;
	player_maxs[1] = 16;
	player_maxs[2] = 32;

	VectorCopy(sv_player->v->velocity, old_vel);
	VectorCopy(pmove.velocity, new_vel);
	if (progstype == PROG_QW)
		VectorCopy(new_vel, sv_player->v->velocity);


	if (!host_client->spectator)
	{
		// link into place and touch triggers
		World_LinkEdict (&sv.world, (wedict_t*)sv_player, true);

/*		for (i = 0; i < pmove.numphysent; i++)
		{

		}
*/
		// touch other objects
		for (i=0 ; i<pmove.numtouch ; i++)
		{
			if (sv_pushplayers.value)
			{
				n = pmove.physents[pmove.touchindex[i]].info;
				if (n && n <= sv.allocated_client_slots)
				{
					float vel;
					vec3_t dir;
					vec3_t svel;
					ent = EDICT_NUM(svprogfuncs, n);
					VectorSubtract(ent->v->origin, sv_player->v->origin, dir);
					VectorNormalize(dir);
					VectorCopy(sv_player->v->velocity, svel);
					VectorNormalize(svel);
					vel = DotProduct(svel, dir);
					VectorMA(ent->v->velocity, sv_pushplayers.value*vel, dir, ent->v->velocity);
				}
			}


			if (pmove.physents[pmove.touchindex[i]].notouch)
				continue;
			n = pmove.physents[pmove.touchindex[i]].info;
			ent = EDICT_NUM(svprogfuncs, n);
			if (playertouch[n/8]&(1<<(n%8)))
				continue;

			if (ent->v->touch)
			{
				if (progstype != PROG_QW && VectorCompare(sv_player->v->velocity, old_vel))
				{
					VectorCopy(pmove.touchvel[i], old_vel);
					VectorCopy(pmove.touchvel[i], sv_player->v->velocity);
				}
				sv.world.Event_Touch(&sv.world, (wedict_t*)ent, (wedict_t*)sv_player);
			}
			playertouch[n/8] |= 1 << (n%8);

			if (sv_player->v->touch && !ent->isfree)
				sv.world.Event_Touch(&sv.world, (wedict_t*)sv_player, (wedict_t*)ent);
		}
	}

	if (progstype != PROG_QW)
	{
		if (VectorCompare(sv_player->v->velocity, old_vel))
			VectorCopy(new_vel, sv_player->v->velocity);
	}
}

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

	if (!svprogfuncs)
		return;

#ifdef VM_Q1
	if (svs.gametype == GT_Q1QVM)
	{
		pr_global_struct->time = sv.time;
		pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
		Q1QVM_PostThink();
	}
	else
#endif
	{
		if (!host_client->spectator)
		{
			pr_global_struct->time = sv.time;
			pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);

			if (pr_global_ptrs->PlayerPostThink)
				PR_ExecuteProgram (svprogfuncs, *pr_global_ptrs->PlayerPostThink);

			WPhys_RunNewmis (&sv.world);
		}
		else if (SpectatorThink)
		{
			pr_global_struct->time = sv.time;
			pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
			PR_ExecuteProgram (svprogfuncs, SpectatorThink);
		}
	}
}

void SV_ReadPrydonCursor(void)
{
	float f;
	int entnum;
	eval_t *cursor_screen, *cursor_start, *cursor_impact, *cursor_entitynumber;

	if (svprogfuncs)
	{
		cursor_screen	= svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_screen", NULL);
		cursor_start	= svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_trace_start", NULL);
		cursor_impact	= svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_trace_endpos", NULL);
		cursor_entitynumber	= svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_trace_ent", NULL);
	}
	else
	{
		cursor_screen	= NULL;
		cursor_start	= NULL;
		cursor_impact	= NULL;
		cursor_entitynumber	= NULL;
	}

	f = MSG_ReadShort() * (1.0f / 32767.0f);
	if (cursor_screen) cursor_screen->_vector[0] = f;
	f = MSG_ReadShort() * (1.0f / 32767.0f);
	if (cursor_screen) cursor_screen->_vector[1] = f;

	f = MSG_ReadFloat();
	if (cursor_start) cursor_start->_vector[0] = f;
	f = MSG_ReadFloat();
	if (cursor_start) cursor_start->_vector[1] = f;
	f = MSG_ReadFloat();
	if (cursor_start) cursor_start->_vector[2] = f;

	f = MSG_ReadFloat();
	if (cursor_impact) cursor_impact->_vector[0] = f;
	f = MSG_ReadFloat();
	if (cursor_impact) cursor_impact->_vector[1] = f;
	f = MSG_ReadFloat();
	if (cursor_impact) cursor_impact->_vector[2] = f;

	entnum = MSGSV_ReadEntity(host_client);
	if (entnum >= sv.world.max_edicts)
	{
		Con_DPrintf("SV_ReadPrydonCursor: client send bad cursor_entitynumber\n");
		entnum = 0;
	}
	// as requested by FrikaC, cursor_trace_ent is reset to world if the
	// entity is free at time of receipt
	if (!svprogfuncs || EDICT_NUM(svprogfuncs, entnum)->isfree)
		entnum = 0;
	if (msg_badread) Con_Printf("SV_ReadPrydonCursor: badread at %s:%i\n", __FILE__, __LINE__);

	if (cursor_entitynumber) cursor_entitynumber->edict = entnum;
}

void SV_ReadQCRequest(void)
{
	int e;
	char args[8];
	char *rname;
	func_t f;
	int i;
	globalvars_t *pr_globals;

	if (!svprogfuncs)
	{
		msg_badread = true;
		return;
	}

	pr_globals = PR_globals(svprogfuncs, PR_CURRENT);

	for (i = 0; ; i++)
	{
		if (i >= sizeof(args)-1)
		{
			if (MSG_ReadByte() != ev_void)
			{
				msg_badread = true;
				return;
			}
			goto done;
		}
		switch(MSG_ReadByte())
		{
		case ev_void:
			goto done;
		case ev_float:
			args[i] = 'f';
			G_FLOAT(OFS_PARM0+i*3) = MSG_ReadFloat();
			break;
		case ev_vector:
			args[i] = 'v';
			G_FLOAT(OFS_PARM0+i*3+0) = MSG_ReadFloat();
			G_FLOAT(OFS_PARM0+i*3+1) = MSG_ReadFloat();
			G_FLOAT(OFS_PARM0+i*3+2) = MSG_ReadFloat();
			break;
		case ev_integer:
			args[i] = 'i';
			G_INT(OFS_PARM0+i*3) = MSG_ReadLong();
			break;
		case ev_string:
			args[i] = 's';
			G_INT(OFS_PARM0+i*3) = PR_TempString(svprogfuncs, MSG_ReadString());
			break;
		case ev_entity:
			args[i] = 'e';
			e = MSGSV_ReadEntity(host_client);
			if (e < 0 || e >= sv.world.num_edicts)
				e = 0;
			G_INT(OFS_PARM0+i*3) = EDICT_TO_PROG(svprogfuncs, EDICT_NUM(svprogfuncs, e));
			break;
		}
	}

done:
	args[i] = 0;
	rname = MSG_ReadString();
	if (i)
		rname = va("Cmd_%s_%s", rname, args);
	else
		rname = va("Cmd_%s", rname);
	f = PR_FindFunction(svprogfuncs, rname, PR_ANY);
	if (f)
	{
		pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
		PR_ExecuteProgram(svprogfuncs, f);
	}
	else
		SV_ClientPrintf(host_client, PRINT_HIGH, "qcrequest \"%s\" not supported\n", rname);
}

void SV_AckEntityFrame(client_t *cl, int framenum)
{
#ifdef PEXT_CSQC
	if (cl->lastsequence_acknowledged + UPDATE_BACKUP > framenum)
	{
		int i;
		for (i = cl->lastsequence_acknowledged+1; i < framenum; i++)
			SV_CSQC_DroppedPacket(cl, i);
	}
	else
	{
		/*too much loss, we don't know what was sent when, so reset the entire entity state*/
		SV_CSQC_DropAll(cl);
	}
#endif
	cl->lastsequence_acknowledged = framenum;
}

/*
===================
SV_ExecuteClientMessage

The current net_message is parsed for the given client
===================
*/
void SV_ExecuteClientMessage (client_t *cl)
{
	client_t *split;
	int		c;
	char	*s;
	usercmd_t	oldest, oldcmd, newcmd;
	client_frame_t	*frame;
	vec3_t o;
	qboolean	move_issued = false; //only allow one move command
	int		checksumIndex;
	qbyte	checksum, calculatedChecksum;
	int		seq_hash, i;

	// calc ping time
	if (cl->frameunion.frames)
	{	//split screen doesn't always have frames.
		frame = &cl->frameunion.frames[cl->netchan.incoming_acknowledged & UPDATE_MASK];

		if (cl->lastsequence_acknowledged + UPDATE_BACKUP > cl->netchan.incoming_acknowledged)
		{
			/*note that if there is packetloss, we can change a single frame's ping_time multiple times
			  this means that the 'ping' is more latency than ping times*/
			if (frame->ping_time == -1 || !sv_ping_ignorepl.ival)
				frame->ping_time = realtime - frame->senttime;	//no more phenomanally low pings please

			if (cl->spectator)
				cl->delay = 0;
			else
			{
				if (frame->ping_time*1000 > sv_minping.value+1)
				{
					cl->delay -= 0.001;
					if (cl->delay < 0)
						cl->delay = 0;
				}
				if (frame->ping_time*1000 < sv_minping.value)
				{
					cl->delay += 0.001;
					if (cl->delay > 1)
						cl->delay = 1;
				}
			}
		}

		if (sv_antilag.ival)
		{
			/*
			extern cvar_t temp1;
			if (temp1.ival)
			frame = &cl->frameunion.frames[(cl->netchan.incoming_acknowledged+temp1.ival) & UPDATE_MASK];
			*/
#ifdef warningmsg
#pragma warningmsg("FIXME: make antilag optionally support non-player ents too")
#endif
			for (i = 0; i < sv.allocated_client_slots; i++)
			{
				cl->laggedents[i].present = frame->playerpresent[i];
				if (cl->laggedents[i].present)
					VectorCopy(frame->playerpositions[i], cl->laggedents[i].laggedpos);
			}
			cl->laggedents_count = sv.allocated_client_slots;

			cl->laggedents_frac = sv_antilag_frac.value;
		}
		else
			cl->laggedents_count = 0;
	}
	else
	{
		Con_Printf("Server bug: No frames!\n");
		cl->send_message = false;
		return;	//shouldn't happen...
	}

	// 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
	if (cl->frameunion.frames)
	{	//split screen doesn't always have frames.
		cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime;
		cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;
		cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].move_msecs = -1;
		cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].packetsizein = net_message.cursize;
		cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].packetsizeout = 0;
	}

	host_client = cl;
	sv_player = host_client->edict;

	seq_hash = cl->netchan.incoming_sequence;

	// 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 (msg_badread)
		{
			Con_Printf ("SVQW_ReadClientMessage: badread\n");
			SV_DropClient (cl);
			return;
		}

		c = MSG_ReadByte ();
haveannothergo:
		if (c == -1)
			break;

//		Con_Printf("(%s) %i: %i\n", cl->name, msg_readcount, c);

		switch (c)
		{
		default:
			Con_Printf ("SVQW_ReadClientMessage: unknown command char %i\n", c);
			SV_DropClient (cl);
			return;

		case clc_nop:
			break;

		case clc_delta:
			cl->delta_sequence = MSG_ReadByte ();
			break;

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

			move_issued = true;

			checksumIndex = MSG_GetReadCount();
			checksum = (qbyte)MSG_ReadByte ();

			// read loss percentage
			cl->lossage = MSG_ReadByte();

			for (split = cl; cl; cl = cl->controlled)	//FIXME
			{
				host_client = cl;
				cl->localtime = sv.time;
				sv_player = cl->edict;

				MSG_ReadDeltaUsercmd (&nullcmd, &oldest);
				MSG_ReadDeltaUsercmd (&oldest, &oldcmd);
				MSG_ReadDeltaUsercmd (&oldcmd, &newcmd);

				if (cl->frameunion.frames)
					cl->frameunion.frames[cl->netchan.outgoing_sequence&UPDATE_MASK].move_msecs = newcmd.msec;

				if ( cl->state == cs_spawned )
				{
					if (split == cl)
					{
						// if the checksum fails, ignore the rest of the packet
						calculatedChecksum = COM_BlockSequenceCRCByte(
							net_message.data + checksumIndex + 1,
							MSG_GetReadCount() - checksumIndex - 1,
							seq_hash);

						if (calculatedChecksum != checksum)
						{
							Con_DPrintf ("Failed command checksum for %s(%d) (%d != %d)\n",
								cl->name, cl->netchan.incoming_sequence, checksum, calculatedChecksum);

							for (cl = cl->controlled; cl; cl = cl->controlled)	//FIXME
							{
								MSG_ReadDeltaUsercmd (&nullcmd, &oldest);
								MSG_ReadDeltaUsercmd (&oldest, &oldcmd);
								MSG_ReadDeltaUsercmd (&oldcmd, &newcmd);
							}
							break;
						}
					}

					if (cl->iscrippled)
					{
						cl->lastcmd.forwardmove = 0;	//hmmm.... does this work well enough?
						oldest.forwardmove = 0;
						newcmd.forwardmove = 0;

						cl->lastcmd.sidemove = 0;
						oldest.sidemove = 0;
						newcmd.sidemove = 0;

						cl->lastcmd.upmove = 0;
						oldest.upmove = 0;
						newcmd.upmove = 0;
					}

#ifdef HLSERVER
					if (svs.gametype == GT_HALFLIFE)
					{
						SVHL_RunPlayerCommand(cl, &oldest, &oldcmd, &newcmd);
					}
					else
#endif
					if (!sv.paused)
					{
						if (sv_nqplayerphysics.ival)
						{
							cl->isindependant = false;
							if (!sv_player->v->fixangle)
							{
								sv_player->v->v_angle[0] = newcmd.angles[0]* (360.0/65536);
								sv_player->v->v_angle[1] = newcmd.angles[1]* (360.0/65536);
								sv_player->v->v_angle[2] = newcmd.angles[2]* (360.0/65536);
							}

							if (newcmd.impulse)// && SV_FiltureImpulse(newcmd.impulse, host_client->trustlevel))
								sv_player->v->impulse = newcmd.impulse;

							sv_player->v->button0 = newcmd.buttons & 1;
							sv_player->v->button2 = (newcmd.buttons >> 1) & 1;
							if (pr_allowbutton1.ival)	//many mods use button1 - it's just a wasted field to many mods. So only work it if the cvar allows.
								sv_player->v->button1 = ((newcmd.buttons >> 2) & 1);
						// DP_INPUTBUTTONS
							sv_player->xv->button3 = ((newcmd.buttons >> 2) & 1);
							sv_player->xv->button4 = ((newcmd.buttons >> 3) & 1);
							sv_player->xv->button5 = ((newcmd.buttons >> 4) & 1);
							sv_player->xv->button6 = ((newcmd.buttons >> 5) & 1);
							sv_player->xv->button7 = ((newcmd.buttons >> 6) & 1);
							sv_player->xv->button8 = ((newcmd.buttons >> 7) & 1);


							cl->lastcmd = newcmd;
							cl->lastcmd.buttons = 0; // avoid multiple fires on lag

							if (msg_badread)
							{
								Con_Printf ("SV_QWReadClientMessage: badread\n");
								SV_DropClient (cl);
								return;
							}
							c = MSG_ReadByte ();
							if (c != clc_move)
							{
								host_client = cl = split;
								sv_player = cl->edict;
								goto haveannothergo;
							}
							continue;
						}
						cl->isindependant = true;
						SV_PreRunCmd();

						if (net_drop < 20)
						{
							while (net_drop > 2)
							{
								SV_RunCmd (&cl->lastcmd, false);
								net_drop--;
							}
							if (net_drop > 1)
								SV_RunCmd (&oldest, false);
							if (net_drop > 0)
								SV_RunCmd (&oldcmd, false);
						}
						SV_RunCmd (&newcmd, false);

						if (!SV_PlayerPhysicsQC || host_client->spectator)
							SV_PostRunCmd();

					}
					else
					{
						if (newcmd.impulse)// && SV_FiltureImpulse(newcmd.impulse, host_client->trustlevel))
							sv_player->v->impulse = newcmd.impulse;
					}

					cl->lastcmd = newcmd;
					cl->lastcmd.buttons = 0; // avoid multiple fires on lag
				}

				if (msg_badread)
				{
					Con_Printf ("SV_ReadClientMessage: badread\n");
					SV_DropClient (cl);
					return;
				}

				c = MSG_ReadByte ();
				if (c != clc_move)
				{
					host_client = cl = split;
					sv_player = cl->edict;
					goto haveannothergo;
				}
			}
			host_client = cl = split;
			sv_player = cl->edict;
			break;

		case clc_prydoncursor:
			SV_ReadPrydonCursor();
			break;
		case clc_qcrequest:
			SV_ReadQCRequest();
			break;

		case clc_stringcmd:
			s = MSG_ReadString ();
			SV_ExecuteUserCommand (s, false);

			host_client = cl;
			sv_player = cl->edict;
			break;

		case clc_tmove:
			o[0] = MSG_ReadCoord();
			o[1] = MSG_ReadCoord();
			o[2] = MSG_ReadCoord();
			// only allowed by spectators
#ifdef SERVER_DEMO_PLAYBACK
			if (sv.mvdplayback)
			{
				VectorCopy(o, host_client->specorigin);
			}
			else
#endif
			if (host_client->spectator)
			{
				VectorCopy(o, sv_player->v->origin);
				World_LinkEdict(&sv.world, (wedict_t*)sv_player, false);
			}
			break;

		case clc_upload:
			SV_NextUpload();
			break;
#ifdef VOICECHAT
		case clc_voicechat:
			SV_VoiceReadPacket();
			break;
#endif
		case clcdp_ackframe:
			cl->delta_sequence = MSG_ReadLong();
			SV_AckEntityFrame(cl, cl->delta_sequence);
			break;
		}
	}

	SV_AckEntityFrame(cl, cl->netchan.incoming_acknowledged);

	host_client = NULL;
	sv_player = NULL;
}
#ifdef Q2SERVER
void SVQ2_ExecuteClientMessage (client_t *cl)
{
	enum clcq2_ops_e		c;
	char	*s;
	usercmd_t	oldest, oldcmd, newcmd;
	q2client_frame_t	*frame;
	qboolean	move_issued = false; //only allow one move command
	int		checksumIndex;
	qbyte	checksum, calculatedChecksum;
	int		seq_hash;
	int lastframe;

	if (!ge)
	{
		Con_Printf("Q2 client without Q2 server\n");
		SV_DropClient(cl);
	}

	// calc ping time
	frame = &cl->frameunion.q2frames[cl->netchan.incoming_acknowledged & Q2UPDATE_MASK];
	if (frame->senttime != -1)
	{
		int ping_time = realtime*1000 - frame->senttime;	//no more phenomanally low pings please
		if (ping_time > sv_minping.value+1)
		{
			cl->delay -= 0.001;
			if (cl->delay < 0)
				cl->delay = 0;
		}
		if (ping_time < sv_minping.value)
		{
			cl->delay += 0.001;
			if (cl->delay > 1)
				cl->delay = 1;
		}
		frame->senttime = -1;
		frame->ping_time = ping_time;
	}

	// 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->frameunion.q2frames[cl->netchan.outgoing_sequence & Q2UPDATE_MASK].senttime = realtime*1000;
	cl->frameunion.q2frames[cl->netchan.outgoing_sequence & Q2UPDATE_MASK].ping_time = -1;

	host_client = cl;
	sv_player = host_client->edict;

	seq_hash = cl->netchan.incoming_sequence;

	// 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 (msg_badread)
		{
			Con_Printf ("SVQ2_ExecuteClientMessage: badread\n");
			SV_DropClient (cl);
			return;
		}

		c = MSG_ReadByte ();
		if (c == -1)
			break;

		switch (c)
		{
		default:
			Con_Printf ("SVQ2_ReadClientMessage: unknown command char %i\n", c);
			SV_DropClient (cl);
			return;

		case clcq2_nop:
			break;

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

			move_issued = true;

			checksumIndex = MSG_GetReadCount();
			checksum = (qbyte)MSG_ReadByte ();


			lastframe = MSG_ReadLong();
			if (lastframe != host_client->delta_sequence)
			{
				cl->delta_sequence = lastframe;
			}

			MSGQ2_ReadDeltaUsercmd (&nullcmd, &oldest);
			MSGQ2_ReadDeltaUsercmd (&oldest, &oldcmd);
			MSGQ2_ReadDeltaUsercmd (&oldcmd, &newcmd);

			if ( cl->state != cs_spawned )
				break;

			// if the checksum fails, ignore the rest of the packet
			calculatedChecksum = Q2COM_BlockSequenceCRCByte(
				net_message.data + checksumIndex + 1,
				MSG_GetReadCount() - checksumIndex - 1,
				seq_hash);

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

			if (cl->iscrippled)
			{
				cl->lastcmd.forwardmove = 0;	//hmmm.... does this work well enough?
				oldest.forwardmove = 0;
				newcmd.forwardmove = 0;

				cl->lastcmd.sidemove = 0;
				oldest.sidemove = 0;
				newcmd.sidemove = 0;

				cl->lastcmd.upmove = 0;
				oldest.upmove = 0;
				newcmd.upmove = 0;
			}

			if (!sv.paused)
			{
				if (net_drop < 20)
				{
					while (net_drop > 2)
					{
						ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&cl->lastcmd);
						net_drop--;
					}
					if (net_drop > 1)
						ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&oldest);
					if (net_drop > 0)
						ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&oldcmd);
				}
				ge->ClientThink (host_client->q2edict, (q2usercmd_t*)&newcmd);
			}

			cl->lastcmd = newcmd;
			break;

		case clcq2_userinfo:
			strncpy (cl->userinfo, MSG_ReadString (), sizeof(cl->userinfo)-1);
			ge->ClientUserinfoChanged (cl->q2edict, cl->userinfo);	//tell the gamecode
			SV_ExtractFromUserinfo(cl, true);	//let the server routines know
			break;

		case clcq2_stringcmd:
			s = MSG_ReadString ();
			SV_ExecuteUserCommand (s, false);

			host_client = cl;
			sv_player = cl->edict;

			if (cl->state == cs_zombie)
				return;	// disconnect command
			break;

#ifdef VOICECHAT
		case clc_voicechat:
			SV_VoiceReadPacket();
			break;
#endif
		}
	}
}
#endif
#ifdef NQPROT
void SVNQ_ReadClientMove (usercmd_t *move)
{
	int		i;
	int		bits;
	client_frame_t	*frame;
	float timesincelast;
	float cltime;

	frame = &host_client->frameunion.frames[host_client->netchan.incoming_acknowledged & UPDATE_MASK];

	if (host_client->protocol == SCP_DARKPLACES7 || (host_client->fteprotocolextensions2 & PEXT2_PREDINFO))
		host_client->last_sequence = MSG_ReadLong ();
	else
		host_client->last_sequence = 0;
	cltime = MSG_ReadFloat ();
	if (cltime < move->fservertime)
		cltime = move->fservertime;
	if (cltime > sv.time)
		cltime = sv.time;
	if (cltime < sv.time - 2)	//if you do lag more than this, you won't get your free time.
		cltime = sv.time - 2;
	timesincelast = cltime - move->fservertime;

	move->fservertime = cltime;
	move->servertime = move->fservertime;

	frame->ping_time = sv.time - cltime;


	if (frame->ping_time*1000 > sv_minping.value+1)
	{
		host_client->delay -= 0.001;
		if (host_client->delay < 0)
			host_client->delay = 0;
	}
	if (frame->ping_time*1000 < sv_minping.value)
	{
		host_client->delay += 0.001;
		if (host_client->delay > 1)
			host_client->delay = 1;
	}


// read current angles
	for (i=0 ; i<3 ; i++)
	{
		if ((host_client->protocol == SCP_FITZ666 || host_client->protocol == SCP_PROQUAKE))
			host_client->edict->v->v_angle[i] = MSG_ReadAngle16 ();
		else
			host_client->edict->v->v_angle[i] = MSG_ReadAngle ();

		move->angles[i] = (host_client->edict->v->v_angle[i] * 256*256)/360;
	}

// read movement
	move->forwardmove = MSG_ReadShort ();
	move->sidemove = MSG_ReadShort ();
	move->upmove = MSG_ReadShort ();

	move->msec=bound(0, timesincelast*1000, 255);
	frame->move_msecs = timesincelast*1000;

// read buttons
	if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7 || (host_client->fteprotocolextensions2 & PEXT2_PRYDONCURSOR))
		bits = MSG_ReadLong();
	else
		bits = MSG_ReadByte ();
	move->buttons = bits;

	i = MSG_ReadByte ();
	if (i)
		move->impulse = i;

	if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7 || (host_client->fteprotocolextensions2 & PEXT2_PRYDONCURSOR))
	{
		SV_ReadPrydonCursor();
	}

	if (SV_RunFullQCMovement(host_client, move))
	{
		pr_global_struct->time = sv.world.physicstime;
		pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
#ifdef VM_Q1
		if (svs.gametype == GT_Q1QVM)
			Q1QVM_PostThink();
		else
#endif
		{
			if (pr_global_struct->PlayerPostThink)
				PR_ExecuteProgram (svprogfuncs, pr_global_struct->PlayerPostThink);
		}
		host_client->isindependant = true;
		return;
	}


//	if (i && SV_FilterImpulse(i, host_client->trustlevel))
//		host_client->edict->v->impulse = i;

	host_client->edict->v->button0 = bits & 1;
	host_client->edict->v->button2 = (bits >> 1) & 1;
	if (pr_allowbutton1.ival)	//many mods use button1 - it's just a wasted field to many mods. So only work it if the cvar allows.
		host_client->edict->v->button1 = ((bits >> 2) & 1);
// DP_INPUTBUTTONS
	host_client->edict->xv->button3 = ((bits >> 2) & 1);
	host_client->edict->xv->button4 = ((bits >> 3) & 1);
	host_client->edict->xv->button5 = ((bits >> 4) & 1);
	host_client->edict->xv->button6 = ((bits >> 5) & 1);
	host_client->edict->xv->button7 = ((bits >> 6) & 1);
	host_client->edict->xv->button8 = ((bits >> 7) & 1);

	if (host_client->last_sequence)
	{
		host_frametime = timesincelast;

		host_client->isindependant = true;
		SV_PreRunCmd();
		SV_RunCmd (move, true);
		SV_PostRunCmd();
	}
	else
		host_client->isindependant = false;
}

void SVNQ_ExecuteClientMessage (client_t *cl)
{
	int		c;
	char	*s;
	client_frame_t	*frame;
	int		seq_hash;

	cl->netchan.outgoing_sequence++;
	cl->netchan.incoming_acknowledged = cl->netchan.outgoing_sequence-1;

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

	// 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->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].senttime = realtime;
	cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].ping_time = -1;
	cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].move_msecs = -1;
	cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].packetsizein = net_message.cursize;
	cl->frameunion.frames[cl->netchan.outgoing_sequence & UPDATE_MASK].packetsizeout = 0;

	host_client = cl;
	sv_player = host_client->edict;

	seq_hash = cl->netchan.incoming_sequence;

	// mark time so clients will know how much to predict
	// other players
 	cl->localtime = sv.time;
	while (1)
	{
		if (msg_badread)
		{
			Con_Printf ("SVNQ_ReadClientMessage: badread\n");
			SV_DropClient (cl);
			return;
		}

		c = MSG_ReadByte ();
		if (c == -1)
			break;

		switch (c)
		{
		default:
			Con_Printf ("SVNQ_ReadClientMessage: unknown command char %i\n", c);
			SV_DropClient (cl);
			return;

		case clc_disconnect:
			host_client = cl;
			sv_player = cl->edict;
			SV_Drop_f();
			break;
		case clc_nop:
			break;

//		case clc_delta:
//			cl->delta_sequence = MSG_ReadByte ();
//			break;

		case clc_move:
			SVNQ_ReadClientMove (&host_client->lastcmd);
//			cmd = host_client->lastcmd;
//			SV_ClientThink();
			break;

		case clc_stringcmd:
			s = MSG_ReadString ();
			SV_ExecuteUserCommand (s, false);

			host_client = cl;
			sv_player = cl->edict;
			break;

		case clc_qcrequest:
			SV_ReadQCRequest();
			break;

		case clcdp_ackframe:
			cl->delta_sequence = MSG_ReadLong();
			SV_AckEntityFrame(cl, cl->delta_sequence);
			break;
		case clcdp_ackdownloaddata:
			SV_DarkPlacesDownloadAck(cl);
			break;

#ifdef VOICECHAT
		case clc_voicechat:
			SV_VoiceReadPacket();
			break;
#endif
		}
	}
}
#endif
/*
==============
SV_UserInit
==============
*/
void SV_UserInit (void)
{
#ifdef VOICECHAT
	Cvar_Register (&sv_voip, cvargroup_serverpermissions);
	Cvar_Register (&sv_voip_echo, cvargroup_serverpermissions);
	Cvar_Register (&sv_voip_record, cvargroup_serverpermissions);
#endif
#ifdef SERVERONLY
	Cvar_Register (&cl_rollspeed, "Prediction stuff");
	Cvar_Register (&cl_rollangle, "Prediction stuff");
#endif
	Cvar_Register (&sv_chatfilter, cvargroup_serverpermissions);
	Cvar_Register (&sv_spectalk, cvargroup_servercontrol);
	Cvar_Register (&sv_mapcheck, cvargroup_servercontrol);

	Cvar_Register (&sv_fullredirect, cvargroup_servercontrol);
	Cvar_Register (&sv_antilag, cvargroup_servercontrol);
	Cvar_Register (&sv_antilag_frac, cvargroup_servercontrol);
	Cvar_Register (&sv_cheatpc, cvargroup_servercontrol);
	Cvar_Register (&sv_cheatspeedchecktime, cvargroup_servercontrol);
	Cvar_Register (&sv_playermodelchecks, cvargroup_servercontrol);

	Cvar_Register (&sv_getrealip, cvargroup_servercontrol);
	Cvar_Register (&sv_realip_kick, cvargroup_servercontrol);
	Cvar_Register (&sv_realiphostname_ipv4, cvargroup_servercontrol);
	Cvar_Register (&sv_realiphostname_ipv6, cvargroup_servercontrol);
	Cvar_Register (&sv_realip_timeout, cvargroup_servercontrol);

	Cvar_Register (&sv_pushplayers, cvargroup_servercontrol);
	Cvar_Register (&sv_protocol_nq, cvargroup_servercontrol);

	Cvar_Register (&sv_pure, cvargroup_servercontrol);
	Cvar_Register (&sv_floodprotect, cvargroup_servercontrol);
	Cvar_Register (&sv_floodprotect_interval, cvargroup_servercontrol);
	Cvar_Register (&sv_floodprotect_messages, cvargroup_servercontrol);
	Cvar_Register (&sv_floodprotect_silencetime, cvargroup_servercontrol);
	Cvar_Register (&sv_floodprotect_suicide, cvargroup_servercontrol);
	Cvar_Register (&sv_floodprotect_sendmessage, cvargroup_servercontrol);

	Cvar_Register (&sv_cmdlikercon, cvargroup_serverpermissions);
	Cvar_Register(&cmd_gamecodelevel, "Access controls");
	Cvar_Register(&cmd_allowaccess, "Access controls");

	Cvar_Register (&votelevel, sv_votinggroup);
	Cvar_Register (&voteminimum, sv_votinggroup);
	Cvar_Register (&votepercent, sv_votinggroup);
	Cvar_Register (&votetime, sv_votinggroup);

	Cvar_Register (&sv_brokenmovetypes, "Backwards compatability");

	Cvar_Register (&sv_edgefriction, "netquake compatability");
}





























































static vec3_t forward, right, up, wishdir;
static float *origin, *velocity, *angles;
static float wishspeed;
extern cvar_t sv_accelerate, sv_friction;
static qboolean onground;


/*
==================
SV_UserFriction

==================
*/
static void SV_UserFriction (void)
{
	extern cvar_t sv_stopspeed;
	float	*vel;
	float	speed, newspeed, control;
	vec3_t	start, stop;
	float	friction;
	trace_t	trace;

	vel = velocity;

	speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]);
	if (!speed)
		return;

// if the leading edge is over a dropoff, increase friction
	start[0] = stop[0] = origin[0] + vel[0]/speed*16;
	start[1] = stop[1] = origin[1] + vel[1]/speed*16;
	start[2] = origin[2] + sv_player->v->mins[2];
	stop[2] = start[2] - 34;

	trace = World_Move (&sv.world, start, vec3_origin, vec3_origin, stop, true, (wedict_t*)sv_player);

	if (trace.fraction == 1.0)
		friction = sv_friction.value*sv_edgefriction.value;
	else
		friction = sv_friction.value;

//	val = GetEdictFieldValue(sv_player, "friction", &frictioncache);
//	if (val && val->_float)
//		friction *= val->_float;

// apply friction
	control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed;
	newspeed = speed - host_frametime*control*friction;

	if (newspeed < 0)
		newspeed = 0;
	newspeed /= speed;

	vel[0] = vel[0] * newspeed;
	vel[1] = vel[1] * newspeed;
	vel[2] = vel[2] * newspeed;
}

static void SV_Accelerate (void)
{
	int			i;
	float		addspeed, accelspeed, currentspeed;

	currentspeed = DotProduct (velocity, wishdir);
	addspeed = wishspeed - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = sv_accelerate.value*host_frametime*wishspeed;
	if (accelspeed > addspeed)
		accelspeed = addspeed;

	for (i=0 ; i<3 ; i++)
		velocity[i] += accelspeed*wishdir[i];
}

static void SV_AirAccelerate (vec3_t wishveloc)
{
	int			i;
	float		addspeed, wishspd, accelspeed, currentspeed;

	wishspd = VectorNormalize (wishveloc);
	if (wishspd > 30)
		wishspd = 30;
	currentspeed = DotProduct (velocity, wishveloc);
	addspeed = wishspd - currentspeed;
	if (addspeed <= 0)
		return;
//	accelspeed = sv_accelerate.value * host_frametime;
	accelspeed = sv_accelerate.value*wishspeed * host_frametime;
	if (accelspeed > addspeed)
		accelspeed = addspeed;

	for (i=0 ; i<3 ; i++)
		velocity[i] += accelspeed*wishveloc[i];
}

/*
===================
SV_AirMove

===================
*/
static void SV_AirMove (void)
{
	int			i;
	vec3_t		wishvel;
	float		fmove, smove;
	float scale, maxspeed;

	AngleVectors (sv_player->v->angles, forward, right, up);

	fmove = cmd.forwardmove;
	smove = cmd.sidemove;

// hack to not let you back into teleporter
	if (sv.time < sv_player->v->teleport_time && fmove < 0)
		fmove = 0;

	for (i=0 ; i<3 ; i++)
		wishvel[i] = forward[i]*fmove + right[i]*smove;

	if ( (int)sv_player->v->movetype != MOVETYPE_WALK)
		wishvel[2] = cmd.upmove;
	else
		wishvel[2] = 0;

	VectorCopy (wishvel, wishdir);
	wishspeed = VectorNormalize(wishdir);
//	val = GetEdictFieldValue(sv_player, "scale", &scalecache);
//	if (!val || !val->_float)
		scale = 1;
//	else
//		scale = val->_float;

	maxspeed=sv_player->xv->maxspeed;//FIXME: This isn't fully compatible code...
	if (sv_player->xv->hasted)
		maxspeed*=sv_player->xv->hasted;

	if (wishspeed > maxspeed*scale)
	{
		VectorScale (wishvel, maxspeed/wishspeed, wishvel);
		wishspeed = maxspeed*scale;
	}

	if ( sv_player->v->movetype == MOVETYPE_NOCLIP)
	{	// noclip
		VectorCopy (wishvel, velocity);
	}
	else if ( onground )
	{
		SV_UserFriction ();
		SV_Accelerate ();
	}
	else
	{	// not on ground, so little effect on velocity
		SV_AirAccelerate (wishvel);
	}
}

static void SV_WaterMove (void)
{
	int		i;
	vec3_t	wishvel;
	float	speed, newspeed, wishspeed, addspeed, accelspeed;
	float scale;
	float maxspeed;

//
// user intentions
//
	AngleVectors (sv_player->v->v_angle, forward, right, up);

	for (i=0 ; i<3 ; i++)
		wishvel[i] = forward[i]*cmd.forwardmove + right[i]*cmd.sidemove;

	if (!cmd.forwardmove && !cmd.sidemove && !cmd.upmove)
		wishvel[2] -= 60;		// drift towards bottom
	else
		wishvel[2] += cmd.upmove;

	wishspeed = Length(wishvel);
//	val = GetEdictFieldValue(sv_player, "scale", &scalecache);
//	if (!val || !val->_float)
		scale = 1;
//	else
//		scale = val->_float;

//	val = GetEdictFieldValue(sv_player, "maxspeed", &maxspeedcache);
//	if (val && val->_float)
//		maxspeed = sv_maxspeed.value*val->_float;
//	else
		maxspeed = host_client->maxspeed;
	if (wishspeed > maxspeed*scale)
	{
		VectorScale (wishvel, maxspeed/wishspeed, wishvel);
		wishspeed = maxspeed*scale;
	}
	wishspeed *= 0.7;

//
// water friction
//
	speed = Length (velocity);
	if (speed)
	{
//		val = GetEdictFieldValue(sv_player, "friction", &frictioncache);
//		if (val&&val->_float)
//			newspeed = speed - host_frametime * speed * sv_friction.value*val->_float;
//		else
			newspeed = speed - host_frametime * speed * sv_friction.value;
		if (newspeed < 0)
			newspeed = 0;
		VectorScale (velocity, newspeed/speed, velocity);
	}
	else
		newspeed = 0;

//
// water acceleration
//
	if (!wishspeed)
		return;

	addspeed = wishspeed - newspeed;
	if (addspeed <= 0)
		return;

	VectorNormalize (wishvel);
	accelspeed = sv_accelerate.value * wishspeed * host_frametime;
	if (accelspeed > addspeed)
		accelspeed = addspeed;

	for (i=0 ; i<3 ; i++)
		velocity[i] += accelspeed * wishvel[i];
}

static void SV_WaterJump (void)
{
	if (sv.time > sv_player->v->teleport_time
	|| !sv_player->v->waterlevel)
	{
		sv_player->v->flags = (int)sv_player->v->flags & ~FL_WATERJUMP;
		sv_player->v->teleport_time = 0;
	}
	sv_player->v->velocity[0] = sv_player->v->movedir[0];
	sv_player->v->velocity[1] = sv_player->v->movedir[1];
}



void SV_ClientThink (void)
{
	vec3_t		v_angle;

	cmd = host_client->lastcmd;
	sv_player = host_client->edict;

	if (host_client->state && host_client->protocol != SCP_BAD)
	{
		sv_player->xv->movement[0] = cmd.forwardmove;
		sv_player->xv->movement[1] = cmd.sidemove;
		sv_player->xv->movement[2] = cmd.upmove;
	}

	if (SV_PlayerPhysicsQC && !host_client->spectator)
	{
		pr_global_struct->time = sv.world.physicstime;
		pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
		PR_ExecuteProgram (svprogfuncs, SV_PlayerPhysicsQC);
		return;
	}

	if (sv_player->v->movetype == MOVETYPE_NONE)
		return;

	onground = (int)sv_player->v->flags & FL_ONGROUND;

	origin = sv_player->v->origin;
	velocity = sv_player->v->velocity;

//	DropPunchAngle ();

//
// if dead, behave differently
//
	if (sv_player->v->health <= 0)
		return;

//
// angles
// show 1/3 the pitch angle and all the roll angle
	angles = sv_player->v->angles;

	VectorCopy (sv_player->v->v_angle, v_angle);
//	VectorAdd (sv_player->v->v_angle, sv_player->v->punchangle, v_angle);
	angles[ROLL] = V_CalcRoll (sv_player->v->angles, sv_player->v->velocity)*4;
	if (!sv_player->v->fixangle)
	{
		angles[PITCH] = -v_angle[PITCH]/3;
		angles[YAW] = v_angle[YAW];
	}

	if ( (int)sv_player->v->flags & FL_WATERJUMP )
	{
		SV_WaterJump ();
		return;
	}
//
// walk
//
	if ( (sv_player->v->waterlevel >= 2)
	&& (sv_player->v->movetype != MOVETYPE_NOCLIP) )
	{
		SV_WaterMove ();
		return;
	}

	SV_AirMove ();
}

#endif
