/*
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_main.c -- server main program

#include "quakedef.h"

#ifndef CLIENTONLY

#define CHAN_AUTO   0
#define CHAN_WEAPON 1
#define CHAN_VOICE  2
#define CHAN_ITEM   3
#define CHAN_BODY   4

extern cvar_t sv_gravity, sv_friction, sv_waterfriction, sv_gamespeed, sv_stopspeed, sv_spectatormaxspeed, sv_accelerate, sv_airaccelerate, sv_wateraccelerate, sv_edgefriction;
extern cvar_t  dpcompat_stats;

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

Con_Printf redirection

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

char	outputbuf[8000];

redirect_t	sv_redirected;
int sv_redirectedlang;

extern cvar_t sv_phs;

/*
==================
SV_FlushRedirect
==================
*/
void SV_FlushRedirect (void)
{
	int totallen;
	char	send[8000+6];

	if (!*outputbuf)
		return;

	if (sv_redirected == RD_PACKET)
	{
		send[0] = 0xff;
		send[1] = 0xff;
		send[2] = 0xff;
		send[3] = 0xff;
		send[4] = A2C_PRINT;
		memcpy (send+5, outputbuf, strlen(outputbuf)+1);

		NET_SendPacket (NS_SERVER, strlen(send)+1, send, net_from);
	}
	else if (sv_redirected == RD_CLIENT)
	{
		int chop;
		char spare;
		char *s = outputbuf;
		totallen = strlen(s)+3;
		while (sizeof(host_client->backbuf_data[0])/2 < totallen)
		{
			chop = sizeof(host_client->backbuf_data[0]) / 2;
			spare = s[chop];
			s[chop] = '\0';

			ClientReliableWrite_Begin (host_client, host_client->protocol==SCP_QUAKE2?svcq2_print:svc_print, chop+3);
			ClientReliableWrite_Byte (host_client, PRINT_HIGH);
			ClientReliableWrite_String (host_client, s);

			s += chop;
			totallen -= chop;
			s[0] = spare;
		}
		ClientReliableWrite_Begin (host_client, host_client->protocol==SCP_QUAKE2?svcq2_print:svc_print, strlen(s)+3);
		ClientReliableWrite_Byte (host_client, PRINT_HIGH);
		ClientReliableWrite_String (host_client, s);
	}

	// clear it
	outputbuf[0] = 0;
}


/*
==================
SV_BeginRedirect

  Send Con_Printf data to the remote client
  instead of the console
==================
*/
void SV_BeginRedirect (redirect_t rd, int lang)
{
	sv_redirected = rd;
	sv_redirectedlang = lang;
	outputbuf[0] = 0;
}

void SV_EndRedirect (void)
{
	SV_FlushRedirect ();
	sv_redirectedlang = 0;	//clenliness rather than functionality. Shouldn't be needed.
	sv_redirected = RD_NONE;
}


/*
================
Con_Printf

Handles cursor positioning, line wrapping, etc
================
*/
#define	MAXPRINTMSG	4096
// FIXME: make a buffer size safe vsprintf?
#ifdef SERVERONLY
void VARGS Con_Printf (const char *fmt, ...)
{
	va_list		argptr;
	char		msg[MAXPRINTMSG];

	va_start (argptr,fmt);
	vsnprintf (msg,sizeof(msg)-1, fmt,argptr);
	va_end (argptr);

	// add to redirected message
	if (sv_redirected)
	{
		if (strlen (msg) + strlen(outputbuf) > sizeof(outputbuf) - 1)
			SV_FlushRedirect ();
		strcat (outputbuf, msg);
		if (sv_redirected != -1)
			return;
	}

	Sys_Printf ("%s", msg);	// also echo to debugging console
	Con_Log(msg); // log to console
}
void Con_TPrintf (translation_t stringnum, ...)
{
	va_list		argptr;
	char		msg[MAXPRINTMSG];
	char *fmt;

	// add to redirected message
	if (sv_redirected)
	{
		fmt = languagetext[stringnum][sv_redirectedlang];
		va_start (argptr,stringnum);
		vsnprintf (msg,sizeof(msg)-1, fmt,argptr);
		va_end (argptr);

		if (strlen (msg) + strlen(outputbuf) > sizeof(outputbuf) - 1)
			SV_FlushRedirect ();
		strcat (outputbuf, msg);
		return;
	}

	fmt = languagetext[stringnum][svs.language];

	va_start (argptr,stringnum);
	vsnprintf (msg,sizeof(msg)-1, fmt,argptr);
	va_end (argptr);

	Sys_Printf ("%s", msg);	// also echo to debugging console
	Con_Log(msg); // log to console
}
/*
================
Con_DPrintf

A Con_Printf that only shows up if the "developer" cvar is set
================
*/
void Con_DPrintf (char *fmt, ...)
{
	va_list		argptr;
	char		msg[MAXPRINTMSG];
	extern cvar_t log_developer;

	if (!developer.value && !log_developer.value)
		return;

	va_start (argptr,fmt);
	vsnprintf (msg,sizeof(msg)-1, fmt,argptr);
	va_end (argptr);

	// add to redirected message
	if (sv_redirected)
	{
		if (strlen (msg) + strlen(outputbuf) > sizeof(outputbuf) - 1)
			SV_FlushRedirect ();
		strcat (outputbuf, msg);
		if (sv_redirected != -1)
			return;
	}

	if (developer.value)
		Sys_Printf ("%s", msg);	// also echo to debugging console

	if (log_developer.value)
		Con_Log(msg); // log to console
}
#endif

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

EVENT MESSAGES

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

void SV_PrintToClient(client_t *cl, int level, char *string)
{
	switch (cl->protocol)
	{
	case SCP_BAD:	//bot
		break;
	case SCP_QUAKE2:
#ifdef Q2SERVER
		ClientReliableWrite_Begin (cl, svcq2_print, strlen(string)+3);
		ClientReliableWrite_Byte (cl, level);
		ClientReliableWrite_String (cl, string);
#endif
		break;
	case SCP_QUAKE3:
		break;
	case SCP_QUAKEWORLD:
		ClientReliableWrite_Begin (cl, svc_print, strlen(string)+3);
		ClientReliableWrite_Byte (cl, level);
		ClientReliableWrite_String (cl, string);
		break;
	case SCP_DARKPLACES6:
	case SCP_DARKPLACES7:
	case SCP_NETQUAKE:
	case SCP_PROQUAKE:
	case SCP_FITZ666:
#ifdef NQPROT
		ClientReliableWrite_Begin (cl, svc_print, strlen(string)+3);
		if (level == PRINT_CHAT)
			ClientReliableWrite_Byte (cl, 1);
		ClientReliableWrite_String (cl, string);
#endif
		break;
	}
}

void SV_StuffcmdToClient(client_t *cl, char *string)
{
	switch (cl->protocol)
	{
	case SCP_BAD:	//bot
		break;
	case SCP_QUAKE2:
#ifdef Q2SERVER
		ClientReliableWrite_Begin (cl, svcq2_stufftext, strlen(string)+3);
		ClientReliableWrite_String (cl, string);
#endif
		break;
	case SCP_QUAKE3:
		break;
	case SCP_QUAKEWORLD:
	case SCP_DARKPLACES6:
	case SCP_DARKPLACES7:
	case SCP_NETQUAKE:
	case SCP_PROQUAKE:
	case SCP_FITZ666:
		ClientReliableWrite_Begin (cl, svc_stufftext, strlen(string)+3);
		ClientReliableWrite_String (cl, string);
		break;
	}
}


/*
=================
SV_ClientPrintf

Sends text across to be displayed if the level passes
=================
*/
void VARGS SV_ClientPrintf (client_t *cl, int level, char *fmt, ...)
{
	va_list		argptr;
	char		string[1024];

	if (level < cl->messagelevel)
		return;

	va_start (argptr,fmt);
	vsnprintf (string,sizeof(string)-1, fmt,argptr);
	va_end (argptr);

	if(strlen(string) >= sizeof(string))
		Sys_Error("SV_ClientPrintf: Buffer stomped\n");

	if (sv.mvdrecording)
	{
		sizebuf_t *msg = MVDWrite_Begin (dem_single, cl - svs.clients, strlen(string)+3);
		MSG_WriteByte (msg, svc_print);
		MSG_WriteByte (msg, level);
		MSG_WriteString (msg, string);
	}

	if (cl->controller)
		SV_PrintToClient(cl->controller, level, string);
	else
		SV_PrintToClient(cl, level, string);
}

void VARGS SV_ClientTPrintf (client_t *cl, int level, translation_t stringnum, ...)
{
	va_list		argptr;
	char		string[1024];
	char *fmt = languagetext[stringnum][cl->language];

	if (level < cl->messagelevel)
		return;

	va_start (argptr,stringnum);
	vsnprintf (string,sizeof(string)-1, fmt,argptr);
	va_end (argptr);

	if(strlen(string) >= sizeof(string))
		Sys_Error("SV_ClientTPrintf: Buffer stomped\n");

	if (sv.mvdrecording)
	{
		sizebuf_t *msg = MVDWrite_Begin (dem_single, cl - svs.clients, strlen(string)+3);
		MSG_WriteByte (msg, svc_print);
		MSG_WriteByte (msg, level);
		MSG_WriteString (msg, string);
	}

	SV_PrintToClient(cl, level, string);
}

/*
=================
SV_BroadcastPrintf

Sends text to all active clients
=================
*/
void VARGS SV_BroadcastPrintf (int level, char *fmt, ...)
{
	va_list		argptr;
	char		string[1024];
	client_t	*cl;
	int			i;

	va_start (argptr,fmt);
	vsnprintf (string,sizeof(string)-1, fmt,argptr);
	va_end (argptr);

	if(strlen(string) >= sizeof(string))
		Sys_Error("SV_BroadcastPrintf: Buffer stomped\n");

	Sys_Printf ("%s", string);	// print to the console

	for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
	{
		if (level < cl->messagelevel)
			continue;
		if (!cl->state)
			continue;
		if (cl->protocol == SCP_BAD)
			continue;

		if (cl->controller)
			continue;

		SV_PrintToClient(cl, level, string);
	}

	if (sv.mvdrecording)
	{
		sizebuf_t *msg = MVDWrite_Begin (dem_all, 0, strlen(string)+3);
		MSG_WriteByte (msg, svc_print);
		MSG_WriteByte (msg, level);
		MSG_WriteString (msg, string);
	}
}


