/*
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 "qwsvdef.h"

#ifndef CLIENTONLY

#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_cheatpc = SCVAR("sv_cheatpc", "125");
cvar_t	sv_cheatspeedchecktime = SCVAR("sv_cheatspeedchecktime", "30");
cvar_t	sv_playermodelchecks = SCVAR("sv_playermodelchecks", "1");

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_nomsec = SCVAR("sv_nomsec", "0");
cvar_t	sv_edgefriction = FCVAR("sv_edgefriction", "edgefriction", "2", 0);

cvar_t	sv_brokenmovetypes = SCVAR("sv_brokenmovetypes", "0");

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

cvar_t	sv_floodprotect = SCVAR("sv_floodprotect", "1");
cvar_t	sv_floodprotect_messages = SCVAR("sv_floodprotect_messages", "4");
cvar_t	sv_floodprotect_interval = SCVAR("sv_floodprotect_interval", "4");
cvar_t  sv_floodprotect_silencetime = SCVAR("sv_floodprotect_silencetime", "10");
cvar_t	sv_floodprotect_suicide = SCVAR("sv_floodprotect_suicide", "1");
cvar_t	sv_floodprotect_sendmessage = FCVAR("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 = SCVAR("sv_getrealip", "0");
cvar_t sv_realip_kick = SCVAR("sv_realip_kick", "0");
cvar_t sv_realiphostname_ipv4 = SCVAR("sv_realiphostname_ipv4", "");
cvar_t sv_realiphostname_ipv6 = SCVAR("sv_realiphostname_ipv6", "");
cvar_t sv_realip_timeout = SCVAR("sv_realip_timeout", "10");

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)
		{
			SV_DropClient(client);
			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, 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;

	if (host_client->state == cs_spawned)
		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"))
		gamedir = "qw";

//NOTE:  This doesn't go through ClientReliableWrite since it's before the user
//spawns.  These functions are written to not overflow
/*	if (host_client->num_backbuf)
	{
		Con_TPrintf(STL_BACKBUFSET, host_client->name, host_client->netchan.message.cursize);
		host_client->num_backbuf = 0;
		SZ_Clear(&host_client->netchan.message);
	}
*/
	if (sizeofcoord > 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 (sizeofcoord == 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);
	}
#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);

	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;
		}
		if (sv.demostate)
		{
			playernum = (MAX_CLIENTS-1-splitnum)|128;
		}
		else 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
	if (sv.demostatevalid)
		ClientReliableWrite_String (host_client, sv.demfullmapname);
	else
		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);

	// send server info string
	if (sv.demostatevalid)
	{
		ClientReliableCheckBlock(host_client, 20 + strlen(sv.demoinfo));
		ClientReliableWrite_Byte (host_client, svc_stufftext);
		ClientReliableWrite_String (host_client, va("fullserverinfo \"%s\"\n", sv.demoinfo) );
	}
	else
	{
		ClientReliableCheckBlock(host_client, 20 + strlen(svs.info));
		ClientReliableWrite_Byte (host_client, svc_stufftext);
		ClientReliableWrite_String (host_client, va("fullserverinfo \"%s\"\n", svs.info) );
	}

	host_client->csqcactive = false;

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

	// send music
	ClientReliableCheckBlock(host_client, 2);

	ClientReliableWrite_Byte (host_client, svc_cdtrack);
	if (svprogfuncs)
		ClientReliableWrite_Byte (host_client, sv.edicts->v->sounds);
	else
		ClientReliableWrite_Byte (host_client, 0);

	SV_LogPlayer(host_client, "new (QW)");
}
#define GAME_DEATHMATCH 0
#define GAME_COOP 1
void SVNQ_New_f (void)
{
	extern cvar_t coop;
	char			message[2048];
	int i;

	MSG_WriteByte (&host_client->netchan.message, svc_print);
#ifdef DISTRIBUTION
	sprintf (message, "%c\n" DISTRIBUTION " QuakeWorld build %i server\n", 2, build_number());
#else
	sprintf (message, "%c\nQUAKEWORLD BUILD %i SERVER\n", 2, build_number());
#endif
	MSG_WriteString (&host_client->netchan.message,message);

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

			host_client->csqcactive = true;
		}
	}

	MSG_WriteByte (&host_client->netchan.message, svc_serverdata);
	switch(host_client->protocol)
	{
#ifdef NQPROT
	case SCP_NETQUAKE:
		SV_LogPlayer(host_client, "new (NQ)");
		MSG_WriteLong (&host_client->netchan.message, NQ_PROTOCOL_VERSION);
		MSG_WriteByte (&host_client->netchan.message, 16);
		break;
	case SCP_DARKPLACES6:
		SV_LogPlayer(host_client, "new (DP6)");
		MSG_WriteLong (&host_client->netchan.message, DP6_PROTOCOL_VERSION);
		MSG_WriteByte (&host_client->netchan.message, sv.allocated_client_slots);
		break;
	case SCP_DARKPLACES7:
		SV_LogPlayer(host_client, "new (DP7)");
		MSG_WriteLong (&host_client->netchan.message, DP7_PROTOCOL_VERSION);
		MSG_WriteByte (&host_client->netchan.message, sv.allocated_client_slots);
		break;
#endif
	default:
		host_client->drop = true;
		break;
	}

	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);
	MSG_WriteByte (&host_client->netchan.message, sv.edicts->v->sounds);
	MSG_WriteByte (&host_client->netchan.message, sv.edicts->v->sounds);