void VARGS SV_BroadcastTPrintf (int level, translation_t stringnum, ...)
{
	va_list		argptr;
	char		string[1024];
	client_t	*cl;
	int			i;
	int oldlang=-1;
	char *fmt = languagetext[stringnum][oldlang=svs.language];

	va_start (argptr,stringnum);
	vsnprintf (string,sizeof(string)-1, fmt,argptr);
	va_end (argptr);

	if(strlen(string) >= sizeof(string))
		Sys_Error("SV_BroadcastPrintf: Buffer stomped\n");

	Sys_Printf ("%s", string);	// print to the console

	for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
	{
		if (level < cl->messagelevel)
			continue;
		if (!cl->state)
			continue;
		if (cl->controller)
			continue;

		if (oldlang!=cl->language)
		{
			fmt = languagetext[stringnum][oldlang=cl->language];

			va_start (argptr,stringnum);
			vsnprintf (string,sizeof(string)-1, fmt,argptr);
			va_end (argptr);

			if(strlen(string) >= sizeof(string))
				Sys_Error("SV_BroadcastPrintf: Buffer stomped\n");
		}

		SV_PrintToClient(cl, level, string);
	}
}


/*
=================
SV_BroadcastCommand

Sends text to all active clients
=================
*/
void VARGS SV_BroadcastCommand (char *fmt, ...)
{
	va_list		argptr;
	char		string[1024];
	int i;
	client_t *cl;

	if (!sv.state)
		return;
	va_start (argptr,fmt);
	vsnprintf (string,sizeof(string), fmt,argptr);
	va_end (argptr);

	for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
	{
		if (cl->controller)
			continue;
		if (cl->state>=cs_connected)
		{
			if (ISQWCLIENT(cl) || ISNQCLIENT(cl))
			{
				ClientReliableWrite_Begin(cl, svc_stufftext, strlen(string)+2);
				ClientReliableWrite_String (cl, string);
			}
			else if (ISQ2CLIENT(cl))
			{
				ClientReliableWrite_Begin(cl, svcq2_stufftext, strlen(string)+2);
				ClientReliableWrite_String (cl, string);
			}
		}
	}
}


/*
=================
SV_Multicast

Sends the contents of sv.multicast to a subset of the clients,
then clears sv.multicast.

MULTICAST_ALL	same as broadcast
MULTICAST_PVS	send to clients potentially visible from org
MULTICAST_PHS	send to clients potentially hearable from org

MULTICAST_ONE	sent to a single client.
MULTICAST_INIT	sent to clients when they first connect. for completeness.
=================
*/
void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int with, int without)
{
	client_t	*client;
	qbyte		*mask;
	int			leafnum;
	int			j;
	qboolean	reliable;
	int pnum = 0;

	if (to == MULTICAST_INIT)
	{
		//we only have one signon buffer. make sure you don't put non-identical protocols in the buffer
		SV_FlushSignon();
		SZ_Write (&sv.signon, sv.multicast.data, sv.multicast.cursize);

		//and send to players that are already on
		to = MULTICAST_ALL_R;
	}

//	to = MULTICAST_ALL;
#ifdef Q2BSPS
	if (sv.world.worldmodel->fromgame == fg_quake2 || sv.world.worldmodel->fromgame == fg_quake3)
	{
		int			area1, area2, cluster;

		reliable = false;

		if (to != MULTICAST_ALL_R && to != MULTICAST_ALL)
		{
			leafnum = CM_PointLeafnum (sv.world.worldmodel, origin);
			area1 = CM_LeafArea (sv.world.worldmodel, leafnum);
		}
		else
		{
			leafnum = 0;	// just to avoid compiler warnings
			area1 = 0;
		}

		switch (to)
		{
		case MULTICAST_ALL_R:
			reliable = true;	// intentional fallthrough
		case MULTICAST_ALL:
			leafnum = 0;
			mask = NULL;
			break;

		case MULTICAST_PHS_R:
			reliable = true;	// intentional fallthrough
		case MULTICAST_PHS:
			leafnum = CM_PointLeafnum (sv.world.worldmodel, origin);
			cluster = CM_LeafCluster (sv.world.worldmodel, leafnum);
			mask = CM_ClusterPHS (sv.world.worldmodel, cluster);
			break;

		case MULTICAST_PVS_R:
			reliable = true;	// intentional fallthrough
		case MULTICAST_PVS:
			leafnum = CM_PointLeafnum (sv.world.worldmodel, origin);
			cluster = CM_LeafCluster (sv.world.worldmodel, leafnum);
			mask = CM_ClusterPVS (sv.world.worldmodel, cluster, NULL, 0);
			break;

		default:
			mask = NULL;
			SV_Error ("SV_Multicast: bad to:%i", to);
		}

		// send the data to all relevent clients
		for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
		{
			if (client->state != cs_spawned)
				continue;

			if (client->protocol == SCP_QUAKEWORLD)
			{
				if (client->fteprotocolextensions & without)
				{
		//			Con_Printf ("Version supressed multicast - without pext\n");
					continue;
				}
				if (!(~client->fteprotocolextensions & ~with))
				{
		//			Con_Printf ("Version supressed multicast - with pext\n");
					continue;
				}
			}

			if (mask)
			{
#ifdef Q2SERVER
				if (ge)
					leafnum = CM_PointLeafnum (sv.world.worldmodel, client->q2edict->s.origin);
				else
#endif
				{
					if (svprogfuncs)
					{
						if (!((int)client->edict->xv->dimension_see & dimension_mask))
							continue;
					}
					leafnum = CM_PointLeafnum (sv.world.worldmodel, client->edict->v->origin);
				}
				cluster = CM_LeafCluster (sv.world.worldmodel, leafnum);
				area2 = CM_LeafArea (sv.world.worldmodel, leafnum);
				if (!CM_AreasConnected (sv.world.worldmodel, area1, area2))
					continue;
				if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
					continue;
			}

			switch (client->protocol)
			{
			case SCP_BAD:
				continue;	//a bot.

			default:
				SV_Error("Multicast: Client is using a bad protocl");

			case SCP_QUAKE3:
				Con_Printf("Skipping multicast for q3 client\n");
				break;
#ifdef NQPROT
			case SCP_NETQUAKE:
			case SCP_PROQUAKE:
			case SCP_FITZ666:
			case SCP_DARKPLACES6:
			case SCP_DARKPLACES7:
				if (reliable)
				{
					ClientReliableCheckBlock(client, sv.nqmulticast.cursize);
					ClientReliableWrite_SZ(client, sv.nqmulticast.data, sv.nqmulticast.cursize);
				}
				else
					SZ_Write (&client->datagram, sv.nqmulticast.data, sv.nqmulticast.cursize);
				break;
#endif
#ifdef Q2SERVER
			case SCP_QUAKE2:
				if (reliable)
				{
					ClientReliableCheckBlock(client, sv.q2multicast.cursize);
					ClientReliableWrite_SZ(client, sv.q2multicast.data, sv.q2multicast.cursize);
				}
				else
					SZ_Write (&client->datagram, sv.q2multicast.data, sv.q2multicast.cursize);
				break;
#endif
			case SCP_QUAKEWORLD:
			    if (reliable)
				{
					ClientReliableCheckBlock(client, sv.multicast.cursize);
					ClientReliableWrite_SZ(client, sv.multicast.data, sv.multicast.cursize);
				}
				else
					SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize);
				break;
			}
		}
	}
	else
#endif
	{
		reliable = false;

		switch (to)
		{
		case MULTICAST_ALL_R:
			reliable = true;	// intentional fallthrough
		case MULTICAST_ALL:
			mask = sv.pvs;		// leaf 0 is everything;
			break;

		case MULTICAST_PHS_R:
			reliable = true;	// intentional fallthrough
		case MULTICAST_PHS:
			if (!sv.phs)	/*broadcast if no pvs*/
				mask = sv.pvs;
			else
			{
				leafnum = sv.world.worldmodel->funcs.LeafnumForPoint(sv.world.worldmodel, origin);
				mask = sv.phs + leafnum * 4*((sv.world.worldmodel->numleafs+31)>>5);
			}
			break;

		case MULTICAST_PVS_R:
			reliable = true;	// intentional fallthrough
		case MULTICAST_PVS:
			leafnum = sv.world.worldmodel->funcs.LeafnumForPoint(sv.world.worldmodel, origin);
			mask = sv.pvs + leafnum * 4*((sv.world.worldmodel->numleafs+31)>>5);
			break;

		case MULTICAST_ONE_R:
			reliable = true;
		case MULTICAST_ONE:
			if (svprogfuncs)
			{
				edict_t *ent = PROG_TO_EDICT(svprogfuncs, pr_global_struct->msg_entity);
				pnum = NUM_FOR_EDICT(svprogfuncs, ent) - 1;
			}
			mask = NULL;
			break;

		default:
			mask = NULL;
			SV_Error ("SV_Multicast: bad to:%i", to);
		}

		// send the data to all relevent clients
		for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
		{
			if (client->state != cs_spawned)
				continue;

			if (client->controller)
				continue;	//FIXME: send if at least one of the players is near enough.

			if (client->protocol == SCP_QUAKEWORLD)
			{
				if (client->fteprotocolextensions & without)
				{
		//			Con_Printf ("Version supressed multicast - without pext\n");
					continue;
				}
				if (!(client->fteprotocolextensions & with) && with)
				{
		//			Con_Printf ("Version supressed multicast - with pext\n");
					continue;
				}
			}

			if (!mask)
			{
				if (pnum != j)
					continue;
			}
			else if (svprogfuncs)
			{
				if (!((int)client->edict->xv->dimension_see & dimension_mask))
					continue;

				if (to == MULTICAST_PHS_R || to == MULTICAST_PHS)
				{
					vec3_t delta;
					VectorSubtract(origin, client->edict->v->origin, delta);
					if (Length(delta) <= 1024)
						goto inrange;
				}

				// -1 is because pvs rows are 1 based, not 0 based like leafs
				if (mask != sv.pvs)
				{
					vec3_t pos;
					VectorAdd(client->edict->v->origin, client->edict->v->view_ofs, pos);
					leafnum = sv.world.worldmodel->funcs.LeafnumForPoint (sv.world.worldmodel, pos)-1;
					if ( !(mask[leafnum>>3] & (1<<(leafnum&7)) ) )
					{
		//				Con_Printf ("PVS supressed multicast\n");
						continue;
					}
				}
			}

	inrange:
			switch (client->protocol)
			{
			case SCP_BAD:
				continue;	//a bot.
			default:
				SV_Error("multicast: Client is using a bad protocol");

			case SCP_QUAKE3:
				Con_Printf("Skipping multicast for q3 client\n");
				break;

#ifdef NQPROT
			case SCP_NETQUAKE:
			case SCP_PROQUAKE:
			case SCP_FITZ666:
			case SCP_DARKPLACES6:
			case SCP_DARKPLACES7:	//extra prediction stuff
				if (reliable)
				{
					ClientReliableCheckBlock(client, sv.nqmulticast.cursize);
					ClientReliableWrite_SZ(client, sv.nqmulticast.data, sv.nqmulticast.cursize);
				}
				else
					SZ_Write (&client->datagram, sv.nqmulticast.data, sv.nqmulticast.cursize);

				break;
#endif
#ifdef Q2SERVER
			case SCP_QUAKE2:
				if (reliable)
				{
					ClientReliableCheckBlock(client, sv.q2multicast.cursize);
					ClientReliableWrite_SZ(client, sv.q2multicast.data, sv.q2multicast.cursize);
				}
				else
					SZ_Write (&client->datagram, sv.q2multicast.data, sv.q2multicast.cursize);
				break;
#endif
			case SCP_QUAKEWORLD:
				if (reliable)
				{
					ClientReliableCheckBlock(client, sv.multicast.cursize);
					ClientReliableWrite_SZ(client, sv.multicast.data, sv.multicast.cursize);
				}
				else
					SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize);
				break;
			}
		}
	}

	if (sv.mvdrecording && ((demo.recorder.fteprotocolextensions & with) == with) && !(demo.recorder.fteprotocolextensions & without))
	{
		sizebuf_t *msg;
		if (!mask)
		{
			/*no distinction between reliable or not*/
			msg = MVDWrite_Begin(dem_single, pnum, sv.multicast.cursize);
		}
		else
		{
			if (reliable)
			{
				msg = MVDWrite_Begin(dem_all, 0, sv.multicast.cursize);
			}
			else
				msg = &demo.datagram;
		}
		SZ_Write(&demo.datagram, sv.multicast.data, sv.multicast.cursize);
	}

#ifdef NQPROT
	SZ_Clear (&sv.nqmulticast);
#endif
#ifdef Q2SERVER
	SZ_Clear (&sv.q2multicast);
#endif
	SZ_Clear (&sv.multicast);
}

//version does all the work now
void VARGS SV_Multicast (vec3_t origin, multicast_t to)
{
	SV_MulticastProtExt(origin, to, FULLDIMENSIONMASK, 0, 0);
}

/*
==================
SV_StartSound

Each entity can have eight independant sound sources, like voice,
weapon, feet, etc.

Channel 0 is an auto-allocate channel, the others override anything
already running on that entity/channel pair.

An attenuation of 0 will play full volume everywhere in the level.
Larger attenuations will drop off.  (max 4 attenuation)

==================
*/
void SV_StartSound (int ent, vec3_t origin, int seenmask, int channel, char *sample, int volume, float attenuation, int pitchadj)
{
    int         sound_num;
    int			extfield_mask;
	int			qwflags;
    int			i;
	qboolean	use_phs;
	qboolean	reliable;
	int requiredextensions = 0;

	if (channel & 256)
	{
		channel -= 256;
		reliable = true;
	}
	else
		reliable = false;

	if (volume < 0 || volume > 255)
	{
		Con_Printf ("SV_StartSound: volume = %i", volume);
		return;
	}

	if (attenuation < 0 || attenuation > 4)
	{
		Con_Printf ("SV_StartSound: attenuation = %f", attenuation);
		return;
	}

	if (channel < 0 || channel > 255)
	{
		Con_Printf ("SV_StartSound: channel = %i", channel);
		return;
	}

// find precache number for sound
	if (!*sample)
		sound_num = 0;
	else
	{
		for (sound_num=1 ; sound_num<MAX_SOUNDS
			&& sv.strings.sound_precache[sound_num] ; sound_num++)
			if (!strcmp(sample, sv.strings.sound_precache[sound_num]))
				break;
	}

    if ( sound_num == MAX_SOUNDS || !sv.strings.sound_precache[sound_num] )
    {
        Con_DPrintf ("SV_StartSound: %s not precacheed\n", sample);
        return;
    }

	if (reliable || !sv_phs.value)	// no PHS flag
		use_phs = false;
	else
		use_phs = attenuation!=0;

//	if (channel == CHAN_BODY || channel == CHAN_VOICE)
//		reliable = true;

	extfield_mask = 0;
	if (volume != DEFAULT_SOUND_PACKET_VOLUME)
		extfield_mask |= NQSND_VOLUME;
	if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)
		extfield_mask |= NQSND_ATTENUATION;
	if (ent >= 8192 || channel >= 8)
		extfield_mask |= DPSND_LARGEENTITY;
	if (sound_num > 0xff)
		extfield_mask |= DPSND_LARGESOUND;
	if (pitchadj && (pitchadj != 100))
		extfield_mask |= FTESND_PITCHADJ;

#ifdef PEXT_SOUNDDBL
	if (channel >= 8 || ent >= 2048 || sound_num > 0xff || pitchadj)
	{
		//if any of the above conditions evaluates to true, then we can't use standard qw protocols
		MSG_WriteByte (&sv.multicast, svcfte_soundextended);
		MSG_WriteByte (&sv.multicast, extfield_mask);
		if (extfield_mask & NQSND_VOLUME)
			MSG_WriteByte (&sv.multicast, volume);
		if (extfield_mask & NQSND_ATTENUATION)
			MSG_WriteByte (&sv.multicast, attenuation*64);
		if (extfield_mask & FTESND_PITCHADJ)
			MSG_WriteByte (&sv.multicast, pitchadj);
		if (extfield_mask & DPSND_LARGEENTITY)
		{
			MSG_WriteEntity (&sv.multicast, ent);
			MSG_WriteByte (&sv.multicast, channel);
		}
		else
			MSG_WriteShort (&sv.multicast, (ent<<3) | channel);
		if (extfield_mask & DPSND_LARGESOUND)
			MSG_WriteShort (&sv.multicast, sound_num);
		else
			MSG_WriteByte (&sv.multicast, sound_num);
		for (i=0 ; i<3 ; i++)
			MSG_WriteCoord (&sv.multicast, origin[i]);

		requiredextensions |= PEXT_SOUNDDBL;
		if (ent > 512)
			requiredextensions |= PEXT_ENTITYDBL;
		if (ent > 1024)
			requiredextensions |= PEXT_ENTITYDBL2;
	}
	else
#endif
	{
		qwflags = (ent<<3) | channel;

		if (volume != DEFAULT_SOUND_PACKET_VOLUME)
			qwflags |= SND_VOLUME;
		if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)
			qwflags |= SND_ATTENUATION;

		MSG_WriteByte (&sv.multicast, svc_sound);
		MSG_WriteShort (&sv.multicast, qwflags);
		if (qwflags & SND_VOLUME)
			MSG_WriteByte (&sv.multicast, volume);
		if (qwflags & SND_ATTENUATION)
			MSG_WriteByte (&sv.multicast, attenuation*64);
		MSG_WriteByte (&sv.multicast, sound_num);
		for (i=0 ; i<3 ; i++)
			MSG_WriteCoord (&sv.multicast, origin[i]);

		if (ent > 512)
			requiredextensions |= PEXT_ENTITYDBL;
		if (ent > 1024)
			requiredextensions |= PEXT_ENTITYDBL2;
	}

#ifdef NQPROT
	MSG_WriteByte (&sv.nqmulticast, svc_sound);
	MSG_WriteByte (&sv.nqmulticast, extfield_mask);
	if (extfield_mask & NQSND_VOLUME)
		MSG_WriteByte (&sv.nqmulticast, volume);
	if (extfield_mask & NQSND_ATTENUATION)
		MSG_WriteByte (&sv.nqmulticast, attenuation*64);
	if (extfield_mask & FTESND_PITCHADJ)
		MSG_WriteByte (&sv.nqmulticast, pitchadj);
	if (extfield_mask & DPSND_LARGEENTITY)
	{
		MSG_WriteEntity (&sv.nqmulticast, ent);
		MSG_WriteByte (&sv.nqmulticast, channel);
	}
	else
		MSG_WriteShort (&sv.nqmulticast, (ent<<3) | channel);
	if (extfield_mask & DPSND_LARGESOUND)
		MSG_WriteShort (&sv.nqmulticast, sound_num);
	else
		MSG_WriteByte (&sv.nqmulticast, sound_num);
	for (i=0 ; i<3 ; i++)
		MSG_WriteCoord (&sv.nqmulticast, origin[i]);
#endif
	if (use_phs)
		SV_MulticastProtExt(origin, reliable ? MULTICAST_PHS_R : MULTICAST_PHS, seenmask, requiredextensions, 0);
	else
		SV_MulticastProtExt(origin, reliable ? MULTICAST_ALL_R : MULTICAST_ALL, seenmask, requiredextensions, 0);
}

void SVQ1_StartSound (float *origin, wedict_t *wentity, int channel, char *sample, int volume, float attenuation, int pitchadj)
{
	edict_t *entity = (edict_t*)wentity;
	int i;
	vec3_t originbuf;
	if (!origin)
	{
		origin = originbuf;
		if (entity->v->solid == SOLID_BSP)
		{
			for (i=0 ; i<3 ; i++)
				origin[i] = entity->v->origin[i]+0.5*(entity->v->mins[i]+entity->v->maxs[i]);
		}
		else
		{
			VectorCopy (entity->v->origin, origin);
		}
	}

	SV_StartSound(NUM_FOR_EDICT(svprogfuncs, entity), origin, entity->xv->dimension_seen, channel, sample, volume, attenuation, pitchadj);
}

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

FRAME UPDATES

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

int		sv_nailmodel, sv_supernailmodel, sv_playermodel;