// set view
	MSG_WriteByte (&host_client->netchan.message, svc_setview);
	MSG_WriteShort (&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
}





#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 < MAX_QWMSGLEN/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 <  MAX_QWMSGLEN/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

void SV_PK3List_f (void)
{
#ifndef PEXT_PK3DOWNLOADS
	Con_Printf ("pk3list not valid -- It's not implemented!\n");
	return;
#else
	int crc;
	char *name;
	int i;


	if (host_client->state != cs_connected)
	{	//fixme: send prints instead
		Con_Printf ("pk3list not valid -- already spawned\n");
		return;
	}

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

	i = atoi(Cmd_Argv(2));

//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)
	{
		char *msg = va("cmd pk3list %s %s\n", Cmd_Argv(1), Cmd_Argv(2));
		Con_TPrintf(STL_BACKBUFSET, host_client->name, host_client->netchan.message.cursize);
		ClientReliableWrite_Begin(host_client, svc_stufftext, 2+strlen(msg));
		ClientReliableWrite_String(&host_client->netchan.message, msg);
		return;
	}
	if (i < 0)
	{
		Con_Printf ("SV_PK3List_f: %s tried to crash us\n", host_client->name);
		SV_DropClient(host_client);
		return;
	}

	for (; ; i++)
	{
		if (host_client->netchan.message.cursize < (MAX_QWMSGLEN/2))
		{	//user's buffer was too small
			MSG_WriteByte(&host_client->netchan.message, svc_stufftext);
			MSG_WriteString(&host_client->netchan.message, va("cmd pk3list %i %i\n", svs.spawncount, 0));
			return;	//and stop before we flood them
		}

		name = COM_GetPathInfo(i, &crc);

		if (name && *name)
		{
			MSG_WriteByte(&host_client->netchan.message, svc_stufftext);
			MSG_WriteString(&host_client->netchan.message, va("echo packfile %s\n", name));
			continue;	//try the next.
		}
		//that's all folks, move on to sound.
		MSG_WriteByte(&host_client->netchan.message, svc_stufftext);
		MSG_WriteString(&host_client->netchan.message, va("soundlist %i 0\n", svs.spawncount));
		return;
	}
#endif
}

/*
==================
SV_Soundlist_f
==================
*/
void SV_Soundlist_f (void)
{
	unsigned int i;
	//char		**s;
	unsigned int n;
	unsigned int maxclientsupportedsounds;

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

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

	n = atoi(Cmd_Argv(2));

//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)
	{
		char *msg = va("cmd soundlist %s %s\n", Cmd_Argv(1), Cmd_Argv(2));
		Con_TPrintf(STL_BACKBUFSET, host_client->name, host_client->netchan.message.cursize);
		ClientReliableWrite_Begin(host_client, svc_stufftext, 1+strlen(msg));
		ClientReliableWrite_String(host_client, msg);
		return;
	}

	if (n >= MAX_SOUNDS)
	{
		SV_EndRedirect();
		Con_Printf ("SV_Soundlist_f: %s send an invalid index\n", host_client->name);
		SV_DropClient(host_client);
		return;
	}

#ifdef PEXT_SOUNDDBL
	if (n > 255)
	{
		MSG_WriteByte (&host_client->netchan.message, svcfte_soundlistshort);
		MSG_WriteShort (&host_client->netchan.message, n);
	}
	else
#endif
	{
		MSG_WriteByte (&host_client->netchan.message, svc_soundlist);
		MSG_WriteByte (&host_client->netchan.message, n);
	}

	maxclientsupportedsounds = 256;
#ifdef PEXT_SOUNDDBL
	if (host_client->fteprotocolextensions & PEXT_SOUNDDBL)
		maxclientsupportedsounds *= 2;
#endif

	if (sv.democausesreconnect)	//read the list from somewhere else
	{
		for (i = 1+n;
			*sv.demsound_precache[i] && host_client->netchan.message.cursize < (MAX_QWMSGLEN/2);
			i++, n++)
			MSG_WriteString (&host_client->netchan.message, sv.demsound_precache[i]);


		if (!*sv.demsound_precache[i])
			n = 0;
	}
	else
	{
		for (i = 1+n;
			i < maxclientsupportedsounds && *sv.strings.sound_precache[i] && host_client->netchan.message.cursize < (MAX_QWMSGLEN/2);
			i++, n++)
		{
			MSG_WriteString (&host_client->netchan.message, sv.strings.sound_precache[i]);
			if (((n&255)==255) && n != i-1)
				break;
		}

		if (!*sv.strings.sound_precache[i])
			n = 0;
	}
	MSG_WriteByte (&host_client->netchan.message, 0);

	// next msg
	MSG_WriteByte (&host_client->netchan.message, n);
}