void SV_FindModelNumbers (void)
{
	int		i;

	sv_nailmodel = -1;
	sv_supernailmodel = -1;
	sv_playermodel = -1;

	for (i=0 ; i<MAX_MODELS ; i++)
	{
		if (!sv.strings.model_precache[i])
			break;
		if (!strcmp(sv.strings.model_precache[i],"progs/spike.mdl") && sv.multicast.prim.coordsize == 2)
			sv_nailmodel = i;
		if (!strcmp(sv.strings.model_precache[i],"progs/s_spike.mdl") && sv.multicast.prim.coordsize == 2)
			sv_supernailmodel = i;
		if (!strcmp(sv.strings.model_precache[i],"progs/player.mdl"))
			sv_playermodel = i;
	}
}

void SV_WriteEntityDataToMessage (client_t *client, sizebuf_t *msg, int pnum)
{
	edict_t	*other;
	edict_t	*ent;
	int i;
	float newa;

	ent = client->edict;

	if (!ent)
		return;

	// send a damage message if the player got hit this frame
	if (ent->v->dmg_take || ent->v->dmg_save)
	{
		other = PROG_TO_EDICT(svprogfuncs, ent->v->dmg_inflictor);
		if (pnum)
		{
			MSG_WriteByte(msg, svcfte_choosesplitclient);
			MSG_WriteByte(msg, pnum);
		}
		MSG_WriteByte (msg, svc_damage);
		MSG_WriteByte (msg, ent->v->dmg_save);
		MSG_WriteByte (msg, ent->v->dmg_take);
		for (i=0 ; i<3 ; i++)
			MSG_WriteCoord (msg, other->v->origin[i] + 0.5*(other->v->mins[i] + other->v->maxs[i]));

		ent->v->dmg_take = 0;
		ent->v->dmg_save = 0;
	}

	// a fixangle might get lost in a dropped packet.  Oh well.
	if (ent->v->fixangle)
	{
		if (pnum)
		{
			MSG_WriteByte(msg, svcfte_choosesplitclient);
			MSG_WriteByte(msg, pnum);
		}
		if (!client->lockangles && (client->fteprotocolextensions2 & PEXT2_SETANGLEDELTA) && client->delta_sequence != -1)
		{
			MSG_WriteByte (msg, svcfte_setangledelta);
			for (i=0 ; i < 3 ; i++)
			{
				newa = ent->v->angles[i] - SHORT2ANGLE(client->lastcmd.angles[i]);
				MSG_WriteAngle16 (msg, newa);
				client->lastcmd.angles[i] = ANGLE2SHORT(ent->v->angles[i]);
			}
		}
		else
		{
			MSG_WriteByte (msg, svc_setangle);
			for (i=0 ; i < 3 ; i++)
				MSG_WriteAngle (msg, ent->v->angles[i]);
		}
		ent->v->fixangle = 0;
		client->lockangles = true;
	}
	else
		client->lockangles = false;
}

/*sends the a centerprint string directly to the client*/
void SV_WriteCenterPrint(client_t *cl, char *s)
{
	if (cl->controller)
	{	//this is a slave client.
		//find the right number and send.
		int pnum = 0;
		client_t *sp;
		for (sp = cl->controller; sp; sp = sp->controlled)
		{
			if (sp == cl)
				break;
			pnum++;
		}
		cl = cl->controller;

		ClientReliableWrite_Begin (cl, svcfte_choosesplitclient, 4 + strlen(s));
		ClientReliableWrite_Byte (cl, pnum);
		ClientReliableWrite_Byte (cl, svc_centerprint);
	}
	else
	{
		ClientReliableWrite_Begin (cl, svc_centerprint, 2 + strlen(s));
	}
	ClientReliableWrite_String (cl, s);

	if (sv.mvdrecording)
	{
		sizebuf_t *msg = MVDWrite_Begin (dem_single, cl - svs.clients, 2 + strlen(s));
		MSG_WriteByte (msg, svc_centerprint);
		MSG_WriteString (msg, s);
	}
}

/*
==================
SV_WriteClientdataToMessage

==================
*/
void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg)
{
#ifdef NQPROT
	int		i;
	int bits, items;
	edict_t	*ent;
#endif
	client_t *split;
	int pnum=0;
	int weaponmodelindex = 0;
	qboolean nqjunk = true;

	// send the chokecount for r_netgraph
	if (ISQWCLIENT(client))
	if (client->chokecount)
	{
		MSG_WriteByte (msg, svc_chokecount);
		MSG_WriteByte (msg, client->chokecount);
		client->chokecount = 0;
	}

	for (split = client; split; split=split->controlled, pnum++)
	{
		SV_WriteEntityDataToMessage(split, msg, pnum);

		if (split->centerprintstring && ! client->num_backbuf)
		{
			SV_WriteCenterPrint(split, split->centerprintstring);
			Z_Free(split->centerprintstring);
			split->centerprintstring = NULL;
		}
	}
/*
	MSG_WriteByte (msg, svc_time);
	MSG_WriteFloat(msg, sv.physicstime);
	client->nextservertimeupdate = sv.physicstime;
*/

#ifdef NQPROT
	if (ISQWCLIENT(client))
		return;

	ent = client->edict;


	if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS))
	{
		MSG_WriteByte (msg, svc_time);
		MSG_WriteFloat(msg, sv.world.physicstime);

		if (client->fteprotocolextensions2 & PEXT2_PREDINFO)
			MSG_WriteLong(msg, client->last_sequence);

//		Con_Printf("%f\n", sv.world.physicstime);
	}


	bits = 0;

	if (ent->v->view_ofs[2] != DEFAULT_VIEWHEIGHT)
		bits |= SU_VIEWHEIGHT;

//	if (ent->v->idealpitch)
//		bits |= SU_IDEALPITCH;

// stuff the sigil bits into the high bits of items for sbar, or else
// mix in items2
//	val = GetEdictFieldValue(ent, "items2", &items2cache);

//	if (val)
//		items = (int)ent->v->items | ((int)val->_float << 23);
//	else
		items = (int)ent->v->items | ((int)pr_global_struct->serverflags << 28);


	bits |= SU_ITEMS;

	if ( (int)ent->v->flags & FL_ONGROUND)
		bits |= SU_ONGROUND;

	if ( ent->v->waterlevel >= 2)
		bits |= SU_INWATER;

	for (i=0 ; i<3 ; i++)
	{
//		if (ent->v->punchangle[i])
//			bits |= (SU_PUNCH1<<i);
		if (ent->v->velocity[i])
			bits |= (SU_VELOCITY1<<i);
	}

	if (client->protocol == SCP_DARKPLACES6 || client->protocol == SCP_DARKPLACES7)
	{
		//bits &= ~SU_ITEMS;
		nqjunk = false;
	}
	else
	{
		nqjunk = true;

		if (ent->v->weaponframe)
			bits |= SU_WEAPONFRAME;

		if (ent->v->armorvalue)
			bits |= SU_ARMOR;

		weaponmodelindex = SV_ModelIndex(ent->v->weaponmodel + svprogfuncs->stringtable);

		if (weaponmodelindex)
			bits |= SU_WEAPONMODEL;

		if (client->protocol == SCP_FITZ666)
		{
			if (weaponmodelindex & 0xff00)
				bits |= FITZSU_WEAPONMODEL2;
			if ((int)ent->v->armorvalue & 0xff00)
				bits |= FITZSU_ARMOR2;
			if ((int)ent->v->currentammo & 0xff00)
				bits |= FITZSU_AMMO2;
			if ((int)ent->v->ammo_shells & 0xff00)
				bits |= FITZSU_SHELLS2;
			if ((int)ent->v->ammo_nails & 0xff00)
				bits |= FITZSU_NAILS2;
			if ((int)ent->v->ammo_rockets & 0xff00)
				bits |= FITZSU_ROCKETS2;
			if ((int)ent->v->ammo_cells & 0xff00)
				bits |= FITZSU_CELLS2;
			if ((int)ent->v->weaponframe & 0xff00)
				bits |= FITZSU_WEAPONFRAME2;
			if (ent->xv->alpha && ent->xv->alpha < 1)
				bits |= FITZSU_WEAPONALPHA;
		}
	}

	if (bits >= (1u<<16))
		bits |= SU_EXTEND1;
	if (bits >= (1u<<24))
		bits |= SU_EXTEND2;
	if (bits >= (1ull<<32))
		bits |= SU_EXTEND3;

// send the data

	MSG_WriteByte (msg, svc_clientdata);
	MSG_WriteShort (msg, bits);

	if (bits & SU_EXTEND1)
		MSG_WriteByte(msg, bits>>16);
	if (bits & SU_EXTEND2)
		MSG_WriteByte(msg, bits>>24);

	if (bits & SU_VIEWHEIGHT)
		MSG_WriteChar (msg, ent->v->view_ofs[2]);

//	if (bits & SU_IDEALPITCH)
//		MSG_WriteChar (msg, ent->v->idealpitch);

	for (i=0 ; i<3 ; i++)
	{
//		if (bits & (SU_PUNCH1<<i))
//			MSG_WriteChar (msg, ent->v->punchangle[i]);
		if (bits & (SU_VELOCITY1<<i))
		{
			if (client->protocol == SCP_DARKPLACES6 || client->protocol == SCP_DARKPLACES7)
				MSG_WriteCoord(msg, ent->v->velocity[i]);
			else
				MSG_WriteChar (msg, ent->v->velocity[i]/16);
		}
	}

	if (bits & SU_ITEMS)
		MSG_WriteLong (msg, items);

	if (bits & SU_WEAPONFRAME)
		MSG_WriteByte (msg, ent->v->weaponframe);
	if (bits & SU_ARMOR)
	{
		if (ent->v->armorvalue>255 && !(bits & FITZSU_ARMOR2))
			MSG_WriteByte (msg, 255);
		else
			MSG_WriteByte (msg, ent->v->armorvalue);
	}
	if (bits & SU_WEAPONMODEL)
		MSG_WriteByte (msg, weaponmodelindex);

	if (nqjunk)
	{
		MSG_WriteShort (msg, ent->v->health);
		MSG_WriteByte (msg, ent->v->currentammo);
		MSG_WriteByte (msg, ent->v->ammo_shells);
		MSG_WriteByte (msg, ent->v->ammo_nails);
		MSG_WriteByte (msg, ent->v->ammo_rockets);
		MSG_WriteByte (msg, ent->v->ammo_cells);

		if (standard_quake)
		{
			MSG_WriteByte (msg, ent->v->weapon);
		}
		else
		{
			for(i=0;i<32;i++)
			{
				if ( ((int)ent->v->weapon) & (1<<i) )
				{
					MSG_WriteByte (msg, i);
					break;
				}
			}
		}
	}

	if (bits & FITZSU_WEAPONMODEL2)	MSG_WriteByte (msg, weaponmodelindex >> 8);
	if (bits & FITZSU_ARMOR2)		MSG_WriteByte (msg, (int)ent->v->armorvalue >> 8);
	if (bits & FITZSU_AMMO2)		MSG_WriteByte (msg, (int)ent->v->currentammo >> 8);
	if (bits & FITZSU_SHELLS2)		MSG_WriteByte (msg, (int)ent->v->ammo_shells >> 8);
	if (bits & FITZSU_NAILS2)		MSG_WriteByte (msg, (int)ent->v->ammo_nails >> 8);
	if (bits & FITZSU_ROCKETS2)		MSG_WriteByte (msg, (int)ent->v->ammo_rockets >> 8);
	if (bits & FITZSU_CELLS2)		MSG_WriteByte (msg, (int)ent->v->ammo_cells >> 8);
	if (bits & FITZSU_WEAPONFRAME2)	MSG_WriteByte (msg, (int)ent->v->weaponframe >> 8);
	if (bits & FITZSU_WEAPONALPHA)	MSG_WriteByte (msg, ent->xv->alpha*255);