/*
==================
SV_Modellist_f
==================
*/
void SV_Modellist_f (void)
{
	unsigned int i;
	unsigned int n;
	unsigned int maxclientsupportedmodels;

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

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

	n = atoi(Cmd_Argv(2));

	if (n >= MAX_MODELS)
	{
		SV_EndRedirect();
		Con_Printf ("SV_Modellist_f: %s send an invalid index\n", host_client->name);
		SV_DropClient(host_client);
		return;
	}

	if (n == 0 && (host_client->zquake_extensions & Z_EXT_VWEP))
	{
		char mname[MAX_QPATH];
		char vweaplist[1024] = "//vwep";
		int pos = strlen(vweaplist);

		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
			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, ' '))
				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(host_client, svc_stufftext, 2+strlen(vweaplist));
			ClientReliableWrite_String(host_client, vweaplist);
		}
	}

//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)
	{
		char *msg = va("cmd modellist %s %s\n", Cmd_Argv(1), Cmd_Argv(2));
		Con_TPrintf(STL_BACKBUFSET, host_client->name, host_client->netchan.message.cursize);
		ClientReliableWrite_Begin(host_client, svc_stufftext, 1+strlen(msg));
		ClientReliableWrite_String(host_client, msg);
		return;
	}

#ifdef PEXT_MODELDBL
	if (n > 255)
	{
		MSG_WriteByte (&host_client->netchan.message, svcfte_modellistshort);
		MSG_WriteShort (&host_client->netchan.message, n);
	}
	else
#endif
	{
		MSG_WriteByte (&host_client->netchan.message, svc_modellist);
		MSG_WriteByte (&host_client->netchan.message, n);
	}

	maxclientsupportedmodels = 256;
#ifdef PEXT_MODELDBL
	if (host_client->fteprotocolextensions & PEXT_MODELDBL)
		maxclientsupportedmodels *= 2;
#endif

	if (sv.democausesreconnect)	//read the list from somewhere else
	{
		for (i = 1+n;
			*sv.demmodel_precache[i] && ((n&255)==0||host_client->netchan.message.cursize < (MAX_QWMSGLEN/2));
			i++, n++)
			MSG_WriteString (&host_client->netchan.message, sv.demmodel_precache[i]);

		if (!*sv.demmodel_precache[i])
			n = 0;
	}
	else
	{
		for (i = 1+n;
			i < maxclientsupportedmodels && sv.strings.model_precache[i] && host_client->netchan.message.cursize < (MAX_QWMSGLEN/2);	//make sure we don't send a 0 next...
			i++)
		{
			MSG_WriteString (&host_client->netchan.message, sv.strings.model_precache[i]);
			if (((n&255)==255) && n != i-1)
				break;
		}
		n = i-1;

		if (!sv.strings.model_precache[i])
			n = 0;
	}

	if (i == maxclientsupportedmodels)
		n = 0;	//doh!

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

	// next msg
	MSG_WriteByte (&host_client->netchan.message, n);
}