//	}
#endif
}

typedef struct {
	int type;	//negative means a global.
	char name[64];
	union {
		evalc_t c;
		eval_t *g;	//just store a pointer to it.
	} eval;
	int statnum;
} qcstat_t;
qcstat_t qcstats[MAX_CL_STATS-32];
int numqcstats;
void SV_QCStatEval(int type, char *name, evalc_t *field, eval_t *global, int statnum)
{
	int i;
	if (numqcstats == sizeof(qcstats)/sizeof(qcstats[0]))
	{
		Con_Printf("Too many stat types\n");
		return;
	}

	for (i = 0; i < numqcstats; i++)
	{
		if (qcstats[i].statnum == statnum)
			break;
	}
	if (i == numqcstats)
	{
		if (i == sizeof(qcstats)/sizeof(qcstats[0]))
		{
			Con_Printf("Too many stats specified for csqc\n");
			return;
		}
		numqcstats++;
	}

	qcstats[i].type = type;
	qcstats[i].statnum = statnum;
	Q_strncpyz(qcstats[i].name, name, sizeof(qcstats[i].name));
	if (type < 0)
		qcstats[i].eval.g = global;
	else
		memcpy(&qcstats[i].eval.c, field, sizeof(evalc_t));
}

void SV_QCStatGlobal(int type, char *globalname, int statnum)
{
	eval_t *glob;

	if (type < 0)
		return;

	glob = svprogfuncs->FindGlobal(svprogfuncs, globalname, PR_ANY, NULL);
	if (!glob)
	{
		Con_Printf("couldn't find named global for csqc stat (%s)\n", globalname);
		return;
	}
	SV_QCStatEval(-type, globalname, NULL, glob, statnum);
}

void SV_QCStatPtr(int type, void *ptr, int statnum)
{
	SV_QCStatEval(-type, "", NULL, ptr, statnum);
}

void SV_QCStatName(int type, char *name, int statnum)
{
	evalc_t cache;
	if (type < 0)
		return;

	memset(&cache, 0, sizeof(cache));
	if (!svprogfuncs->GetEdictFieldValue(svprogfuncs, NULL, name, &cache))
		return;

	SV_QCStatEval(type, name, &cache, NULL, statnum);
}

void SV_QCStatFieldIdx(int type, unsigned int fieldindex, int statnum)
{
	evalc_t cache;
	char *name;
	etype_t ftype;

	if (type < 0)
		return;

	if (!svprogfuncs->QueryField(svprogfuncs, fieldindex, &ftype, &name, &cache))
	{
		Con_Printf("invalid field for csqc stat\n");
		return;
	}
	SV_QCStatEval(type, name, &cache, NULL, statnum);
}

void SV_ClearQCStats(void)
{
	numqcstats = 0;
}

extern cvar_t dpcompat_stats;
void SV_UpdateQCStats(edict_t	*ent, int *statsi, char **statss, float *statsf)
{
	char *s;
	int i;
	int t;

	for (i = 0; i < numqcstats; i++)
	{
		eval_t *eval;
		t = qcstats[i].type;
		if (t < 0)
		{
			t = -t;
			eval = qcstats[i].eval.g;
		}
		else
		{
			eval = svprogfuncs->GetEdictFieldValue(svprogfuncs, ent, qcstats[i].name, &qcstats[i].eval.c);
		}
		if (!eval)
			continue;

		switch(t)
		{
		case ev_float:
			statsf[qcstats[i].statnum] = eval->_float;
			break;
		case ev_vector:
			statsf[qcstats[i].statnum+0] = eval->_vector[0];
			statsf[qcstats[i].statnum+1] = eval->_vector[1];
			statsf[qcstats[i].statnum+2] = eval->_vector[2];
			break;
		case ev_integer:
			statsi[qcstats[i].statnum] = eval->_int;
			break;
		case ev_entity:
			statsi[qcstats[i].statnum] = NUM_FOR_EDICT(svprogfuncs, PROG_TO_EDICT(svprogfuncs, eval->edict));
			break;
		case ev_string:
			s = PR_GetString(svprogfuncs, eval->string);
			statss[qcstats[i].statnum] = s;
//			statsi[qcstats[i].statnum+0] = LittleLong(((int*)s)[0]);	//so the network is sent out correctly as a string.
//			statsi[qcstats[i].statnum+1] = LittleLong(((int*)s)[1]);
//			statsi[qcstats[i].statnum+2] = LittleLong(((int*)s)[2]);
//			statsi[qcstats[i].statnum+3] = LittleLong(((int*)s)[3]);
			break;
		}
	}
}