/*
==================
SV_PreSpawn_f
==================
*/
void SV_PreSpawn_f (void)
{
	unsigned	buf, bufs;
	unsigned	check;

	unsigned statics;

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

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

	if (sv.democausesreconnect)
		bufs = sv.num_demosignon_buffers;
	else
		bufs = sv.num_signon_buffers;
	statics = sv.numextrastatics;
	buf = atoi(Cmd_Argv(2));

	if (buf >= bufs+statics+sv.num_edicts+255)
	{
		SV_EndRedirect();
		Con_Printf ("SV_Modellist_f: %s send an invalid index\n", host_client->name);
		SV_DropClient(host_client);
		return;
	}

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

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

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

//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)
	{
		char *msg = va("cmd prespawn %s %s %s\n", Cmd_Argv(1), Cmd_Argv(2), Cmd_Argv(3));
		Con_TPrintf(STL_BACKBUFSET, host_client->name, host_client->netchan.message.cursize);
		ClientReliableWrite_Begin(host_client, svc_stufftext, 1+strlen(msg));
		ClientReliableWrite_String(host_client, msg);
		return;
	}

	if (buf >= bufs && !sv.democausesreconnect)
	{
		int i;
		entity_state_t from;
		entity_state_t *state;
		edict_t *ent;
		svcustomtents_t *ctent;


		memset(&from, 0, sizeof(from));
		while (host_client->netchan.message.cursize < (host_client->netchan.message.maxsize/2))	//static entities
		{
			if (buf - bufs >= sv.numextrastatics)
				break;

			state = &sv.extendedstatics[buf - bufs];

			if (host_client->fteprotocolextensions & PEXT_SPAWNSTATIC2)
			{
				MSG_WriteByte(&host_client->netchan.message, svc_spawnstatic2);
				SV_WriteDelta(&from, state, &host_client->netchan.message, true, host_client->fteprotocolextensions);
			}
			else if (state->modelindex < 256)
			{
				MSG_WriteByte(&host_client->netchan.message, svc_spawnstatic);

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

				MSG_WriteByte (&host_client->netchan.message, state->frame);
				MSG_WriteByte (&host_client->netchan.message, (int)state->colormap);
				MSG_WriteByte (&host_client->netchan.message, (int)state->skinnum);
				for (i=0 ; i<3 ; i++)
				{
					MSG_WriteCoord(&host_client->netchan.message, state->origin[i]);
					MSG_WriteAngle(&host_client->netchan.message, state->angles[i]);
				}
			}
			buf++;
		}
		while (host_client->netchan.message.cursize < (host_client->netchan.message.maxsize/2))	//baselines
		{
			if (buf - bufs - sv.numextrastatics >= sv.num_edicts)
				break;

			ent = EDICT_NUM(svprogfuncs, buf - bufs - sv.numextrastatics);

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

			if (!ent)
			{
				MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline);

				MSG_WriteShort (&host_client->netchan.message, buf - bufs - sv.numextrastatics);

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

				MSG_WriteByte (&host_client->netchan.message, 0);
				MSG_WriteByte (&host_client->netchan.message, 0);
				MSG_WriteByte (&host_client->netchan.message, 0);
				for (i=0 ; i<3 ; i++)
				{
					MSG_WriteCoord(&host_client->netchan.message, 0);
					MSG_WriteAngle(&host_client->netchan.message, 0);
				}
			}
			else if (host_client->fteprotocolextensions & PEXT_SPAWNSTATIC2)
			{
				MSG_WriteByte(&host_client->netchan.message, svcfte_spawnbaseline2);
				SV_WriteDelta(&from, state, &host_client->netchan.message, true, host_client->fteprotocolextensions);
			}
			else if (state->modelindex < 256)
			{
				MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline);

				MSG_WriteShort (&host_client->netchan.message, buf - bufs - sv.numextrastatics);

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

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

			buf++;
		}
		while (host_client->netchan.message.cursize < (host_client->netchan.message.maxsize/2))
		{
			i = buf - bufs - sv.numextrastatics - sv.num_edicts;
			if (i >= 255)
				break;

			ctent = &sv.customtents[i];

			buf++;
			if (!*ctent->particleeffecttype)
			{	//effect isn't registered.
				continue;
			}

			if (host_client->fteprotocolextensions & PEXT_CUSTOMTEMPEFFECTS)
			{
				MSG_WriteByte(&host_client->netchan.message, svcfte_customtempent);
				MSG_WriteByte(&host_client->netchan.message, 255);
				MSG_WriteByte(&host_client->netchan.message, i);
				MSG_WriteByte(&host_client->netchan.message, ctent->netstyle);
				MSG_WriteString(&host_client->netchan.message, ctent->particleeffecttype);
				if (ctent->netstyle & CTE_STAINS)
				{
					MSG_WriteChar(&host_client->netchan.message, ctent->stain[0]);
					MSG_WriteChar(&host_client->netchan.message, ctent->stain[0]);
					MSG_WriteChar(&host_client->netchan.message, ctent->stain[0]);
					MSG_WriteByte(&host_client->netchan.message, ctent->radius);
				}
				if (ctent->netstyle & CTE_GLOWS)
				{
					MSG_WriteByte(&host_client->netchan.message, ctent->dlightrgb[0]);
					MSG_WriteByte(&host_client->netchan.message, ctent->dlightrgb[1]);
					MSG_WriteByte(&host_client->netchan.message, ctent->dlightrgb[2]);
					MSG_WriteByte(&host_client->netchan.message, ctent->dlightradius);
					MSG_WriteByte(&host_client->netchan.message, ctent->dlighttime);
				}
			}
		}
	}
	else if (buf >= bufs)
	{
		buf = bufs+sv.numextrastatics+sv.num_edicts+255;
	}
	else
	{
		if (sv.democausesreconnect)
		{
			if (host_client->netchan.message.cursize+sv.signon_buffer_size[buf]+30 < host_client->netchan.message.maxsize)
			{
				SZ_Write (&host_client->netchan.message,
					sv.demosignon_buffers[buf],
					sv.demosignon_buffer_size[buf]);
				buf++;
			}
		}
		else
		{
			if (host_client->netchan.message.cursize+sv.signon_buffer_size[buf]+30 < host_client->netchan.message.maxsize)
			{
				SZ_Write (&host_client->netchan.message,
					sv.signon_buffers[buf],
					sv.signon_buffer_size[buf]);
					buf++;
			}
		}
	}
	if (buf == bufs+sv.numextrastatics+sv.num_edicts+255)
	{	// all done prespawning
		MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
		MSG_WriteString (&host_client->netchan.message, va("cmd spawn %i\n",svs.spawncount) );
	}
	else
	{	// need to prespawn more
		MSG_WriteByte (&host_client->netchan.message, svc_stufftext);
		MSG_WriteString (&host_client->netchan.message,
			va("cmd prespawn %i %i\n", svs.spawncount, buf) );
	}
}

/*
==================
SV_Spawn_f
==================
*/
void SV_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;
	}

// 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_FullClientUpdateToClient (client, host_client);

// send all current light styles
	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
	{
		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
		{
			if (i >= MAX_STANDARDLIGHTSTYLES)
				if (!sv.strings.lightstyles[i])
					continue;
#ifdef PEXT_LIGHTSTYLECOL
			if ((host_client->fteprotocolextensions & PEXT_LIGHTSTYLECOL) && sv.strings.lightstylecolours[i]!=7)
			{
				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;
			}
			else
			{
				SV_SetUpClientEdict(split, ent);
			}

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

		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, svc_updatestatlong, 6);
	ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
	ClientReliableWrite_Long (host_client, secret_total);

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

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

	ClientReliableWrite_Begin (host_client, svc_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.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 = host_client->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);	//let the server routines know

		ge->ClientBegin(split->q2edict);
		split->istobeloaded = false;
	}
	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.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 (spawnparamglobals[i])
						*spawnparamglobals[i] = split->spawn_parms[i];
				}

				// call the spawn function
				pr_global_struct->time = sv.physicstime;
				pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
				PR_ExecuteProgram (svprogfuncs, SpectatorConnect);
			}
		}
		else
		{
			if (svprogfuncs)
			{
				eval_t *eval, *eval2;
				eval = PR_FindGlobal(svprogfuncs, "ClientReEnter", 0);
				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);
					SV_UnlinkEdict(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 (spawnparamglobals[j])
							*spawnparamglobals[j] = split->spawn_parms[j];
					}
					pr_global_struct->time = sv.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 (spawnparamglobals[i])
							*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.physicstime;
						pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
						G_FLOAT(OFS_PARM0) = split->csqcactive;	//this arg is part of EXT_CSQC_1, but doesn't have to be supported by the mod
						PR_ExecuteProgram (svprogfuncs, pr_global_struct->ClientConnect);
				
						// actually spawn the player
						pr_global_struct->time = sv.physicstime;
						pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, split->edict);
						PR_ExecuteProgram (svprogfuncs, pr_global_struct->PutClientInServer);
					}

					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;


	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);
		}
		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;
	qboolean 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 = true;
	}

	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)
	{
		//would overflow the packet.
		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;

		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);
	vsprintf (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");

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

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

	if (!host_client->upload)
	{
		host_client->upload = FS_OpenVFS(host_client->uploadfn, "wb", FS_GAME);
		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");
			*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);
		}
	}

}

//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_pk3s;
	extern	cvar_t	allow_download_wads;
	extern	cvar_t	allow_download_root;
	extern	cvar_t	allow_download_configs;

	//allowed at all?
	if (!allow_download.value)
		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,	"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;

	//pk3s.
	if (!strcmp("pk3", COM_FileExtension(name)) || !strcmp("pak", COM_FileExtension(name)))
		if (strnicmp(name, "pak", 3))	//don't give out q3 pk3 files.
			return !!allow_download_pk3s.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(*p);
	}

	if (!SV_AllowDownload(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);

	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))
				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)
				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:
		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:
		name = va("dlsize \"%s\" e\n", name);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
		ClientReliableWrite_String (host_client, name);
		break;
	case -2:
		name = va("dlsize \"%s\" p\n", name);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
		ClientReliableWrite_String (host_client, name);
		break;
	case 0:
		name = va("dlsize \"%s\" %u\n", name, loc.len);
		ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(name));
		ClientReliableWrite_String (host_client, name);
		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;
	}

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

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

	//redirection protocol-specific code goes here.
	if (result == -4)
	{
	}

	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 = "";
			break;
		}
		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");
		}
#ifdef PEXT_CHUNKEDDOWNLOADS
		else 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
		{
			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);
		}

		//it errored because it was a redirection.
		//ask the client to grab the alternate file instead.
		if (redirection)
		{
			//tell the client to download the new one.
			ClientReliableWrite_Begin (host_client, ISQ2CLIENT(host_client)?svcq2_stufftext:svc_stufftext, 2+strlen(redirection)); 
			ClientReliableWrite_String (host_client, va("\ndownload \"%s\"\n", redirection));
		}
		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)
				sprintf (text, "[SPEC] {%s}:", host_client->name);
			else
				continue;
		}
		else
			sprintf (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;

	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))
		sprintf (text, "[SPEC] %s: ", host_client->name);
	else if (team)
		sprintf (text, "(%s): ", host_client->name);
	else
		sprintf (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))
	{
		MVDWrite_Begin (dem_all, 0, strlen(text)+3);
	}
	else
		MVDWrite_Begin (dem_multiple, cls, strlen(text)+3);

	MSG_WriteByte ((sizebuf_t*)demo.dbuf, svc_print);
	MSG_WriteByte ((sizebuf_t*)demo.dbuf, PRINT_CHAT);
	MSG_WriteString ((sizebuf_t*)demo.dbuf, 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;

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

	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), 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));
			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.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.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 i;
	client_t *cl;
	int newv;

	newv = sv.paused^1;

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

	sv.paused = newv;

	sv.pausedstart = Sys_DoubleTime();

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

	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)
			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)
{
	SV_EndRedirect ();
	if (!host_client->spectator)
		SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTDROPPED, host_client->name);
	SV_DropClient (host_client);
}