/*this function calculates the current stat values for the given client*/
void SV_CalcClientStats(client_t *client, int statsi[MAX_CL_STATS], float statsf[MAX_CL_STATS], char *statss[MAX_CL_STATS])
{
	extern qboolean pr_items2;
	edict_t *ent;
	ent = client->edict;
	memset (statsi, 0, sizeof(int)*MAX_CL_STATS);
	memset (statsf, 0, sizeof(float)*MAX_CL_STATS);
	memset (statss, 0, sizeof(char*)*MAX_CL_STATS);

	// if we are a spectator and we are tracking a player, we get his stats
	// so our status bar reflects his
	if (client->spectator && client->spec_track > 0)
		ent = svs.clients[client->spec_track - 1].edict;

#ifdef HLSERVER
	if (svs.gametype == GT_HALFLIFE)
	{
		SVHL_BuildStats(client, statsi, statsf, statss);
	}
	else
#endif
	{
		statsf[STAT_HEALTH] = ent->v->health;	//sorry, but mneh
		statsi[STAT_WEAPON] = SV_ModelIndex(PR_GetString(svprogfuncs, ent->v->weaponmodel));
		if (client->fteprotocolextensions & PEXT_MODELDBL)
		{
			if ((unsigned)statsi[STAT_WEAPON] >= MAX_MODELS)
				statsi[STAT_WEAPON] = 0;
		}
		else
		{
			if ((unsigned)statsi[STAT_WEAPON] >= 256)
				statsi[STAT_WEAPON] = 0;
		}
		statsf[STAT_AMMO] = ent->v->currentammo;
		statsf[STAT_ARMOR] = ent->v->armorvalue;
		statsf[STAT_SHELLS] = ent->v->ammo_shells;
		statsf[STAT_NAILS] = ent->v->ammo_nails;
		statsf[STAT_ROCKETS] = ent->v->ammo_rockets;
		statsf[STAT_CELLS] = ent->v->ammo_cells;
		if (!client->spectator)
		{
			statsf[STAT_ACTIVEWEAPON] = ent->v->weapon;
			if ((client->csqcactive && !(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) || client->protocol != SCP_QUAKEWORLD)
				statsf[STAT_WEAPONFRAME] = ent->v->weaponframe;
		}

		// stuff the sigil bits into the high bits of items for sbar
		if (pr_items2)
			statsi[STAT_ITEMS] = (int)ent->v->items | ((int)ent->xv->items2 << 23);
		else
			statsi[STAT_ITEMS] = (int)ent->v->items | ((int)pr_global_struct->serverflags << 28);

		statsf[STAT_VIEWHEIGHT] = ent->v->view_ofs[2];
	#ifdef PEXT_VIEW2
		if (ent->xv->view2)
			statsi[STAT_VIEW2] = NUM_FOR_EDICT(svprogfuncs, PROG_TO_EDICT(svprogfuncs, ent->xv->view2));
		else
			statsi[STAT_VIEW2] = 0;
	#endif

		if (!ent->xv->viewzoom)
			statsi[STAT_VIEWZOOM] = 255;
		else
			statsi[STAT_VIEWZOOM] = ent->xv->viewzoom*255;

		if (client->fteprotocolextensions2 & PEXT2_PREDINFO)
		{
			statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value;
			statsf[STAT_MOVEVARS_ENTGRAVITY] = host_client->entgravity;
			statsf[STAT_MOVEVARS_MAXSPEED] = host_client->maxspeed;
		}
		if (client->protocol == SCP_DARKPLACES7)
		{
			/*note: statsf is truncated, which would mess things up*/
			float	*statsfi = (float*)statsi;
	//		statsfi[STAT_MOVEVARS_WALLFRICTION] = sv_wall
			statsfi[STAT_MOVEVARS_FRICTION] = sv_friction.value;
			statsfi[STAT_MOVEVARS_WATERFRICTION] = sv_waterfriction.value;
			statsfi[STAT_MOVEVARS_TICRATE] = 72;
			statsfi[STAT_MOVEVARS_TIMESCALE] = sv_gamespeed.value;
			statsfi[STAT_MOVEVARS_GRAVITY] = sv_gravity.value;
			statsfi[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value;
			statsfi[STAT_MOVEVARS_MAXSPEED] = host_client->maxspeed;
			statsfi[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_spectatormaxspeed.value;
			statsfi[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value;
			statsfi[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value;
			statsfi[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value;
			statsfi[STAT_MOVEVARS_ENTGRAVITY] = host_client->entgravity;
			statsfi[STAT_MOVEVARS_JUMPVELOCITY] = 270;//sv_jumpvelocity.value;	//bah
			statsfi[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value;
			statsfi[STAT_MOVEVARS_MAXAIRSPEED] = host_client->maxspeed;
			statsfi[STAT_MOVEVARS_STEPHEIGHT] = 18;
			statsfi[STAT_MOVEVARS_AIRACCEL_QW] = 1;
			statsfi[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_gravity.value;
		}

		SV_UpdateQCStats(ent, statsi, statss, statsf);
	}
}

/*
=======================
SV_UpdateClientStats

Performs a delta update of the stats array.  This should only be performed
when a reliable message can be delivered this frame.
=======================
*/
void SV_UpdateClientStats (client_t *client, int pnum)
{
	int		statsi[MAX_CL_STATS];
	float	statsf[MAX_CL_STATS];
	char	*statss[MAX_CL_STATS];
	int		i, m;

	/*figure out what the stat values should be*/
	SV_CalcClientStats(client, statsi, statsf, statss);

	m = MAX_QW_STATS;
	if (client->fteprotocolextensions & (PEXT_HEXEN2|PEXT_CSQC))
		m = MAX_CL_STATS;

	for (i=0 ; i<m ; i++)
	{
#ifdef SERVER_DEMO_PLAYBACK
		if (sv.demofile)
		{
			if (!client->spec_track)
			{
				statsf[i] = 0;
				if (i == STAT_HEALTH)
					statsf[i] = 100;
			}
			else
			{
				statsf[i] = sv.recordedplayer[client->spec_track - 1].stats[i];
				statsi[i] = sv.recordedplayer[client->spec_track - 1].stats[i];
			}
		}
#endif
		if (!ISQWCLIENT(client))
		{
			if (!statsi[i])
				statsi[i] = statsf[i];
			if (statsi[i] != client->statsi[i])
			{
				client->statsi[i] = statsi[i];
				ClientReliableWrite_Begin(client, svcnq_updatestatlong, 6);
				ClientReliableWrite_Byte(client, i);
				ClientReliableWrite_Long(client, statsi[i]);
			}
		}
		else
		{
#ifdef PEXT_CSQC
			if (client->fteprotocolextensions & PEXT_CSQC)
			{
				if (statss[i] || client->statss[i])
				if (strcmp(statss[i]?statss[i]:"", client->statss[i]?client->statss[i]:""))
				{
					client->statss[i] = statss[i];
					if (pnum)
					{
						ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 5+strlen(statss[i]));
						ClientReliableWrite_Byte(client->controller, pnum);
						ClientReliableWrite_Byte(client->controller, svcfte_updatestatstring);
						ClientReliableWrite_Byte(client->controller, i);
						ClientReliableWrite_String(client->controller, statss[i]);
					}
					else
					{
						ClientReliableWrite_Begin(client, svcfte_updatestatstring, 3+strlen(statss[i]));
						ClientReliableWrite_Byte(client, i);
						ClientReliableWrite_String(client, statss[i]);
					}
				}
			}
			if (dpcompat_stats.ival)
			{
				if (statsf[i])
				{
					statsi[i] = statsf[i];
					statsf[i] = 0;
				}
			}
#endif

			if (statsf[i])
			{
				if (client->fteprotocolextensions & PEXT_CSQC)
				{
					if (statsf[i] != client->statsf[i])
					{
						if (statsf[i] - (float)(int)statsf[i] == 0 && statsf[i] >= 0 && statsf[i] <= 255)
						{
							if (pnum)
							{
								ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 5);
								ClientReliableWrite_Byte(client->controller, pnum);
								ClientReliableWrite_Byte(client->controller, svcqw_updatestatbyte);
								ClientReliableWrite_Byte(client->controller, i);
								ClientReliableWrite_Byte(client->controller, statsf[i]);
							}
							else
							{
								ClientReliableWrite_Begin(client, svcqw_updatestatbyte, 3);
								ClientReliableWrite_Byte(client, i);
								ClientReliableWrite_Byte(client, statsf[i]);
							}
						}
						else
						{
							if (pnum)
							{
								ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 8);
								ClientReliableWrite_Byte(client->controller, pnum);
								ClientReliableWrite_Byte(client->controller, svcfte_updatestatfloat);
								ClientReliableWrite_Byte(client->controller, i);
								ClientReliableWrite_Float(client->controller, statsf[i]);
							}
							else
							{
								ClientReliableWrite_Begin(client, svcfte_updatestatfloat, 6);
								ClientReliableWrite_Byte(client, i);
								ClientReliableWrite_Float(client, statsf[i]);
							}
						}
						client->statsf[i] = statsf[i];
						/*make sure statsf is correct*/
						client->statsi[i] = statsf[i];
					}
					continue;
				}
				else
				{
					statsi[i] = statsf[i];
				}
			}
			if (statsi[i] != client->statsi[i])
			{
				client->statsi[i] = statsi[i];
				client->statsf[i] = statsi[i];

				if (statsi[i] >=0 && statsi[i] <= 255)
				{
					if (pnum)
					{
						ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 5);
						ClientReliableWrite_Byte(client->controller, pnum);
						ClientReliableWrite_Byte(client->controller, svcqw_updatestatbyte);
						ClientReliableWrite_Byte(client->controller, i);
						ClientReliableWrite_Byte(client->controller, statsi[i]);
					}
					else
					{
						ClientReliableWrite_Begin(client, svcqw_updatestatbyte, 3);
						ClientReliableWrite_Byte(client, i);
						ClientReliableWrite_Byte(client, statsi[i]);
					}
				}
				else
				{
					if (pnum)
					{
						ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 8);
						ClientReliableWrite_Byte(client->controller, pnum);
						ClientReliableWrite_Byte(client->controller, svcqw_updatestatlong);
						ClientReliableWrite_Byte(client->controller, i);
						ClientReliableWrite_Long(client->controller, statsi[i]);
					}
					else
					{
						ClientReliableWrite_Begin(client, svcqw_updatestatlong, 6);
						ClientReliableWrite_Byte(client, i);
						ClientReliableWrite_Long(client, statsi[i]);
					}
				}
			}
		}
	}
}

/*
=======================
SV_SendClientDatagram
=======================
*/
qboolean SV_SendClientDatagram (client_t *client)
{
	qbyte		buf[MAX_OVERALLMSGLEN];
	sizebuf_t	msg;
	unsigned int sentbytes, fnum;

	msg.data = buf;
	msg.maxsize = sizeof(buf);
	msg.cursize = 0;
	msg.allowoverflow = true;
	msg.overflowed = false;
	msg.prim = client->datagram.prim;

	if (client->protocol != SCP_FITZ666 && !client->netchan.fragmentsize)
		msg.maxsize = MAX_DATAGRAM;

	if (sv.world.worldmodel && !client->controller)
	{
		if (ISQ2CLIENT(client))
		{
			SVQ2_BuildClientFrame (client);

			// send over all the relevant entity_state_t
			// and the player_state_t
			SVQ2_WriteFrameToClient (client, &msg);
		}
		else
		{
			// add the client specific data to the datagram
			SV_WriteClientdataToMessage (client, &msg);

			// send over all the objects that are in the PVS
			// this will include clients, a packetentities, and
			// possibly a nails update
			SV_WriteEntitiesToClient (client, &msg, false);
		}
#ifdef VOICECHAT
		SV_VoiceSendPacket(client, &msg);
#endif
	}

	// copy the accumulated multicast datagram
	// for this client out to the message
	if (client->datagram.overflowed)
		Con_Printf ("WARNING: datagram overflowed for %s\n", client->name);
	else
		SZ_Write (&msg, client->datagram.data, client->datagram.cursize);
	SZ_Clear (&client->datagram);

	// send deltas over reliable stream
	if (sv.world.worldmodel)
		if (!ISQ2CLIENT(client) && Netchan_CanReliable (&client->netchan, SV_RateForClient(client)))
		{
			int pnum=1;
			client_t *c;
			SV_UpdateClientStats (client, 0);

			for (c = client->controlled; c; c = c->controlled,pnum++)
				SV_UpdateClientStats(c, pnum);
		}

	if (msg.overflowed)
	{
		Con_Printf ("WARNING: msg overflowed for %s\n", client->name);
		SZ_Clear (&msg);
	}

	SV_DarkPlacesDownloadChunk(client, &msg);

	// send the datagram
	fnum = client->netchan.outgoing_sequence;
	sentbytes = Netchan_Transmit (&client->netchan, msg.cursize, buf, SV_RateForClient(client));

	if (ISQWCLIENT(client) || ISNQCLIENT(client))
		client->frameunion.frames[fnum & UPDATE_MASK].packetsizeout += sentbytes;
	return true;
}

client_t *SV_SplitClientDest(client_t *client, qbyte first, int size)
{
	client_t *sp;
	if (client->controller)
	{	//this is a slave client.
		//find the right number and send.
		int pnum = 0;
		for (sp = client->controller; sp; sp = sp->controlled)
		{
			if (sp == client)
				break;
			pnum++;
		}
		sp = client->controller;

		ClientReliableWrite_Begin (sp, svcfte_choosesplitclient, size+2);
		ClientReliableWrite_Byte (sp, pnum);
		ClientReliableWrite_Byte (sp, first);
		return sp;
	}
	else
	{
		ClientReliableWrite_Begin (client, first, size);
		return client;
	}
}

void SV_FlushBroadcasts (void)
{
	client_t *client;
	int j;
	// append the broadcast messages to each client messages
	for (j=0, client = svs.clients ; j<MAX_CLIENTS ; j++, client++)
	{
		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

#ifdef Q2SERVER
		if (ISQ2CLIENT(client))
		{
			ClientReliableCheckBlock(client, sv.q2reliable_datagram.cursize);
			ClientReliableWrite_SZ(client, sv.q2reliable_datagram.data, sv.q2reliable_datagram.cursize);

			if (client->state != cs_spawned)
				continue;	// datagrams only go to spawned
			SZ_Write (&client->datagram
				, sv.q2datagram.data
				, sv.q2datagram.cursize);
		}
		else
#endif
#ifdef NQPROT
		if (!ISQWCLIENT(client))
		{
			if (client->pextknown)
			{
				ClientReliableCheckBlock(client, sv.nqreliable_datagram.cursize);
				ClientReliableWrite_SZ(client, sv.nqreliable_datagram.data, sv.nqreliable_datagram.cursize);
			}
			if (client->state != cs_spawned)
				continue;	// datagrams only go to spawned
			SZ_Write (&client->datagram
				, sv.nqdatagram.data
				, sv.nqdatagram.cursize);
		}
		else
#endif
		{
			ClientReliableCheckBlock(client, sv.reliable_datagram.cursize);
			ClientReliableWrite_SZ(client, sv.reliable_datagram.data, sv.reliable_datagram.cursize);

			if (client->state != cs_spawned)
				continue;	// datagrams only go to spawned
			SZ_Write (&client->datagram
				, sv.datagram.data
				, sv.datagram.cursize);
		}
	}

	SZ_Clear (&sv.reliable_datagram);
	SZ_Clear (&sv.datagram);
#ifdef NQPROT
	SZ_Clear (&sv.nqreliable_datagram);
	SZ_Clear (&sv.nqdatagram);
#endif
	SZ_Clear (&sv.q2reliable_datagram);
	SZ_Clear (&sv.q2datagram);
}

/*
=======================
SV_UpdateToReliableMessages
=======================
*/
void SV_UpdateToReliableMessages (void)
{
	int			i, j;
	client_t *client, *sp;
	edict_t *ent;
	char *name;

	float curgrav;
	float curspeed;
	int curfrags;

// check for changes to be sent over the reliable streams to all clients
	for (i=0, host_client = svs.clients ; i<MAX_CLIENTS ; i++, host_client++)
	{
		if ((svs.gametype == GT_Q1QVM || svs.gametype == GT_PROGS) && host_client->state == cs_spawned)
		{
			//DP_SV_CLIENTCOLORS
			if (host_client->edict->xv->clientcolors != host_client->playercolor)
			{
				Info_SetValueForKey(host_client->userinfo, "topcolor", va("%i", (int)host_client->edict->xv->clientcolors/16), sizeof(host_client->userinfo));
				Info_SetValueForKey(host_client->userinfo, "bottomcolor", va("%i", (int)host_client->edict->xv->clientcolors&15), sizeof(host_client->userinfo));
				{
					SV_ExtractFromUserinfo (host_client, true);	//this will take care of nq for us anyway.

					MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
					MSG_WriteByte (&sv.reliable_datagram, i);
					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, i);
					MSG_WriteString (&sv.reliable_datagram, "bottomcolor");
					MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "bottomcolor"));
				}
			}

			name = PR_GetString(svprogfuncs, host_client->edict->v->netname);
			if (name != host_client->name)
			{
				if (strcmp(host_client->name, name))
				{
					char oname[80];
					Q_strncpyz(oname, host_client->name, sizeof(oname));

					Con_DPrintf("Client %s programatically renamed to %s\n", host_client->name, name);
					Info_SetValueForKey(host_client->userinfo, "name", name, sizeof(host_client->userinfo));
					SV_ExtractFromUserinfo (host_client, true);

					if (strcmp(oname, host_client->name))
					{
						MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
						MSG_WriteByte (&sv.reliable_datagram, i);
						MSG_WriteString (&sv.reliable_datagram, "name");
						MSG_WriteString (&sv.reliable_datagram, host_client->name);
					}
				}
				host_client->edict->v->netname = PR_SetString(svprogfuncs, host_client->name);
			}
		}

		if (host_client->state != cs_spawned)
		{
			if (!host_client->state && host_client->name && host_client->name[0])	//if this is a writebyte bot
			{
				if (host_client->old_frags != (int)host_client->edict->v->frags)
				{
					for (j=0, client = svs.clients ; j<MAX_CLIENTS ; j++, client++)
					{
						if (client->state < cs_connected)
							continue;
						ClientReliableWrite_Begin(client, svc_updatefrags, 4);
						ClientReliableWrite_Byte(client, i);
						ClientReliableWrite_Short(client, host_client->edict->v->frags);
					}

					if (sv.mvdrecording)
					{
						sizebuf_t *msg = MVDWrite_Begin(dem_all, 0, 4);
						MSG_WriteByte(msg, svc_updatefrags);
						MSG_WriteByte(msg, i);
						MSG_WriteShort(msg, host_client->edict->v->frags);
					}

					host_client->old_frags = host_client->edict->v->frags;
				}
			}
			continue;
		}
		if (svs.gametype == GT_PROGS || svs.gametype == GT_Q1QVM)
		{
			ent = host_client->edict;

			curfrags = host_client->edict->v->frags;
			curgrav = ent->xv->gravity*sv_gravity.value;
			curspeed = ent->xv->maxspeed;
			if (progstype != PROG_QW)
			{
				if (!curgrav)
					curgrav = sv_gravity.value;
				if (!curspeed)
					curspeed = sv_maxspeed.value;
			}
			if (ent->xv->hasted)
				curspeed*=ent->xv->hasted;
		}
		else
		{
			curgrav = sv_gravity.value;
			curspeed = sv_maxspeed.value;
			curfrags = 0;
		}
#ifdef SVCHAT	//enforce a no moving time when chatting. Prevent client prediction going mad.
		if (host_client->chat.active)
			curspeed = 0;
#endif

		if (!ISQ2CLIENT(host_client))
		{
			if (host_client->sendinfo)
			{
				host_client->sendinfo = false;
				SV_FullClientUpdate (host_client, NULL);
			}
			if (host_client->old_frags != curfrags)
			{
				for (j=0, client = svs.clients ; j<MAX_CLIENTS ; j++, client++)
				{
					if (client->state < cs_connected)
						continue;
					if (client->controller)
						continue;
					ClientReliableWrite_Begin(client, svc_updatefrags, 4);
					ClientReliableWrite_Byte(client, i);
					ClientReliableWrite_Short(client, curfrags);
				}

				if (sv.mvdrecording)
				{
					sizebuf_t *msg = MVDWrite_Begin(dem_all, 0, 4);
					MSG_WriteByte(msg, svc_updatefrags);
					MSG_WriteByte(msg, i);
					MSG_WriteShort(msg, curfrags);
				}

				host_client->old_frags = curfrags;
			}

			{
				if (host_client->entgravity != curgrav)
				{
					if (ISQWCLIENT(host_client))
					{
						sp = SV_SplitClientDest(host_client, svc_entgravity, 5);
						ClientReliableWrite_Float(sp, curgrav/movevars.gravity);	//lie to the client in a cunning way
					}
					host_client->entgravity = curgrav;
				}

				if (host_client->maxspeed != curspeed)
				{	//MSVC can really suck at times (optimiser bug)
					if (ISQWCLIENT(host_client))
					{
						if (host_client->controller)
						{	//this is a slave client.
							//find the right number and send.
							int pnum = 0;
							client_t *sp;
							for (sp = host_client->controller; sp; sp = sp->controlled)
							{
								if (sp == host_client)
									break;
								pnum++;
							}
							sp = host_client->controller;

							ClientReliableWrite_Begin (sp, svcfte_choosesplitclient, 7);
							ClientReliableWrite_Byte (sp, pnum);
							ClientReliableWrite_Byte (sp, svc_maxspeed);
							ClientReliableWrite_Float(sp, curspeed);
						}
						else
						{
							ClientReliableWrite_Begin(host_client, svc_maxspeed, 5);
							ClientReliableWrite_Float(host_client, curspeed);
						}
					}
					host_client->maxspeed = curspeed;
				}
			}
		}
	}

	if (sv.reliable_datagram.overflowed)
	{
		Con_Printf("WARNING: Reliable datagram overflowed\n");
		SZ_Clear (&sv.reliable_datagram);
	}

	if (sv.datagram.overflowed)
		SZ_Clear (&sv.datagram);

#ifdef NQPROT
	if (sv.nqdatagram.overflowed)
		SZ_Clear (&sv.nqdatagram);
#endif
#ifdef Q2SERVER
	if (sv.q2datagram.overflowed)
		SZ_Clear (&sv.q2datagram);
#endif

	SV_FlushBroadcasts();
}