/*
=================
SV_PTrack_f

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

	if (!host_client->spectator && !sv.demofile)
		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));
	if (*sv.recordedplayer[i].userinfo)
	{
		host_client->spec_track = i+1;
		return;
	}

	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)
	{
		SV_ClientPrintf (host_client, PRINT_HIGH, "Effective rate %i\n", SV_RateForClient(host_client));
		return;
	}

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

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

	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_Char(client, i);
					ClientReliableWrite_String(client, key);
					ClientReliableWrite_String(client, val);
				}
			}
		}

		if (sv.mvdrecording)
		{
			MVDWrite_Begin (dem_all, 0, strlen(key)+strlen(val)+4);
			MSG_WriteByte ((sizebuf_t*)demo.dbuf, svc_setinfo);
			MSG_WriteByte ((sizebuf_t*)demo.dbuf, i);
			MSG_WriteString ((sizebuf_t*)demo.dbuf, key);
			MSG_WriteString ((sizebuf_t*)demo.dbuf, 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);
	}
}

extern qboolean	sv_allow_cheats;

void Cmd_Notarget_f (void)
{
	if (!sv_allow_cheats)
	{
		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_allow_cheats)
	{
		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_allow_cheats)
	{
		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;
		}
	}
	else if (developer.value || host_client->netchan.remote_address.type == NA_LOOPBACK)	//we don't want clients doing nasty things... like setting movetype 3123
	{
		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_allow_cheats)
	{
		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_allow_cheats)
	{
		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_allow_cheats)
	{
		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));
	SV_LinkEdict (sv_player, false);
}

void ED_ClearEdict (progfuncs_t *progfuncs, edict_t *e);
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_ClearEdict(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;
}
/*
==================
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);

	host_client->old_frags = 0;
	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
	PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetNewParms);
	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
	{
		if (spawnparamglobals[i])
			host_client->spawn_parms[i] = *spawnparamglobals[i];
		else
			host_client->spawn_parms[i] = 0;
	}

	// call the spawn function
	pr_global_struct->time = sv.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.physicstime;
	pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, sv_player);
	PR_ExecuteProgram (svprogfuncs, pr_global_struct->PutClientInServer);

	// send notification to all clients
	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);

	host_client->old_frags = 0;
	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
	PR_ExecuteProgram (svprogfuncs, pr_global_struct->SetNewParms);
	for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
	{
		if (spawnparamglobals[i])
			host_client->spawn_parms[i] = *spawnparamglobals[i];
		else
			host_client->spawn_parms[i] = 0;
	}

	SV_SpawnSpectator ();

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

	// send notification to all clients
	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;
	int 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 (cl->protocol != SCP_QUAKEWORLD)
			continue;

		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: %ffps (min%f max %f), in: %fbps, out: %fbps\n", cl->name, ftime/frames, minf, maxf, (1000.0f*inbytes)/msecs, (1000.0f*outbytes)/msecs);
		else
			SV_ClientPrintf(host_client, PRINT_HIGH, "%s: no information available\n", cl->name);
	}
}

void SV_EnableClientsCSQC(void)
{
#ifdef PEXT_CSQC
	if (host_client->fteprotocolextensions & PEXT_CSQC || atoi(Cmd_Argv(1)))
		host_client->csqcactive = true;
	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_MVDList_f (void);
void SV_MVDInfo_f (void);
typedef struct
{
	char	*name;
	void	(*func) (void);
	qboolean	noqchandling;
} ucmd_t;

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

	{"join", Cmd_Join_f},
	{"observe", Cmd_Observe_f},

	{"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},

	{"dlsize", SV_DownloadSize_f},
	{"download", SV_BeginDownload_f},
	{"nextdl", SV_NextDownload_f},

	{"ptrack", SV_PTrack_f}, //ZOID - used with autocam
	{"enablecsqc", SV_EnableClientsCSQC},
	{"disablecsqc", SV_DisableClientsCSQC},

	{"snap", SV_NoSnap_f},
	{"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},

	{"stopdownload", SV_StopDownload_f},
	{"demolist", SV_MVDList_f},
	{"demoinfo", SV_MVDInfo_f},

	{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

extern ucmd_t nqucmds[];
/*
==================
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 ();
}
#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_FullClientUpdateToClient (client, 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
	{
		memset (ent->v, 0, pr_edict_size);
		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, svc_updatestat, 6);
	ClientReliableWrite_Byte (host_client, STAT_TOTALSECRETS);
	ClientReliableWrite_Long (host_client, pr_global_struct->total_secrets);

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

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

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



	SZ_Write (&host_client->netchan.message, sv.signon.data, sv.signon.cursize);
	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 (spawnparamglobals[i])
						*spawnparamglobals[i] = host_client->spawn_parms[i];
				}

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

			// call the spawn function
			pr_global_struct->time = sv.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.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_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);
		}
		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 );
	}



	SZ_Write (&host_client->netchan.message, sv.signon.data, sv.signon.cursize);
//	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)
{
	edict_t *ent;
	entity_state_t *state;
	int i, e;
	if (host_client->state != cs_connected)
	{
		Con_Printf ("prespawn not valid -- already spawned\n");
		return;
	}

	for (e = 1; e < sv.num_edicts; e++)
	{
		ent = EDICT_NUM(svprogfuncs, e);
		state = &ent->baseline;

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

		if (!ent)
		{
			MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline);

			MSG_WriteShort (&host_client->netchan.message, e);

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

			MSG_WriteByte (&host_client->netchan.message, 0);
			MSG_WriteByte (&host_client->netchan.message, 0);
			MSG_WriteByte (&host_client->netchan.message, 0);
			for (i=0 ; i<3 ; i++)
			{
				MSG_WriteCoord(&host_client->netchan.message, 0);
				MSG_WriteAngle(&host_client->netchan.message, 0);
			}
		}
		else
		{
			if (host_client->protocol != SCP_NETQUAKE && (state->modelindex > 255 || state->frame > 255))
			{
				MSG_WriteByte(&host_client->netchan.message, svcdp_spawnbaseline2);

				MSG_WriteShort (&host_client->netchan.message, e);

				MSG_WriteShort (&host_client->netchan.message, state->modelindex);
				MSG_WriteShort (&host_client->netchan.message, state->frame);
			}
			else
			{
				MSG_WriteByte(&host_client->netchan.message, svc_spawnbaseline);

				MSG_WriteShort (&host_client->netchan.message, e);

				MSG_WriteByte (&host_client->netchan.message, state->modelindex&255);
				MSG_WriteByte (&host_client->netchan.message, state->frame&255);
			}

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

	for (i = 0; i < sv.num_signon_buffers; i++)
		SZ_Write (&host_client->netchan.message, sv.signon_buffers[i], sv.signon_buffer_size[i]);

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


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

	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), cl->name));
	}
}

ucmd_t nqucmds[] =
{
	{"new",			SVNQ_New_f, true},

	{"status",		NULL},

	{"god",			Cmd_God_f},
	{"give",		Cmd_Give_f},
	{"notarget",	Cmd_Notarget_f},
	{"fly",			NULL},
	{"noclip",		Cmd_Noclip_f},
	{"pings",		SV_Pings_f},


	{"name",		SVNQ_NQInfo_f},
	{"say",			SV_Say_f},
	{"say_team",	SV_Say_Team_f},
	{"tell",		SV_SayOne_f},
	{"color",		SVNQ_NQColour_f},
	{"kill",		SV_Kill_f},
	{"pause",		SV_Pause_f},
	{"spawn",		SVNQ_Spawn_f, true},
	{"begin",		SVNQ_Begin_f, true},
	{"prespawn",	SVNQ_PreSpawn_f, true},
	{"kick",		NULL},
	{"ping",		SVNQ_Ping_f},
	{"ban",			NULL},
	{"vote",		SV_Vote_f},

	{"download",	SV_BeginDownload_f},
	{"sv_startdownload",	SVDP_StartDownload_f},

	{"setinfo", SV_SetInfo_f},
	{"playermodel",	NULL},
	{"playerskin",	NULL},
	{"rate",		SV_Rate_f},

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

	{NULL, NULL}
};
/*
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


int implevels[256];
qboolean SV_FiltureImpulse(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->solid_edicts.next ; l != &node->solid_edicts ; l = next)
	{
		next = l->next;
		check = EDICT_FROM_AREA(l);

		if (check->v->owner == pl)
			continue;		// player's own missile
		if (check->v->solid == SOLID_BSP
			|| check->v->solid == SOLID_BBOX
			|| check->v->solid == SOLID_SLIDEBOX)
		{
			if (check == 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;

			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;
			pmove.numphysent++;

			VectorCopy (check->v->origin, pe->origin);
			pe->info = NUM_FOR_EDICT(svprogfuncs, check);
			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->trigger_edicts.next ; l != &node->trigger_edicts ; l = next)
	{
		next = l->next;
		check = 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)
	// test the point
			if (model->funcs.PointContents (model, 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.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)
{
	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;
	}

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

	if (cl->edict->v->movetype == MOVETYPE_NOCLIP)
	{
		if (cl->zquake_extensions & Z_EXT_PM_TYPE_NEW)
			return PM_SPECTATOR;
		return PM_OLD_SPECTATOR;
	}

	if (cl->edict->v->movetype == MOVETYPE_FLY)
		return PM_FLY;

	if (cl->edict->v->movetype == MOVETYPE_NONE)
		return PM_NONE;

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

	return PM_NORMAL;
}


//called for common csqc/server code (supposedly)
void SV_RunEntity (edict_t *ent);

/*
===========
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];

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

	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.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;
	}

#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.value)	//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_FiltureImpulse(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;
	}

	SV_CheckVelocity(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)
	{	//csqc independant physics support
		pr_global_struct->frametime = host_frametime;
		pr_global_struct->time = sv.time;
		SV_RunEntity(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)
		{
#define FL_JUMPRELEASED 4096
			jumpable = (int)sv_player->v->flags & FL_JUMPRELEASED;

			pmove.waterjumptime = sv_player->v->teleport_time;
			if (pmove.waterjumptime > sv.time)
				sv_player->v->flags = (int)sv_player->v->flags | FL_WATERJUMP;
			sv_player->v->teleport_time = sv.time + pmove.waterjumptime;
		}
		else
			jumpable = false;
		
#ifdef VM_Q1
		if (svs.gametype == GT_Q1QVM)
			Q1QVM_PlayerPreThink();
		else
#endif
			PR_ExecuteProgram (svprogfuncs, pr_global_struct->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;
		}

		SV_RunThink (sv_player);
	}

//	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];

	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.worldmodel;
	pmove.cmd = *ucmd;
	pmove.hullnum = SV_HullNumForPlayer(sv_player->xv->hull, sv_player->v->mins, sv_player->v->maxs);

	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_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.
		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;

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

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

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

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

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

	if (!host_client->spectator)
	{
		// link into place and touch triggers
		SV_LinkEdict (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 (!ent->v->touch || (playertouch[n/8]&(1<<(n%8))))
				continue;

			pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, ent);
			pr_global_struct->other = EDICT_TO_PROG(svprogfuncs, sv_player);
			pr_global_struct->time = sv.time;
#ifdef VM_Q1
			if (svs.gametype == GT_Q1QVM)
				Q1QVM_Touch();
			else
#endif
				PR_ExecuteProgram (svprogfuncs, ent->v->touch);
			playertouch[n/8] |= 1 << (n%8);
		}
	}
}

/*
===========
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);

			PR_ExecuteProgram (svprogfuncs, pr_global_struct->PlayerPostThink);

			SV_RunNewmis ();
		}
		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_start", NULL);
		cursor_impact	= svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_impact", NULL);
		cursor_entitynumber	= svprogfuncs->GetEdictFieldValue(svprogfuncs, host_client->edict, "cursor_entitynumber", 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 = (unsigned short)MSG_ReadShort();
	if (entnum >= sv.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[9];
	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))
			if (MSG_ReadByte() != ev_void)
			{
				msg_badread = true;
				return;
			}
		switch(MSG_ReadByte())
		{
		case ev_void:
			i = 6;
			break;
		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] = 's';
			e = MSG_ReadShort();
			if (e < 0 || e >= sv.num_edicts)
				e = 0;
			G_INT(OFS_PARM0+i*3) = EDICT_TO_PROG(svprogfuncs, EDICT_NUM(svprogfuncs, e));
			break;
		}
	}

	rname = MSG_ReadString();
	f = PR_FindFunc(svprogfuncs, va("Cmd_%s_%s", rname, args), PR_ANY);
	if (f)
		PR_ExecuteProgram(svprogfuncs, f);
}

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

The current net_message is parsed for the given client
===================
*/
void SV_ClientThink (void);
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_acknoledged + UPDATE_BACKUP > cl->netchan.incoming_acknowledged)
		{
			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;
				}
			}
		}