#ifdef _MSC_VER
#pragma optimize( "", off )
#endif



/*
=======================
SV_SendClientMessages
=======================
*/
void SV_SendClientMessages (void)
{
	int			i, j;
	client_t	*c;
	int sentbytes, fnum;
	float pt = sv.paused?realtime:sv.world.physicstime;

#ifdef Q3SERVER
	if (svs.gametype == GT_QUAKE3)
	{
		for (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)
		{
			if (c->state <= cs_zombie)
				continue;

			if (c->drop)
			{
				SV_DropClient(c);
				c->drop = false;
				continue;
			}

			if (c->protocol == SCP_BAD)	//this is a bot.
			{
				SZ_Clear (&c->netchan.message);
				SZ_Clear (&c->datagram);
				continue;
			}

			SVQ3_SendMessage(c);
		}
		return;
	}
#endif

// update frags, names, etc
	SV_UpdateToReliableMessages ();

// build individual updates
	for (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)
	{
		if (c->state <= cs_zombie)
			continue;

		if (c->drop)
		{
			SV_DropClient(c);
			c->drop = false;
			continue;
		}

#ifdef SVCHAT
		SV_ChatThink(c);
#endif

		if (c->wasrecorded)
		{
			c->netchan.message.cursize = 0;
			c->datagram.cursize = 0;
			continue;
		}

		if (c->istobeloaded && c->state == cs_zombie)
		{	//not yet present.
			c->netchan.message.cursize = 0;
			c->datagram.cursize = 0;
			continue;
		}

#ifdef Q3SERVER
		if (ISQ3CLIENT(c))
		{	//q3 protocols bypass backbuffering and pretty much everything else
			if (c->state <= cs_zombie)
				continue;
			SVQ3_SendMessage(c);
			continue;
		}
#endif

		// check to see if we have a backbuf to stick in the reliable
		if (c->num_backbuf)
		{
			// will it fit?
			if (c->netchan.message.cursize + c->backbuf_size[0] <
				c->netchan.message.maxsize)
			{

				Con_DPrintf("%s: backbuf %d bytes\n",
					c->name, c->backbuf_size[0]);

				// it'll fit
				SZ_Write(&c->netchan.message, c->backbuf_data[0],
					c->backbuf_size[0]);

				//move along, move along
				for (j = 1; j < c->num_backbuf; j++)
				{
					memcpy(c->backbuf_data[j - 1], c->backbuf_data[j],
						c->backbuf_size[j]);
					c->backbuf_size[j - 1] = c->backbuf_size[j];
				}

				c->num_backbuf--;
				if (c->num_backbuf)
				{
					memset(&c->backbuf, 0, sizeof(c->backbuf));
					c->backbuf.data = c->backbuf_data[c->num_backbuf - 1];
					c->backbuf.cursize = c->backbuf_size[c->num_backbuf - 1];
					c->backbuf.maxsize = sizeof(c->backbuf_data[c->num_backbuf - 1]);
				}
			}
		}

		if (c->protocol == SCP_BAD)
		{
			SZ_Clear (&c->netchan.message);
			SZ_Clear (&c->datagram);
			c->num_backbuf = 0;
			continue;
		}

		// if the reliable message overflowed,
		// drop the client
		if (c->netchan.message.overflowed)
		{
			SZ_Clear (&c->netchan.message);
			SZ_Clear (&c->datagram);
			SV_BroadcastPrintf (PRINT_HIGH, "%s overflowed\n", c->name);
			Con_Printf ("WARNING: reliable overflow for %s\n",c->name);
			c->send_message = true;
			c->netchan.cleartime = 0;	// don't choke this message
			SV_DropClient (c);
			continue;
		}

#ifdef NQPROT
		// only send messages if the client has sent one
		// and the bandwidth is not choked
		if (ISNQCLIENT(c))
		{
			//tread carefully with NQ:
			//while loading models etc, NQ will error out if it receives anything that it wasn't expecting.
			//we should still send unreliable nops whenever we want as a keepalive (and we may need to in order to wake up the client).
			//other unreliables are disallowed when connecting, due to sync issues.
			//reliables may be sent only if some other code has said that its okay (to avoid stray name changes killing clients).
			if (c->state == cs_connected)
			{
				if (c->nextservertimeupdate > pt + 6)
					c->nextservertimeupdate = 0;

				c->netchan.nqunreliableonly = !c->send_message;
				c->datagram.cursize = 0;
				if (!c->send_message && c->nextservertimeupdate < pt)
				{
					if (c->nextservertimeupdate)
						MSG_WriteByte(&c->datagram, svc_nop);
					c->nextservertimeupdate = pt+5;
				}
				c->send_message = true;
				//we can still send an outgoing packet if something set send_message. This should really only be svnq_new_f and friends.
			}
			else
			{
				if (c->nextservertimeupdate > pt + 0.1)
					c->nextservertimeupdate = 0;

				c->netchan.nqunreliableonly = false;
				c->send_message = false;
				//nq sends one packet only for each server physics frame
				if (c->nextservertimeupdate < pt && c->state != cs_zombie)
				{
					c->send_message = true;
					c->nextservertimeupdate = pt + 1.0/77;
				}
			}
		}
		//qw servers will set send_message on packet reception.
#endif

		if (!c->send_message)
			continue;
		c->send_message = false;	// try putting this after choke?

		if (c->controller)
			continue;	/*shouldn't have been set*/

		if (!sv.paused && !Netchan_CanPacket (&c->netchan, SV_RateForClient(c)))
		{
			c->chokecount++;
			c->waschoked = true;
			continue;		// bandwidth choke
		}
		c->waschoked = false;

		if (sv.time > c->ratetime + 1)
		{
			c->inrate = c->netchan.bytesin / (sv.time - c->ratetime);
			c->outrate = c->netchan.bytesout / (sv.time - c->ratetime);
			c->netchan.bytesin = 0;
			c->netchan.bytesout = 0;
			c->ratetime = sv.time;
		}

		if (c->state == cs_spawned)
			SV_SendClientDatagram (c);
		else
		{
			SV_SendClientPrespawnInfo(c);

			SV_DarkPlacesDownloadChunk(c, &c->datagram);
			fnum = c->netchan.outgoing_sequence;
			sentbytes = Netchan_Transmit (&c->netchan, c->datagram.cursize, c->datagram.data, SV_RateForClient(c));	// just update reliable
			if (ISQWCLIENT(c) || ISNQCLIENT(c))
				c->frameunion.frames[fnum & UPDATE_MASK].packetsizeout += sentbytes;
			c->datagram.cursize = 0;
		}

	}

	SV_CleanupEnts();
}

#ifdef _MSC_VER
#pragma optimize( "", on )
#endif

void SV_WriteMVDMessage (sizebuf_t *msg, int type, int to, float time);

void DemoWriteQTVTimePad(int msecs);
#define Max(a, b) ((a>b)?a:b)
void SV_SendMVDMessage(void)
{
	int			i, j, m, cls = 0;
	client_t	*c;
	qbyte		buf[MAX_DATAGRAM];
	sizebuf_t	msg;
	int		statsi[MAX_CL_STATS];
	float	statsf[MAX_CL_STATS];
	char	*statss[MAX_CL_STATS];
	float		min_fps;
	extern		cvar_t sv_demofps;
	extern		cvar_t sv_demoPings;
//	extern		cvar_t	sv_demoMaxSize;
	sizebuf_t *dmsg;

	SV_MVD_RunPendingConnections();

	if (!sv.mvdrecording)
		return;

	if (sv_demoPings.value)
	{
		if (sv.time - demo.pingtime > sv_demoPings.value)
		{
			SV_MVDPings();
			demo.pingtime = sv.time;
		}
	}


	if (sv_demofps.value <= 1)
		min_fps = 30.0;
	else
		min_fps = sv_demofps.value;

	min_fps = Max(4, min_fps);
	if (sv.time - demo.time < 1.0/min_fps)
		return;

	for (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)
	{
		if (c->state != cs_spawned)
			continue;	// datagrams only go to spawned

		cls |= 1 << i;
	}

	if (!cls)
	{
		SZ_Clear (&demo.datagram);
		DemoWriteQTVTimePad((int)((sv.time - demo.time)*1000));
		DestFlush(false);
		demo.time = sv.time;
		return;
	}

	msg.data = buf;
	msg.maxsize = sizeof(buf);
	msg.cursize = 0;
	msg.allowoverflow = true;
	msg.overflowed = false;

	m = MAX_QW_STATS;
	if (demo.recorder.fteprotocolextensions & (PEXT_HEXEN2|PEXT_CSQC))
		m = MAX_CL_STATS;

	for (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)
	{
		if (c->state != cs_spawned)
			continue;	// datagrams only go to spawned

		if (c->spectator)
			continue;

		/*figure out what the stat values should be*/
		SV_CalcClientStats(c, statsi, statsf, statss);

		for (j=0 ; j<m ; j++)
		{
			if (demo.recorder.fteprotocolextensions & PEXT_CSQC)
			{
				if (statss[j] || demo.statss[i][j])
				if (strcmp(statss[j]?statss[j]:"", demo.statss[i][j]?demo.statss[i][j]:""))
				{
					sizebuf_t *msg = MVDWrite_Begin(dem_stats, i, 3+strlen(statss[j]));
					demo.statss[i][j] = statss[j];
					MSG_WriteByte(msg, svcfte_updatestatstring);
					MSG_WriteByte(msg, j);
					MSG_WriteString(msg, statss[j]);
				}
			}

			if (statsf[j])
			{
				if (demo.recorder.fteprotocolextensions & PEXT_CSQC)
				{
					if (statsf[j] != demo.statsf[i][j])
					{
						if (statsf[j] - (float)(int)statsf[j] == 0 && statsf[j] >= 0 && statsf[j] <= 255)
						{
							dmsg = MVDWrite_Begin(dem_stats, i, 3);
							MSG_WriteByte(dmsg, svcqw_updatestatbyte);
							MSG_WriteByte(dmsg, j);
							MSG_WriteByte(dmsg, statsf[j]);
						}
						else
						{
							dmsg = MVDWrite_Begin(dem_stats, i, 6);
							MSG_WriteByte(dmsg, svcfte_updatestatfloat);
							MSG_WriteByte(dmsg, j);
							MSG_WriteFloat(dmsg, statsf[j]);
						}
						demo.statsf[i][j] = statsf[j];
						/*make sure statsf is correct*/
						demo.statsi[i][j] = statsf[j];
					}
					continue;
				}
				else
					statsi[j] = statsf[j];
			}

			if (statsi[j] != demo.statsi[i][j])
			{
				demo.statsi[i][j] = statsi[j];
				demo.statsf[i][j] = statsi[j];
				if (statsi[j] >=0 && statsi[j] <= 255)
				{
					dmsg = MVDWrite_Begin(dem_stats, i, 3);
					MSG_WriteByte(dmsg, svcqw_updatestatbyte);
					MSG_WriteByte(dmsg, j);
					MSG_WriteByte(dmsg, statsi[j]);
				}
				else
				{
					dmsg = MVDWrite_Begin(dem_stats, i, 6);
					MSG_WriteByte(dmsg, svcqw_updatestatlong);
					MSG_WriteByte(dmsg, j);
					MSG_WriteLong(dmsg, statsi[j]);
				}
			}
		}
	}

	// send over all the objects that are in the PVS
	// this will include clients, a packetentities, and
	// possibly a nails update
	msg.cursize = 0;
	msg.prim = demo.recorder.netchan.netprim;
	if (!demo.recorder.delta_sequence)
		demo.recorder.delta_sequence = -1;

	// copy the accumulated multicast datagram
	// for this client out to the message
	if (demo.datagram.cursize && sv.mvdrecording)
	{
		dmsg = MVDWrite_Begin(dem_all, 0, demo.datagram.cursize);
		SZ_Write (dmsg, demo.datagram.data, demo.datagram.cursize);
		SZ_Clear (&demo.datagram);
	}

	while (demo.lastwritten < demo.parsecount-1 && sv.mvdrecording)
	{
		SV_MVDWritePackets(1);
	}

	demo.recorder.delta_sequence = demo.recorder.netchan.incoming_sequence&255;
	demo.recorder.netchan.incoming_sequence++;
	demo.frames[demo.parsecount&DEMO_FRAMES_MASK].time = demo.time = sv.time;

	if (sv.mvdrecording)
	{
		SV_WriteEntitiesToClient (&demo.recorder, &msg, true);
		SV_WriteMVDMessage(&msg, dem_all, 0, sv.time);
//		dmsg = MVDWrite_Begin(dem_all, 0, msg.cursize);
//		SZ_Write (dmsg, msg.data, msg.cursize);
	}

	demo.parsecount++;

//	MVDSetMsgBuf(demo.dbuf,&demo.frames[demo.parsecount&DEMO_FRAMES_MASK].buf);
}



/*
=======================
SV_SendMessagesToAll

FIXME: does this sequence right?
=======================
*/
void SV_SendMessagesToAll (void)
{
	int			i;
	client_t	*c;

	for (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)
		if (c->state)		// FIXME: should this only send to active?
			c->send_message = true;

	SV_SendClientMessages ();
}

#endif