#ifdef PEXT_CSQC
		if (cl->lastsequence_acknoledged + UPDATE_BACKUP > cl->netchan.incoming_acknowledged)
		{
			for (i = cl->lastsequence_acknoledged+1; i < cl->netchan.incoming_acknowledged; i++)
				SV_CSQC_DroppedPacket(cl, i);
		}
		else
			SV_CSQC_DropAll(cl);
#endif
		cl->lastsequence_acknoledged = cl->netchan.incoming_acknowledged;
	}
	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 ("SV_ReadClientMessage: badread\n");
			SV_DropClient (cl);
			return;
		}

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

		switch (c)
		{
		default:
			Con_Printf ("SV_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_nomsec.value)
						{
							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.value)	//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_ReadClientMessage: 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)
							SV_PostRunCmd();

					}

					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
			if (sv.mvdplayback)
			{
				VectorCopy(o, host_client->specorigin);
			}
			else if (host_client->spectator)
			{
				VectorCopy(o, sv_player->v->origin);
				SV_LinkEdict(sv_player, false);
			}
			break;

		case clc_upload:
			SV_NextUpload();
			break;

		}
	}

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

	// 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;
//	cl->q2frames[cl->netchan.outgoing_sequence & UPDATE_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 ("SV_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);	//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;
		}
	}
}
#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->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;


// read current angles
	for (i=0 ; i<3 ; i++)
	{
		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=timesincelast*1000;//MSG_ReadFloat;

// read buttons
	if (host_client->protocol == SCP_DARKPLACES6 || host_client->protocol == SCP_DARKPLACES7)
		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)
	{
		SV_ReadPrydonCursor();
	}

	if (SV_RunFullQCMovement(host_client, move))
	{
		pr_global_struct->time = sv.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_FiltureImpulse(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.value)	//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;
		SV_RunEntity(host_client->edict);
		host_client->isindependant = true;
	}
	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;

	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 ("SV_ReadClientMessage: badread\n");
			SV_DropClient (cl);
			return;
		}

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

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

		case clc_disconnect:
			SV_DropClient (cl);
			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 50:
			MSG_ReadLong();
			break;
		case clcdp_ackdownloaddata:
			SV_DarkPlacesDownloadAck(host_client);
			break;
		}
	}
}
#endif
/*
==============
SV_UserInit
==============
*/
void SV_UserInit (void)
{
#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_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_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

==================
*/
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 = SV_Move (start, vec3_origin, vec3_origin, stop, true, 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;
}

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];
}

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

===================
*/
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);
	}
}

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];
}

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)
	{
		pr_global_struct->time = sv.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
