#include "quakedef.h"

#if !defined(CLIENTONLY) && defined(NETPREPARSE)
/*Testing this code should typically be done with the three following mods:
Prydon gate
Nexuiz
FrikBots (both NQ+QW).

If those 3 mods work, then pretty much everything else will
*/

//#define NEWPREPARSE

/*I want to rewrite this
to use something like
*/

#ifdef NEWPREPARSE
enum protocol_type
{
	PPT_FLOAT,
	PPT_ENT,
	PPT_COORD,
	PPT_ANGLE,
	PPT_BYTE,
	PPT_SHORT,
	PPT_LONG,
	PPT_STRING
};

#define PPT_POS PPT_COORD,PPT_COORD,PPT_COORD

union protocol_data
{
	float	fd;
	int		id;
	unsigned char	*str;
};

static union protocol_data pp_data[1024];
static enum protocol_type pp_temptypes[1024], *pp_types;
static unsigned char pp_sdata[4096];
static unsigned int pp_sdata_offset;
static int pp_dest;
static qboolean pp_fault;

static unsigned int pp_expectedelements;
static unsigned int pp_receivedelements;
static qboolean (*pp_curdecision) (enum protocol_type *pt, union protocol_data *pd);

static enum protocol_type pp_root[] = {PPT_BYTE};
static qboolean pp_root_decide(enum protocol_type *pt, union protocol_data *pd);

static void decide(enum protocol_type *types, unsigned int numtypes, qboolean (*newdecision) (enum protocol_type *pt, union protocol_data *pd))
{
	pp_types = types;
	pp_expectedelements = numtypes;
	pp_curdecision = newdecision;
}

static void pp_flush(multicast_t to, vec3_t origin, void (*flushfunc)(client_t *cl, sizebuf_t *msg, enum protocol_type *pt, union protocol_data *pd), enum protocol_type *pt, union protocol_data *pd)
{

	client_t	*client;
	qbyte		*mask;
	int			leafnum;
	int			j;
	qboolean	reliable;

	decide(pp_root, 1, pp_root_decide);



	{
		reliable = false;

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

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

		case MULTICAST_PVS_R:
			reliable = true;	// intentional fallthrough
		case MULTICAST_PVS:
			cluster = sv.world.worldmodel->funcs.LeafnumForPoint(sv.world.worldmodel, origin);
			if (cluster >= 0)
				mask = sv.pvs + cluster * 4*((sv.world.worldmodel->numclusters+31)>>5);
			else
				mask = NULL;
			break;

		default:
			return;
		}

		// send the data to all relevent clients
		for (j = 0, client = svs.clients; j < sv.allocated_client_slots; 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 (!((int)client->edict->xv->dimension_see & (int)pr_global_struct->dimension_send))
				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;
			}

			if (mask)
			{
				cluster = sv.world.worldmodel->funcs.ClusterForPoint (sv.world.worldmodel, client->edict->v->origin);
				if (cluster >= 0 && !(mask[leafnum>>3] & (1<<(leafnum&7)) ) )
				{
					continue;
				}
			}

inrange:
			if (client->protocol == SCP_BAD)
			{
				/*bot*/
				continue;
			}
			if (reliable)
				flushfunc(client, &client->netchan.message, pt, pd);
			else
				flushfunc(client, &client->datagram, pt, pd);
		}
	}
/*
	if (sv.mvdrecording && !with)	//mvds don't get the pext stuff
	{
		flushfunc(&dem.recorder,
		if (reliable)
		{
			MVDWrite_Begin(dem_all, 0, sv.multicast.cursize);
			SZ_Write(&demo.dbuf->sb, sv.multicast.data, sv.multicast.cursize);
		} else
			SZ_Write(&demo.datagram, sv.multicast.data, sv.multicast.cursize);
	}*/

	pp_sdata_offset = 0;
	pp_receivedelements = 0;
}

#define DECIDE(t) decide(t, sizeof(t)/sizeof(*t), t##_decide)
#define DECIDE2(t,f) decide(t, sizeof(t)/sizeof(*t), f)

static void pp_identity_flush(client_t *cl, sizebuf_t *msg, enum protocol_type *pt, union protocol_data *pd)
{
	unsigned int i;
	for (i = 0; i < pp_receivedelements; i++)
	{
		switch(pt[i])
		{
		case PPT_BYTE:
			MSG_WriteByte(msg, pd[i].id);
			break;
		case PPT_ENT:
			MSG_WriteEntity(msg, pd[i].id);
			break;
		case PPT_SHORT:
			MSG_WriteShort(msg, pd[i].id);
			break;
		case PPT_COORD:
			MSG_WriteCoord(msg, pd[i].fd);
			break;
		case PPT_ANGLE:
			MSG_WriteAngle(msg, pd[i].fd);
			break;
		case PPT_STRING:
			MSG_WriteString(msg, pd[i].str);
			break;
		}
	}
}

/*flush is our last attempt to cope with unrecognised/invalid messages, it will send stuff though as it was, and most likely get things wrong*/
void NPP_Flush(void)
{
	pp_fault = false;
	pp_flush(MULTICAST_ALL_R, NULL, pp_identity_flush, pp_types, pp_data);
}

static void pp_entry(int dest, enum protocol_type pt, union protocol_data pd)
{
	if (pp_receivedelements)
	{
		if (pp_dest != dest)
		{
			if (!pp_fault)
				Con_Printf("Preparse: MSG destination changed in the middle of a packet 0x%x.\n", pp_data[0].id);
			NPP_Flush();
		}
	}
	pp_dest = dest;

	if (pp_fault)
	{
		pp_types[pp_receivedelements] = pt;
		pp_data[pp_receivedelements] = pd;
		pp_receivedelements++;
	}
	else if (pp_types[pp_receivedelements] != pt)
	{
		Con_Printf("Preparse: Unmatched expectation at entry %i in svc 0x%x.\n", pp_receivedelements+1, pp_data[0].id);

		pp_types[pp_receivedelements] = pt;
		pp_data[pp_receivedelements] = pd;
		pp_receivedelements++;

faulted:
		pp_fault = true;
		if (pp_temptypes != pp_types)
		{
			memcpy(pp_temptypes, pp_types, sizeof(*pp_temptypes)*pp_receivedelements);
			pp_types = pp_temptypes;
		}
	}
	else
	{
		pp_data[pp_receivedelements++] = pd;

		if (pp_expectedelements == pp_receivedelements)
		{
			if (!pp_curdecision(pp_types, pp_data))
			{
				if (pp_types[pp_receivedelements-1] == PPT_BYTE)
					Con_Printf("Preparse: Unhandled byte %i@%i in svc%i.\n", pd.id, pp_receivedelements, pp_data[0].id);
				else
					Con_Printf("Preparse: Unhandled data @%i in svc%i.\n", pp_receivedelements, pp_data[0].id);
				goto faulted;
			}
		}
	}
}

void NPP_NQWriteByte(int dest, qbyte data)
{
	union protocol_data pd;
	pd.id = data;
	pp_entry(dest, PPT_BYTE, pd);
}
void NPP_NQWriteChar(int dest, char data)
{
	union protocol_data pd;
	pd.id = (unsigned char)data;
	pp_entry(dest, PPT_BYTE, pd);
}
void NPP_NQWriteShort(int dest, short data)
{
	union protocol_data pd;
	pd.id = (unsigned char)data;
	pp_entry(dest, PPT_SHORT, pd);
}
void NPP_NQWriteLong(int dest, long data)
{
	union protocol_data pd;
	pd.id = data;
	pp_entry(dest, PPT_LONG, pd);
}
void NPP_NQWriteAngle(int dest, float data)
{
	union protocol_data pd;
	pd.fd = data;
	pp_entry(dest, PPT_ANGLE, pd);
}
void NPP_NQWriteCoord(int dest, float data)
{
	union protocol_data pd;
	pd.fd = data;
	pp_entry(dest, PPT_COORD, pd);
}
void NPP_NQWriteString(int dest, char *data)
{
	unsigned int l;
	union protocol_data pd;
	l = strlen(data)+1;
	if (pp_sdata_offset + l > sizeof(pp_sdata))
		SV_Error("preparse string overflow\n");
	pd.str = pp_sdata + pp_sdata_offset;
	memcpy(pd.str, data, l);
	pp_entry(dest, PPT_STRING, pd);
}
void NPP_NQWriteEntity(int dest, short data)
{
	union protocol_data pd;
	pd.id = (unsigned short)data;
	pp_entry(dest, PPT_ENTITY, pd);
}

void NPP_QWWriteByte(int dest, qbyte data)
{
	NPP_NQWriteByte(dest, data);
}
void NPP_QWWriteChar(int dest, char data)
{
	NPP_NQWriteChar(dest, data);
}
void NPP_QWWriteShort(int dest, short data)
{
	NPP_NQWriteShort(dest, data);
}
void NPP_QWWriteLong(int dest, long data)
{
	NPP_NQWriteLong(dest, data);
}
void NPP_QWWriteAngle(int dest, float data)
{
	NPP_NQWriteAngle(dest, data);
}
void NPP_QWWriteCoord(int dest, float data)
{
	NPP_NQWriteCoord(dest, data);
}
void NPP_QWWriteString(int dest, char *data)
{
	NPP_NQWriteString(dest, data);
}
void NPP_QWWriteEntity(int dest, int data)
{
	NPP_NQWriteEntity(dest, data);
}











static enum protocol_type pp_svc_temp_entity_beam[] = {PPT_BYTE, PPT_BYTE, PPT_ENT, PPT_POS, PPT_POS};
static void pp_svc_temp_entity_beam_flush(client_t *cl, sizebuf_t *msg, enum protocol_type *pt, union protocol_data *pd)
{
	MSG_WriteByte(msg, pd[0].id);
	MSG_WriteByte(msg, pd[1].id);
	MSG_WriteEntity(msg, pd[2].id);
	MSG_WriteCoord(msg, pd[3].fd);
	MSG_WriteCoord(msg, pd[4].fd);
	MSG_WriteCoord(msg, pd[5].fd);
	MSG_WriteCoord(msg, pd[6].fd);
	MSG_WriteCoord(msg, pd[7].fd);
	MSG_WriteCoord(msg, pd[8].fd);
}
static qboolean pp_svc_temp_entity_beam_decide(enum protocol_type *pt, union protocol_data *pd)
{
	vec3_t org;
	org[0] = pd[3].fd;
	org[1] = pd[4].fd;
	org[2] = pd[5].fd;
	pp_flush(MULTICAST_PHS, org, pp_svc_temp_entity_beam_flush, pt, pd);
	return true;
}

static void pp_svc_temp_entity_gunshot_flush(client_t *cl, sizebuf_t *msg, enum protocol_type *pt, union protocol_data *pd)
{
	int offset = (progstype == PROG_QW)?3:2;
	int count = (offset == 3)?pd[2].id:1;

	while (count > 0)
	{
		MSG_WriteByte(msg, pd[0].id);
		MSG_WriteByte(msg, pd[1].id);
		if (cl->protocol == SCP_QUAKEWORLD)
		{
			if (count > 255)
			{
				MSG_WriteByte(msg, 255);
				count-=255;
			}
			else
			{
				MSG_WriteByte(msg, count);
				count = 0;
			}
		}
		else
			count--;
		MSG_WriteCoord(msg, pd[offset+0].fd);
		MSG_WriteCoord(msg, pd[offset+1].fd);
		MSG_WriteCoord(msg, pd[offset+2].fd);
	}
}
static qboolean pp_svc_temp_entity_gunshot(enum protocol_type *pt, union protocol_data *pd)
{
	vec3_t org;
	int offset = (progstype == PROG_QW)?3:2;
	org[0] = pd[offset+0].fd;
	org[1] = pd[offset+1].fd;
	org[2] = pd[offset+2].fd;
	pp_flush(MULTICAST_PHS, org, pp_svc_temp_entity_gunshot_flush, pt, pd);
	return true;
}

static qboolean pp_decide_pvs_2(enum protocol_type *pt, union protocol_data *pd)
{
	vec3_t org;
	org[0] = pd[2].fd;
	org[1] = pd[3].fd;
	org[2] = pd[4].fd;
	pp_flush(MULTICAST_PVS, org, pp_identity_flush, pt, pd);
	return true;
}
static qboolean pp_decide_phs_2(enum protocol_type *pt, union protocol_data *pd)
{
	vec3_t org;
	org[0] = pd[2].fd;
	org[1] = pd[3].fd;
	org[2] = pd[4].fd;
	pp_flush(MULTICAST_PHS, org, pp_identity_flush, pt, pd);
	return true;
}

static enum protocol_type pp_svc_temp_entity[] = {PPT_BYTE, PPT_BYTE};
static qboolean pp_svc_temp_entity_decide(enum protocol_type *pt, union protocol_data *pd)
{
	switch(pd[1].id)
	{
	case TE_LIGHTNING1:
	case TE_LIGHTNING2:
	case TE_LIGHTNING3:
		DECIDE(pp_svc_temp_entity_beam);
		return true;
	case TE_EXPLOSION:
	case TEDP_EXPLOSIONQUAD:
	case TE_SPIKE:
	case TE_SUPERSPIKE:
	case TEDP_SPIKEQUAD:
	case TEDP_SUPERSPIKEQUAD:
	case TEDP_SMALLFLASH:
		{
			static enum protocol_type fmt[] = {PPT_BYTE, PPT_BYTE, PPT_POS};
			DECIDE2(fmt, pp_decide_phs_2);
		}
		return true;

	case TEDP_GUNSHOTQUAD:
	case TE_TAREXPLOSION:
	case TE_WIZSPIKE:
	case TE_KNIGHTSPIKE:
	case TE_LAVASPLASH:
	case TE_TELEPORT:
		{
			static enum protocol_type fmt[] = {PPT_BYTE, PPT_BYTE, PPT_POS};
			DECIDE2(fmt, pp_decide_pvs_2);
		}
		return true;

	case TE_GUNSHOT:
		if (progstype == PROG_QW)
		{
			static enum protocol_type fmt[] = {PPT_BYTE, PPT_BYTE, PPT_BYTE, PPT_POS};
			DECIDE2(fmt, pp_svc_temp_entity_gunshot);
		}
		else
		{
			static enum protocol_type fmt[] = {PPT_BYTE, PPT_BYTE, PPT_POS};
			DECIDE2(fmt, pp_svc_temp_entity_gunshot);
		}
		return true;

	case 12:
		if (progstype == PROG_QW)
		{
			/*TEQW_BLOOD*/
		}
		else
		{
			/*TENQ_EXPLOSION2*/
		}
		return false;

	case 13:
		if (progstype == PROG_QW)
		{
			/*TEQW_LIGHTNINGBLOOD*/
		}
		else
		{
			/*TENQ_BEAM*/
		}
		return false;

	case TEDP_BLOOD:
	case TEDP_SPARK:
		{
			static enum protocol_type fmt[] = {PPT_BYTE, PPT_BYTE, PPT_POS, PPT_BYTE,PPT_BYTE,PPT_BYTE, PPT_BYTE};
			DECIDE2(fmt, pp_decide_pvs_2);
		}
		return true;

	case TE_BULLET:
	case TE_SUPERBULLET:

	case TE_RAILTRAIL:

		// hexen 2
	case TEH2_STREAM_CHAIN:
	case TEH2_STREAM_SUNSTAFF1:
	case TEH2_STREAM_SUNSTAFF2:
	case TEH2_STREAM_LIGHTNING:
	case TEH2_STREAM_COLORBEAM:
	case TEH2_STREAM_ICECHUNKS:
	case TEH2_STREAM_GAZE:
	case TEH2_STREAM_FAMINE:

	case TEDP_BLOODSHOWER:
	case TEDP_EXPLOSIONRGB:
	case TEDP_PARTICLECUBE:
	case TEDP_PARTICLERAIN: // [vector] min [vector] max [vector] dir [short] count [byte] color
	case TEDP_PARTICLESNOW: // [vector] min [vector] max [vector] dir [short] count [byte] color
	case TEDP_CUSTOMFLASH:
	case TEDP_FLAMEJET:
	case TEDP_PLASMABURN:
	case TEDP_TEI_G3:
	case TEDP_SMOKE:
	case TEDP_TEI_BIGEXPLOSION:
	case TEDP_TEI_PLASMAHIT:
	default:
		return false;
	}
}

qboolean pp_root_decide(enum protocol_type *pt, union protocol_data *pd)
{
	switch (pd[0].id)
	{
	case svc_temp_entity:
		DECIDE(pp_svc_temp_entity);
		return true;
	default:
		return false;
	}
}





#else

static sizebuf_t	*writedest;
static client_t		*cldest;
struct netprim_s *destprim;
static int majortype;
static int minortype;
static int protocollen;

static qbyte buffer[MAX_QWMSGLEN];
static int bufferlen;
static int nullterms;

static int multicastpos;	//writecoord*3 offset
static int multicasttype;
static int requireextension;
static qboolean ignoreprotocol;
static int te_515sevilhackworkaround;
extern qboolean ssqc_deprecated_warned;

#define svc_setfrags 14
#define svc_updatecolors 17

#ifdef HEXEN2
#define svch2_clearviewflags 41	//hexen2.
#define svch2_setangles_lerp 50
#endif

//these are present in the darkplaces engine.
//I wanna knick their mods.
#define svcdp_skybox	37

#define	svcdp_showlmp			35		// [string] slotname [string] lmpfilename [short] x [short] y
#define	svcdp_hidelmp			36		// [string] slotname

#define	TE_RAILTRAIL_NEH		15 // [vector] origin [coord] red [coord] green [coord] blue	(fixme: ignored)
#define	TE_EXPLOSION3_NEH		16 // [vector] origin [coord] red [coord] green [coord] blue	(fixme: ignored)
#define TE_LIGHTNING4_NEH		17 // [string] model [entity] entity [vector] start [vector] end
#define TE_SMOKE_NEH			18
#define TE_EXPLOSIONSMALL2		20	//	org.

client_t *Write_GetClient(void);
sizebuf_t *QWWriteDest (int dest);
#ifdef NQPROT
sizebuf_t *NQWriteDest (int dest);
#endif

void NPP_SetInfo(client_t *cl, char *key, char *value)
{
	int i;

	//its common for bots to be set up by renaming players randomly etc
	//such bots should appear that way in server browsers.
	//on the other hand, players might end up with postfixes on their names.
	//this will break clientside name/stats parsing of course... not much we can do about that other than fixing the mods.
	
	if (cl->netchan.remote_address.type != NA_INVALID)
	{
		int vlen = strlen(value);
		if (!strcmp(key, "name") && 7+vlen+1 <= sizeof(buffer))
		{
			if (progstype != PROG_QW)
			{
				memmove(buffer+7, value, vlen+1);
				buffer[0] = svc_setinfo;
				buffer[1] = cl - svs.clients;
				buffer[2] = 'n';
				buffer[3] = 'a';
				buffer[4] = 'm';
				buffer[5] = 'e';
				buffer[6] = 0;
				bufferlen = 7+ vlen+1;
				return;
			}
		}
	}

	bufferlen = 0;

	if (!strcmp(key, "colours"))
	{
		i = atoi(value);
		InfoBuf_SetKey (&cl->userinfo, "bottomcolor", va("%i", i&15));
		InfoBuf_SetKey (&cl->userinfo, "topcolor", va("%i", i>>4));
	}
	InfoBuf_SetKey (&cl->userinfo, key, value);
	if (!*InfoBuf_ValueForKey (&cl->userinfo, "name"))
		cl->name[0] = '\0';
	else // process any changed values
		SV_ExtractFromUserinfo (cl, false);

	i = cl - svs.clients;
	if (!strcmp(key, "colours"))
	{
		MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
		MSG_WriteByte (&sv.reliable_datagram, i);
		MSG_WriteString (&sv.reliable_datagram, "bottomcolor");
		MSG_WriteString (&sv.reliable_datagram, InfoBuf_ValueForKey(&cl->userinfo, "bottomcolor"));

		MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
		MSG_WriteByte (&sv.reliable_datagram, i);
		MSG_WriteString (&sv.reliable_datagram, "topcolor");
		MSG_WriteString (&sv.reliable_datagram, InfoBuf_ValueForKey(&cl->userinfo, "topcolor"));
	}
	else
	{
		MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
		MSG_WriteByte (&sv.reliable_datagram, i);
		MSG_WriteString (&sv.reliable_datagram, key);
		MSG_WriteString (&sv.reliable_datagram, InfoBuf_ValueForKey(&cl->userinfo, key));
	}
}

void NPP_NQFlush(void)
{
	if (!bufferlen)
		return;

	switch(majortype)
	{
	case svc_cdtrack:
		if (bufferlen!=protocollen)
			Con_Printf("NQFlush: svc_cdtrack wasn't the right length\n");
		else
			bufferlen-=1;
		break;
	case svc_updatefrags:
		break;
		//ignore these.
	case svc_print:
	case svcdp_skybox:
		bufferlen = 0;
		break;
	case svc_updatename:
		NPP_SetInfo(&svs.clients[buffer[1]], "name", buffer+2);
		break;
	case svc_updatecolors:
		NPP_SetInfo(&svs.clients[buffer[1]], "colours", va("%i", buffer[2]));
		break;
	case svc_intermission:
//		if (writedest == &sv.reliable_datagram)
		{
			client_t *cl;
			int i;
#ifdef HEXEN2
			char *h2finale = NULL;
			char *h2title = NULL;
			if (progstype == PROG_H2)
			{
				/*FIXME: hexen2 intermission+finale includes the viewheight. NQ does not, and QW has explicit position with scores but no finale*/
				/*hexen2 does something like this in the client, but we don't support those protocols, so translate to something usable*/
				char *title[13] = {"gfx/finale.lmp", "gfx/meso.lmp", "gfx/egypt.lmp", "gfx/roman.lmp", "gfx/castle.lmp", "gfx/castle.lmp", "gfx/end-1.lmp", "gfx/end-2.lmp", "gfx/end-3.lmp", "gfx/castle.lmp", "gfx/mpend.lmp", "gfx/mpmid.lmp", "gfx/end-3.lmp"};
				int lookup[13] = {394, 395, 396, 397, 358, strcmp(T_GetString(400+5*2+1), "BAD STRING")?400+5*2+1:400+4*2, 386+6, 386+7, 386+8, 391, 538, 545, 561};
				//5 is the demo sell screen, which changes depending on hexen2 vs portals.
				if (buffer[1] < 13)
				{
					h2title = title[buffer[1]];
					h2finale = T_GetString(lookup[buffer[1]]);
				}
			}
#endif

			for (i = 0, cl = svs.clients; i < sv.allocated_client_slots; i++, cl++)
			{
				if (cl->state == cs_spawned && ISQWCLIENT(cl))
				{
#ifdef HEXEN2
					if (h2finale)
					{
						ClientReliableCheckBlock(cl, 6 + strlen(h2title) + 3 + strlen(h2finale) + 1);
						ClientReliableWrite_Byte(cl, svc_finale);

						ClientReliableWrite_Byte(cl, '/');
						ClientReliableWrite_Byte(cl, 'F');
						ClientReliableWrite_Byte(cl, 'f');	//hexen2-style finale

						ClientReliableWrite_Byte(cl, '/');
						ClientReliableWrite_Byte(cl, 'I');
						ClientReliableWrite_SZ(cl, h2title, strlen(h2title));
						ClientReliableWrite_Byte(cl, ':');	//image

						ClientReliableWrite_Byte(cl, '/');
						ClientReliableWrite_Byte(cl, 'P');	//image should be a background.

						ClientReliableWrite_String(cl, h2finale);
					}
					else
#endif
						if (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
					{	//special intermission mode to leave the view attached to the viewentity (as required for nq - especially rogue's finale) instead of hacking it to some specific point
						ClientReliableCheckBlock(cl, 5);
						ClientReliableWrite_Byte(cl, svc_finale);
						ClientReliableWrite_String(cl, "/FI");
					}
					else
					{
						ClientReliableCheckBlock(cl, 16);
						ClientReliableWrite_Byte(cl, svc_intermission);
						ClientReliableWrite_Coord(cl, cl->edict->v->origin[0]);
						ClientReliableWrite_Coord(cl, cl->edict->v->origin[1]);
						ClientReliableWrite_Coord(cl, cl->edict->v->origin[2]+cl->edict->v->view_ofs[2]);
						ClientReliableWrite_Angle(cl, cl->edict->v->angles[0]);
						ClientReliableWrite_Angle(cl, cl->edict->v->angles[1]);
						ClientReliableWrite_Angle(cl, cl->edict->v->angles[2]);
					}
				}
			}
			bufferlen = 0;
			protocollen=0;
			writedest = NULL;
		}
		break;
//	case svc_finale:	//finale does exist in vanilla qw. apparently. hurrah.
//		bufferlen = 0;
//		break;
	case svc_cutscene:
		//finale with no text. and explicitly no flags (which mostly prevents the 'completed' banner appearing), should be equivelent.
		buffer[0] = svc_finale;
		buffer[1] = '/';
		buffer[2] = '.';
		buffer[3] = 0;
		bufferlen = 4;
		break;
	case svc_setview:
		requireextension = PEXT_SETVIEW;

		if (cldest)	//catch it to work with all clients
		{
			cldest->viewent = *(unsigned short*)&buffer[1];
//			bufferlen = 0;
			if (cldest->viewent == (cldest - svs.clients)+1)
				cldest->viewent = 0;	//self is the same as none
		}
//		bufferlen = 0;
		break;
	case svcdp_hidelmp:
		if (progstype == PROG_TENEBRAE)
		{
			bufferlen = 0;
			break;
		}
		requireextension = PEXT_SHOWPIC;
		buffer[0] = svcfte_hidepic;
		break;
	case svcdp_showlmp:
		if (progstype == PROG_TENEBRAE)
		{
			bufferlen = 0;
			break;
		}
		requireextension = PEXT_SHOWPIC;
		memmove(buffer+2, buffer+1, bufferlen-1);
		bufferlen++;
		buffer[0] = svcfte_showpic;
		buffer[1] = 0;	//top left
		//pad the bytes to shorts.
		buffer[bufferlen] = buffer[bufferlen-1];
		buffer[bufferlen-1] = 0;
		buffer[bufferlen+1] = 0;
		bufferlen+=2;
		break;

	case svcfte_cgamepacket:
		if (sv.csqcdebug)
		{
			/*shift the data up by two bytes*/
			memmove(buffer+3, buffer+1, bufferlen-1);

			/*add a length in the 2nd/3rd bytes*/
			buffer[1] = (bufferlen-1);
			buffer[2] = (bufferlen-1) >> 8;

			bufferlen += 2;
		}
		break;
	case svc_temp_entity:
		switch (buffer[1])
		{
		default:
			if (te_515sevilhackworkaround)
			{
				if (sv.csqcdebug)
				{
					/*shift the data up by two bytes, but don't care about the first byte*/
					memmove(buffer+3, buffer+1, bufferlen-1);

					/*add a length in the 2nd/3rd bytes, if needed*/
					buffer[1] = (bufferlen-1) & 0xff;
					buffer[2] = (bufferlen-1) >> 8;

					bufferlen += 2;
				}
				/*replace the svc itself*/
				buffer[0] = svcfte_cgamepacket;
			}
			break;
		case TE_EXPLOSION:
			if (writedest == &sv.datagram)
			{	//for old clients, use a te_explosion.
				//for clients that support it, use a TEQW_EXPLOSIONNOSPRITE
				vec3_t org;
				coorddata cd;
				if (sv.multicast.cursize + bufferlen > sv.multicast.maxsize)
					SV_FlushBroadcasts();
				SZ_Write(&sv.multicast, buffer, bufferlen);

				memcpy(&cd, &buffer[2+destprim->coordsize*0], destprim->coordsize);
				org[0] = MSG_FromCoord(cd, destprim->coordsize);
				memcpy(&cd, &buffer[2+destprim->coordsize*1], destprim->coordsize);
				org[1] = MSG_FromCoord(cd, destprim->coordsize);
				memcpy(&cd, &buffer[2+destprim->coordsize*2], destprim->coordsize);
				org[2] = MSG_FromCoord(cd, destprim->coordsize);

				requireextension = PEXT_TE_BULLET;
				SV_MulticastProtExt(org, multicasttype, pr_global_struct->dimension_send, 0, requireextension);
				buffer[1] = TEQW_EXPLOSION_NOSPRITE;
			}
			break;
		case TENQ_BEAM:
			requireextension = PEXT_TE_BULLET;
			//should translate it to lightning or something for old clients
			buffer[1] = TEQW_BEAM;
			break;
		case TENQ_EXPLOSION2:	//happens with rogue.
			requireextension = PEXT_TE_BULLET;
			if (writedest == &sv.datagram || writedest == &sv.multicast)
			{
				vec3_t org;
				coorddata cd;
				memcpy(&cd, &buffer[2+destprim->coordsize*0], destprim->coordsize);
				org[0] = MSG_FromCoord(cd, destprim->coordsize);
				memcpy(&cd, &buffer[2+destprim->coordsize*1], destprim->coordsize);
				org[1] = MSG_FromCoord(cd, destprim->coordsize);
				memcpy(&cd, &buffer[2+destprim->coordsize*2], destprim->coordsize);
				org[2] = MSG_FromCoord(cd, destprim->coordsize);

				buffer[1] = TE_EXPLOSION;					//use a generic crappy explosion
				SZ_Write(&sv.multicast, buffer, bufferlen-2);	//trim the two trailing colour bytes
				SV_MulticastProtExt(org, multicasttype, pr_global_struct->dimension_send, 0, requireextension);
			}
			buffer[1] = TEQW_EXPLOSION2;	//TENQ_EXPLOSION2 conflicts with TEQW_BLOOD
			break;
		}
		break;
	}
	if (ignoreprotocol)
	{
		ignoreprotocol=false;
		bufferlen = 0;
	}




	if (cldest)
	{
		if (!requireextension || cldest->fteprotocolextensions & requireextension)
		if (bufferlen && ISQWCLIENT(cldest))
		{
			ClientReliableCheckBlock(cldest, bufferlen);
			ClientReliableWrite_SZ(cldest, buffer, bufferlen);
		}
		cldest = NULL;
	}
	else
	{
		if (multicastpos && (writedest == &sv.datagram || writedest == &sv.multicast))
			writedest = &sv.multicast;
		else
			multicastpos = 0;
		if (bufferlen)
		{
			if (writedest->cursize + bufferlen > writedest->maxsize)
			{
				SV_FlushBroadcasts();
			}
			SZ_Write(writedest, buffer, bufferlen);
		}

		if (multicastpos)
		{
			vec3_t org;
			coorddata cd;

			memcpy(&cd, &buffer[multicastpos+destprim->coordsize*0], destprim->coordsize);
			org[0] = MSG_FromCoord(cd, destprim->coordsize);
			memcpy(&cd, &buffer[multicastpos+destprim->coordsize*1], destprim->coordsize);
			org[1] = MSG_FromCoord(cd, destprim->coordsize);
			memcpy(&cd, &buffer[multicastpos+destprim->coordsize*2], destprim->coordsize);
			org[2] = MSG_FromCoord(cd, destprim->coordsize);

			SV_MulticastProtExt(org, multicasttype, pr_global_struct->dimension_send, requireextension, 0);
		}
		writedest = NULL;
	}
	bufferlen = 0;
	protocollen=0;
	nullterms = 0;
	multicastpos=0;
	requireextension=0;
}
void NPP_NQCheckFlush(void)
{
	if (bufferlen >= protocollen && protocollen && !nullterms)
		NPP_NQFlush();
}

void NPP_NQCheckDest(int dest)
{
	if (dest == MSG_ONE)
	{
		client_t *cl = Write_GetClient();
		if (!cl)
		{
			Con_Printf("Not a client\n");
			return;
		}
		if (bufferlen && ((cldest && cldest != cl) || writedest))
		{
			Con_Printf("MSG destination changed in the middle of a packet %i.\n", (int)*buffer);
			NPP_NQFlush();
		}
		writedest = NULL;
		cldest = cl;
		destprim = &cldest->netchan.message.prim;
	}
	else
	{
		sizebuf_t	*ndest = QWWriteDest(dest);
		if (bufferlen && (cldest || (writedest && writedest != ndest)))
		{
			Con_DPrintf("NQCheckDest: MSG destination changed in the middle of a packet %i.\n", (int)*buffer);
			NPP_NQFlush();
		}
		cldest = NULL;
		writedest = ndest;
		destprim = &writedest->prim;
	}
}
void NPP_AddData(const void *data, int len)
{
	if (bufferlen+len > sizeof(buffer))
		Sys_Error("Preparse buffer was filled\n");
	memcpy(buffer+bufferlen, data, len);
	bufferlen+=len;
}

void NPP_NQWriteByte(int dest, qbyte data)	//replacement write func (nq to qw)
{
	NPP_NQCheckDest(dest);

#ifdef NQPROT
	if (dest == MSG_ONE)
	{
		client_t *cl = Write_GetClient();
		if (!cl)
		{
			Con_Printf("msg_entity: not a client\n");
			return;
		}
		else
		{
			if (cl->protocol == SCP_BAD) // is a bot
				return;
			else if (!ISQWCLIENT(cl))
			{
				ClientReliableCheckBlock(cl, sizeof(qbyte));
				ClientReliableWrite_Byte(cl, data);
				return;
			}
		}
	}
	else
		MSG_WriteByte (NQWriteDest(dest), data);
#endif

	if (!bufferlen)	//new message section
	{
		majortype = data;
		switch(data)
		{
		case svcdp_showlmp:
		case svcdp_hidelmp:
		case svc_sound:
			break;
		case svc_temp_entity:
			te_515sevilhackworkaround = false;
			break;
		case svc_setangle:
			protocollen = sizeof(qbyte) + destprim->anglesize*3;
			break;
		case svc_setview:
			protocollen = sizeof(qbyte)*1 + sizeof(short);
			break;
		case svc_updatename:
			nullterms = 1;
			break;
		case svc_setfrags:
			protocollen = 4;
			break;
		case svc_updatecolors:
			protocollen = 3;
			break;
		case svc_print:
			protocollen = 3;
			nullterms = 1;
			break;
		case svc_cdtrack:
			if (progstype == PROG_QW)
				protocollen = 2;
			else
				protocollen = 3;
			break;
		case svc_killedmonster:
			protocollen = 1;
			break;
		case svc_foundsecret:
			protocollen = 1;
			break;
		case svc_intermission:
			if (progstype == PROG_H2)
				protocollen = 2;
			else
				protocollen = 1;
			break;
		case svc_finale:
			nullterms = 1;
			protocollen = 2;
			break;
		case svcdp_skybox:
			protocollen = 2;//it's just a string
			break;
		case svcnq_updatestatlong:	//insta fixup
			data = svcqw_updatestatlong;	//ho hum... let it through (should check size later.)
			protocollen = 6;
			break;
		case svc_stufftext:
		case svc_centerprint:
			nullterms = 1;
			break;
#ifdef HEXEN2
		case svch2_clearviewflags:
			if (progstype == PROG_H2)
			{
				protocollen = 2;
				ignoreprotocol = true;
			}
			else
			{
				Con_DPrintf("NQWriteByte: bad protocol %i\n", (int)data);
				protocollen = sizeof(buffer);
			}
			break;
		case svch2_setangles_lerp:
			if (progstype == PROG_H2)
			{
				majortype = data = svc_setangle;
				protocollen = sizeof(qbyte) + destprim->anglesize*3;
			}
			else
			{
				Con_DPrintf("NQWriteByte: bad protocol %i\n", (int)data);
				protocollen = sizeof(buffer);
			}
			break;
#endif
		case svc_cutscene:
			nullterms = 1;
			break;
		case svcdp_updatestatbyte:
			protocollen = 3;
			ignoreprotocol = true;
			break;
		case svcfte_cgamepacket:
			protocollen = sizeof(buffer);
			break;
		default:
			if (data & 128)
			{
				majortype = 128;	//nq 'fast update'
				ignoreprotocol = true;
				//this handling is explicitly for hipnotic.
				//hipnotic sends a camera entity this way so that svc_setview knows where to position itself.
				//we emulate this with quakeworld protocols, so we don't necessarily need to support it other than to strip it.
				//we're probably screwed if we're acting as an nq server, but few people will use that.
			}
			else
			{
				Con_DPrintf("NQWriteByte: bad protocol %i\n", (int)data);
				protocollen = sizeof(buffer);
			}
			break;
		}
	}
	if (bufferlen == 1 && !protocollen)	//some of them depend on the following bytes for size.
	{
		switch(majortype)
		{
		case 128:
#ifdef NQPROT
			{
				unsigned int bits = buffer[0];
				protocollen = 1;
				if (bits & NQU_MOREBITS)
				{
					bits |= data<<8;
					protocollen+=1;
				}

				if (bits != (NQU_SIGNAL|NQU_MOREBITS|NQU_ORIGIN1|NQU_ORIGIN2|NQU_ORIGIN3|NQU_LONGENTITY))
				{	//this mask is what hipnotic uses. warn if anyone else tries it.
					Con_Printf("fast update will be stripped\n");
					PR_StackTrace(svprogfuncs, false);
				}
				if (bits & NQU_ORIGIN1)
					protocollen+=2;
				if (bits & NQU_ORIGIN2)
					protocollen+=2;
				if (bits & NQU_ORIGIN3)
					protocollen+=2;
				if (bits & NQU_ANGLE2)
					protocollen+=1;
				//nolerp no data
				if (bits & NQU_FRAME)
					protocollen+=1;

				if (bits & NQU_ANGLE1)
					protocollen+=1;
				if (bits & NQU_ANGLE3)
					protocollen+=1;
				if (bits & NQU_MODEL)
					protocollen+=1;
				if (bits & NQU_COLORMAP)
					protocollen+=1;
				if (bits & NQU_SKIN)
					protocollen+=1;
				if (bits & NQU_EFFECTS)
					protocollen+=1;
				if (bits & NQU_LONGENTITY)
					protocollen+=2;
				else
					protocollen+=1;
				if (bits & DPU_EXTEND1)
					Con_DPrintf("NQWriteByte: fast update with extension bits is not supported\n");
			}
#else
			ignoreprotocol = true;
#endif
			break;
		case svc_sound:
			protocollen = 5+destprim->coordsize*3;
			if (data & NQSND_VOLUME)
				protocollen++;
			if (data & NQSND_ATTENUATION)
				protocollen++;
			if (data & NQSND_LARGEENTITY)//extension
				protocollen++;
			if (data & NQSND_LARGESOUND)//extension
				protocollen++;
#ifdef warningmsg
#pragma warningmsg("NPP_NQWriteByte: this ignores SVC_SOUND from nq mods (nexuiz)")
#endif
			ignoreprotocol = true;
			break;
		case svc_temp_entity:
			switch(data)
			{
			case TENQ_BEAM:
			case TE_LIGHTNING1:
			case TE_LIGHTNING2:
			case TE_LIGHTNING3:
				multicastpos=4;
				multicasttype=MULTICAST_PHS;
				protocollen = destprim->coordsize*6+sizeof(short)+sizeof(qbyte)*2;
				break;
			case TE_GUNSHOT:
				multicastpos=3;
				multicasttype=MULTICAST_PVS;
				//we need to emit annother qbyte here. QuakeWorld has a number of particles.
				//emit it here and we don't need to remember to play with temp_entity later
				NPP_AddData(&data, sizeof(qbyte));
				data = 1;
				protocollen = destprim->coordsize*3+sizeof(qbyte)*3;
				break;
			case TE_EXPLOSION:
			case TE_SPIKE:
			case TE_SUPERSPIKE:
				multicastpos=2;
				multicasttype=MULTICAST_PHS;
				protocollen = destprim->coordsize*3+sizeof(qbyte)*2;
				break;
			case TE_LAVASPLASH:
				multicastpos=2;
				multicasttype=MULTICAST_ALL;
				protocollen = destprim->coordsize*3+sizeof(qbyte)*2;
				break;
			case TE_TAREXPLOSION:
			case TE_WIZSPIKE:
			case TE_KNIGHTSPIKE:
			case TE_TELEPORT:
				multicastpos=2;
				multicasttype=MULTICAST_PVS;
				protocollen = destprim->coordsize*3+sizeof(qbyte)*2;
				break;
			case TE_EXPLOSION3_NEH:
				protocollen = sizeof(qbyte) + destprim->coordsize*6;
				ignoreprotocol = true;
				break;
			case TENQ_EXPLOSION2:
				protocollen = sizeof(qbyte)*4 + destprim->coordsize*3;
				multicastpos=2;
				multicasttype=MULTICAST_PHS;
				break;
			case TE_EXPLOSIONSMALL2:
				data = TE_EXPLOSION;
				protocollen = sizeof(qbyte)*2 + destprim->coordsize*3;
				multicastpos=2;
				multicasttype=MULTICAST_PHS;
				break;
			case TE_RAILTRAIL:
				protocollen = destprim->coordsize*6+sizeof(qbyte)*1;
				multicastpos=2;
				multicasttype=MULTICAST_PHS;
				break;
			case TEH2_STREAM_LIGHTNING_SMALL:
			case TEH2_STREAM_CHAIN:
			case TEH2_STREAM_SUNSTAFF1:
			case TEH2_STREAM_SUNSTAFF2:
			case TEH2_STREAM_LIGHTNING:
			case TEH2_STREAM_ICECHUNKS:
			case TEH2_STREAM_GAZE:
			case TEH2_STREAM_FAMINE:
				protocollen = destprim->coordsize*6+sizeof(short)+sizeof(qbyte)*(2+2);
				multicastpos = 8;
				multicasttype=MULTICAST_PHS;
				break;
			case TEH2_STREAM_COLORBEAM:
				protocollen = destprim->coordsize*6+sizeof(short)+sizeof(qbyte)*(3+2);
				multicastpos = 8;
				multicasttype=MULTICAST_PHS;
				break;

			case TEDP_FLAMEJET:	//TE_FLAMEJET
				protocollen = destprim->coordsize*6 +sizeof(qbyte)*3;
				multicastpos = 2;
				multicasttype=MULTICAST_PVS;
				break;

			case TEDP_TEI_G3:
				protocollen = destprim->coordsize*9+sizeof(qbyte)*2;
				multicastpos = 2;
				multicasttype=MULTICAST_PHS;
				break;

			case TEDP_SMOKE:
				protocollen = destprim->coordsize*6+sizeof(qbyte)*3;
				multicastpos = 2;
				multicasttype=MULTICAST_PHS;
				break;

			case TEDP_TEI_BIGEXPLOSION:
				protocollen = destprim->coordsize*3+sizeof(qbyte)*2;
				multicastpos = 2;
				multicasttype=MULTICAST_PHS;
				break;

			case TEDP_TEI_PLASMAHIT:
				protocollen = destprim->coordsize*6+sizeof(qbyte)*3;
				multicastpos = 2;
				multicasttype=MULTICAST_PHS;
				break;

			default:
				protocollen = sizeof(buffer);
				if (dest == MSG_MULTICAST)
				{
					Con_DPrintf("NQWriteByte: unknown tempentity %i\n", data);
				}
				else
				{
					te_515sevilhackworkaround = true;
					if (!ssqc_deprecated_warned)
					{
						ssqc_deprecated_warned = true;
						Con_Printf("NQWriteByte: invalid tempentity %i. Future errors will be dprinted. You may need to enable sv_csqcdebug.\n", data);
						PR_StackTrace(svprogfuncs, false);
					}
					else
						Con_DPrintf("NQWriteByte: unknown tempentity %i\n", data);
				}
				break;
			}
			break;
		case svc_updatename:
		case svc_stufftext:
		case svc_centerprint:
			break;
		default:
			Con_Printf("NQWriteByte: Non-Implemented svc\n");
			protocollen = sizeof(buffer);
			break;
		}
	}
	if (!protocollen)	//these protocols take strings, and are thus dynamically sized.
	{
		switch(majortype)
		{
		case svc_updatename:
			if (bufferlen < 2)
				break;	//don't truncate the name if the mod is sending the slot number
		case svc_stufftext:
		case svc_centerprint:
		case svc_cutscene:
			if (!data)
				protocollen = bufferlen;
			break;
		case svcdp_hidelmp:
			//tenebrae compat:
			if (progstype == PROG_TENEBRAE)
			{
				//svc, coord6, byte, long, long, effectname
				if (bufferlen >= sizeof(qbyte)*2+destprim->coordsize*6+sizeof(int)*2 && !data)
					protocollen = bufferlen;
				break;
			}
			//nehahra/dp compat
			if (!data)
				protocollen = bufferlen;
			break;
		case svcdp_showlmp:
			//tenebrae compat:
			if (progstype == PROG_TENEBRAE)
			{
				//svc, coord3, byte, effectname
				if (bufferlen >= sizeof(qbyte)*2+destprim->coordsize*3 && !data)
					protocollen = bufferlen;
				break;
			}
			// [string] slotname [string] lmpfilename [byte] x [byte] y
			//note: nehara uses bytes!
			//and the rest of dp uses shorts. how nasty is that?
			if (!data)
			{	//second string, plus 2 bytes.
				int i;
				for (i = 0; i < bufferlen; i++)
					if (!buffer[i])
						protocollen = bufferlen+2;
			}
			break;
		}
	}

	NPP_AddData(&data, sizeof(qbyte));
	if (!data && bufferlen>=protocollen)
		if (nullterms)
			nullterms--;
	NPP_NQCheckFlush();
}

void NPP_NQWriteChar(int dest, char data)	//replacement write func (nq to qw)
{
	NPP_NQWriteByte(dest, (qbyte)data);
	return;
	/*
	NPP_NQCheckDest(dest);
	if (!bufferlen)
	{
		NPP_NQWriteByte(dest, (qbyte)data);
		return;
	}

#ifdef NQPROT
	if (dest == MSG_ONE) {
		client_t *cl = Write_GetClient();
		if (cl && cl->nqprot)
		{
			ClientReliableCheckBlock(cl, sizeof(char));
			ClientReliableWrite_Char(cl, data);
		}
	} else
		MSG_WriteChar (NQWriteDest(dest), data);
#endif

	NPP_AddData(&data, sizeof(char));
	NPP_NQCheckFlush();*/
}

void NPP_NQWriteShort(int dest, short data)	//replacement write func (nq to qw)
{
	union {
		qbyte b[2];
		short s;
	} u;
	u.s = LittleShort(data);
	NPP_NQWriteByte(dest, u.b[0]);
	NPP_NQWriteByte(dest, u.b[1]);
}

void NPP_NQWriteLong(int dest, long data)	//replacement write func (nq to qw)
{
	union {
		qbyte b[4];
		int l;
	} u;
	u.l = LittleLong(data);
	NPP_NQWriteByte(dest, u.b[0]);
	NPP_NQWriteByte(dest, u.b[1]);
	NPP_NQWriteByte(dest, u.b[2]);
	NPP_NQWriteByte(dest, u.b[3]);
}
void NPP_NQWriteFloat(int dest, float data)	//replacement write func (nq to qw)
{
	union {
		qbyte b[4];
		float f;
	} u;
	u.f = LittleFloat(data);
	NPP_NQWriteByte(dest, u.b[0]);
	NPP_NQWriteByte(dest, u.b[1]);
	NPP_NQWriteByte(dest, u.b[2]);
	NPP_NQWriteByte(dest, u.b[3]);
}
void NPP_NQWriteAngle(int dest, float in)	//replacement write func (nq to qw)
{
	char data = (int)(in*256/360) & 255;
	NPP_NQCheckDest(dest);

#ifdef NQPROT
	if (cldest)
	{
		if (cldest->protocol == SCP_BAD)
			return;
		else if (!ISQWCLIENT(cldest))
		{
			ClientReliableCheckBlock(cldest, sizeof(char));
			ClientReliableWrite_Angle(cldest, in);
			return;
		}
	}
	else
		MSG_WriteAngle (NQWriteDest(dest), in);
#endif

	if (!bufferlen)
	{
		Con_Printf("NQWriteAngle: Messages should start with WriteByte (last was %i)\n", majortype);
		PR_StackTrace(svprogfuncs, false);
	}

	if (destprim->anglesize==2)
	{
		coorddata cd = MSG_ToAngle(in, destprim->anglesize);
		NPP_AddData(&cd.b2, sizeof(cd.b2));
	}
	else
		NPP_AddData(&data, sizeof(char));
	NPP_NQCheckFlush();
}
void NPP_NQWriteCoord(int dest, float in)	//replacement write func (nq to qw)
{
	NPP_NQCheckDest(dest);

#ifdef NQPROT
	if (dest == MSG_ONE)
	{
		client_t *cl = Write_GetClient();
		if (!cl)
		{
			Con_Printf("msg_entity: not a client\n");
			return;
		}
		else
		{
			if (cl->protocol == SCP_BAD)
				return;
			else if (!ISQWCLIENT(cl))
			{
				ClientReliableCheckBlock(cl, sizeof(float));
				ClientReliableWrite_Coord(cl, in);
				return;
			}
		}
	}
	else
		MSG_WriteCoord (NQWriteDest(dest), in);
#endif

	if (!bufferlen)
	{
		Con_Printf("NQWriteCoord: Messages should start with WriteByte\n");
		PR_StackTrace(svprogfuncs, false);
	}

	if (destprim->coordsize==4)
	{
		float dataf = in;

		dataf = LittleFloat(dataf);
		NPP_AddData(&dataf, sizeof(float));
	}
	else
	{
		short datas = (int)(in*8)&0xffff;

		datas = LittleShort(datas);
		NPP_AddData(&datas, sizeof(short));
	}
	NPP_NQCheckFlush();
}
void NPP_NQWriteString(int dest, const char *data)	//replacement write func (nq to qw)
{
	NPP_NQCheckDest(dest);

#ifdef NQPROT
	if (dest == MSG_ONE)
	{
		client_t *cl = Write_GetClient();
		if (!cl)
		{
			Con_Printf("msg_entity: not a client\n");
			return;
		}
		else
		{
			if (cl->protocol == SCP_BAD)
				return;
			else if (!ISQWCLIENT(cl))
			{
				ClientReliableCheckBlock(cl, strlen(data)+1);
				ClientReliableWrite_String(cl, data);
				return;
			}
		}
	}
	else
		MSG_WriteString (NQWriteDest(dest), data);
#endif

	if (!bufferlen)
	{
		Con_Printf("NQWriteString: Messages should start with WriteByte\n");
		PR_StackTrace(svprogfuncs, false);
	}

	NPP_AddData(data, strlen(data)+1);

	if (!protocollen)	//these protocols take strings, and are thus dynamically sized.
	{
		switch(majortype)
		{
		case svc_updatename:
		case svc_stufftext:
		case svc_centerprint:
		case svc_cutscene:
			protocollen = bufferlen;
			break;
		}
	}

	if (nullterms)
		nullterms--;
	NPP_NQCheckFlush();
}
void NPP_NQWriteEntity(int dest, int data)	//replacement write func (nq to qw)
{
	NPP_NQCheckDest(dest);

	if (majortype == svc_temp_entity && data > 0 && data <= sv.allocated_client_slots)
		if (svs.clients[data-1].viewent)
			data = svs.clients[data-1].viewent;

#ifdef NQPROT
	if (dest == MSG_ONE)
	{
		client_t *cl = Write_GetClient();
		if (!cl)
		{
			Con_Printf("msg_entity: not a client\n");
			return;
		}
		else
		{
			if (cl->protocol == SCP_BAD)
				return;
			else if (!ISQWCLIENT(cl))
			{
				ClientReliableCheckBlock(cl, sizeof(short));
				ClientReliableWrite_Entity(cl, data);
				return;
			}
		}
	}
	else
		MSG_WriteEntity (NQWriteDest(dest), data);
#endif

	if (!bufferlen)
	{
		Con_Printf("NQWriteEntity: Messages should start with WriteByte\n");
		PR_StackTrace(svprogfuncs, false);
	}


	NPP_AddData(&data, sizeof(short));
	NPP_NQCheckFlush();
}
















#ifdef NQPROT

float NPP_ReadFloat(qbyte *buf)
{
	union
	{
		float f;
		qbyte b[4];
	} u;
	memcpy(u.b, buf, sizeof(u.f));
	return LittleFloat(u.f);
}
short NPP_ReadShort(qbyte *buf)
{
	union
	{
		short s;
		qbyte b[2];
	} u;
	memcpy(u.b, buf, sizeof(u.s));
	return LittleShort(u.s);
}
unsigned short NPP_ReadUShort(qbyte *buf)
{
	union
	{
		unsigned short s;
		qbyte b[2];
	} u;
	memcpy(u.b, buf, sizeof(u.s));
	return LittleShort(u.s);
}

//qw to nq translation is only useful if we allow nq clients to connect.

void NPP_QWFlush(void)
{
	qbyte b;
	if (!bufferlen)
		return;

	switch(majortype)
	{
	case svc_updatename:	//not a standard feature, but hey, if a progs wants bots.
		NPP_SetInfo(&svs.clients[buffer[1]], "name", buffer+2);
		break;
	case svc_updatecolors:
		NPP_SetInfo(&svs.clients[buffer[1]], "colours", va("%i", buffer[2]));
		break;
	case svc_cdtrack:
		if (bufferlen!=protocollen)
			Con_Printf("QWFlush: svc_cdtrack wasn't the right length\n");
		else
		{
			//qw cdtracks have only a loop byte.
			//nq has initial+loop values.
			b = (bufferlen==2)?buffer[1]:0;
			NPP_AddData(&b, sizeof(qbyte));
		}
		break;
		//ignore these.
	case svc_intermission:
//		if (writedest == &sv.reliable_datagram)
		{
			client_t *cl;
			int i;
			for (i = 0, cl = svs.clients; i < sv.allocated_client_slots; i++, cl++)
			{
				if (cl->state == cs_spawned && !ISQWCLIENT(cl))
				{
					vec3_t org, ang;

					if (cl->zquake_extensions & Z_EXT_SERVERTIME)
					{
						ClientReliableCheckBlock(cl, 6);
						ClientReliableWrite_Byte(cl, svcqw_updatestatlong);
						ClientReliableWrite_Byte(cl, STAT_TIME);
						ClientReliableWrite_Long(cl, (int)(sv.world.physicstime * 1000));
						cl->nextservertimeupdate = sv.world.physicstime+10;
					}

					ClientReliableCheckBlock(cl, 1);
					ClientReliableWrite_Byte(cl, svc_intermission);

					i = 1;
					if (destprim->coordsize == 4)
					{
						org[0] = NPP_ReadFloat(buffer+i+0);
						org[1] = NPP_ReadFloat(buffer+i+4);
						org[2] = NPP_ReadFloat(buffer+i+8);
						i += 12;
					}
					else
					{
						org[0] = NPP_ReadShort(buffer+i+0) / 8.0;
						org[1] = NPP_ReadShort(buffer+i+2) / 8.0;
						org[2] = NPP_ReadShort(buffer+i+4) / 8.0;
						i += 6;
					}

					if (destprim->anglesize == 2)
					{
						ang[0] = NPP_ReadUShort(buffer+i+0)*360.0/0xffff;
						ang[1] = NPP_ReadUShort(buffer+i+2)*360.0/0xffff;
						ang[2] = NPP_ReadUShort(buffer+i+4)*360.0/0xffff;
						i += 6;
					}
					else
					{
						ang[0] = (*(qbyte*)&buffer[i+0])*360.0/0xff;
						ang[1] = (*(qbyte*)&buffer[i+1])*360.0/0xff;
						ang[2] = (*(qbyte*)&buffer[i+2])*360.0/0xff;
						i += 3;
					}

					//move nq players to origin + angle
					VectorCopy(org, cl->edict->v->origin);
					VectorCopy(ang, cl->edict->v->angles);
					cl->edict->v->angles[0]*=r_meshpitch.value;
				}
			}
		}
		bufferlen = 0;
		protocollen=0;
		writedest = NULL;
//	case svc_finale:
//		bufferlen = 0;
		break;
	case svc_setview:
		requireextension = PEXT_SETVIEW;
//		bufferlen = 0;
		break;
	case svc_muzzleflash:
		if (bufferlen < 3)
			Con_Printf("Dodgy muzzleflash\n");
		else
		{
			short data;
			float org[3];
			edict_t *ent = EDICT_NUM_UB(svprogfuncs, LittleShort((*(short*)&buffer[1])));
			ent->muzzletime = sv.world.physicstime+host_frametime;	//flag the entity as needing an EF_MUZZLEFLASH
			VectorCopy(ent->v->origin, org);

			//we need to make a fake muzzleflash position for multicast to work properly.
			multicastpos = 4;
			data = LittleShort((short)(org[0]*8));
			NPP_AddData(&data, sizeof(short));
			data = LittleShort((short)(org[1]*8));
			NPP_AddData(&data, sizeof(short));
			data = LittleShort((short)(org[2]*8));
			NPP_AddData(&data, sizeof(short));
		}
		bufferlen = 0;	//can't send this to nq. :(
		break;
	case svc_smallkick:
	case svc_bigkick:
		bufferlen = 0;
		break;
	case svc_updateuserinfo:
		if (buffer[6])
		{
			unsigned int j = buffer[1];
			if (j < sv.allocated_client_slots)
			{
				InfoBuf_FromString(&svs.clients[j].userinfo, buffer+6, false);
				if (*InfoBuf_ValueForKey(&svs.clients[j].userinfo, "name"))
					SV_ExtractFromUserinfo(&svs.clients[j], false);
				else
					*svs.clients[j].name = '\0';
			}
		}
		else
		{
			unsigned int j = buffer[1];
			if (j < sv.allocated_client_slots)
			{
				*svs.clients[j].name = '\0';
				InfoBuf_Clear(&svs.clients[j].userinfo, true);
			}
		}

		break;
	case svcfte_cgamepacket:
		if (sv.csqcdebug)
		{
			/*shift the data up by two bytes*/
			memmove(buffer+3, buffer+1, bufferlen-1);

			/*add a length in the 2nd/3rd bytes*/
			buffer[1] = (bufferlen-1);
			buffer[2] = (bufferlen-1) >> 8;

			bufferlen += 2;
		}
		break;
	case svc_temp_entity:
		switch(minortype)
		{
		default:
			if (te_515sevilhackworkaround)
			{
				if (sv.csqcdebug)
				{
					/*shift the data up by two bytes*/
					memmove(buffer+3, buffer+1, bufferlen-1);

					/*add a length in the 2nd/3rd bytes*/
					buffer[1] = (bufferlen-1);
					buffer[2] = (bufferlen-1) >> 8;

					bufferlen += 2;
				}
				/*replace the svc itself*/
				buffer[0] = svcfte_cgamepacket;
			}
			break;
		case TEQW_LIGHTNINGBLOOD:
		case TEQW_BLOOD:		//needs to be converted to a particle
			{
				vec3_t org;
				qbyte count;
				qbyte colour;
				char dir[3];
				short s;
				int v;
				int i;
				qbyte svc;
				svc = svc_particle;
				org[0] = (*(short*)&buffer[multicastpos])/8.0f;
				org[1] = (*(short*)&buffer[multicastpos+2])/8.0f;
				org[2] = (*(short*)&buffer[multicastpos+4])/8.0f;
				count = bound(0, buffer[2]*20, 254);	//255 is taken to mean an explosion, for some reason.
				if (minortype == TEQW_LIGHTNINGBLOOD)
					colour = 225;
				else
					colour = 73;

				for (i=0 ; i<3 ; i++)
				{
					v = 0*16;
					if (v > 127)
						v = 127;
					else if (v < -128)
						v = -128;
					dir[i] = v;
				}

				bufferlen = 0;		//restart
				protocollen = 1000;

				multicastpos = 1;

				NPP_AddData(&svc, sizeof(qbyte));
				for (i = 0; i < 3; i++)
				{
					if (destprim->coordsize == 4)
						NPP_AddData(&org[i], sizeof(float));
					else
					{
						s = org[i]*8;
						NPP_AddData(&s, sizeof(short));
					}
				}
				NPP_AddData(&dir[0], sizeof(char));
				NPP_AddData(&dir[1], sizeof(char));
				NPP_AddData(&dir[2], sizeof(char));
				NPP_AddData(&count, sizeof(qbyte));
				NPP_AddData(&colour, sizeof(qbyte));
			}
			break;
		case TE_GUNSHOT:	//needs byte 3 removed
			if (bufferlen >= 3)
			{
				memmove(buffer+2, buffer+3, bufferlen-3);
				bufferlen--;
			}
			break;
		}
	}
	if (ignoreprotocol)
	{
		ignoreprotocol=false;
		bufferlen = 0;
	}




	if (cldest)
	{
		if (!requireextension || cldest->fteprotocolextensions & requireextension)
		if (bufferlen && !ISQWCLIENT(cldest))
		{
			ClientReliableCheckBlock(cldest, bufferlen);
			ClientReliableWrite_SZ(cldest, buffer, bufferlen);
		}
		cldest = NULL;
	}
	else
	{
		if (multicastpos && (writedest == &sv.nqdatagram || writedest == &sv.nqmulticast))
			writedest = &sv.nqmulticast;
		else
			multicastpos = 0;
		if (bufferlen)
			SZ_Write(writedest, buffer, bufferlen);

		if (multicastpos)
		{
			int qwsize;
			vec3_t org;
			coorddata cd;

			memcpy(&cd, &buffer[multicastpos+destprim->coordsize*0], destprim->coordsize);
			org[0] = MSG_FromCoord(cd, destprim->coordsize);
			memcpy(&cd, &buffer[multicastpos+destprim->coordsize*1], destprim->coordsize);
			org[1] = MSG_FromCoord(cd, destprim->coordsize);
			memcpy(&cd, &buffer[multicastpos+destprim->coordsize*2], destprim->coordsize);
			org[2] = MSG_FromCoord(cd, destprim->coordsize);

			qwsize = sv.multicast.cursize;
			sv.multicast.cursize = 0;
			SV_MulticastProtExt(org, multicasttype, pr_global_struct->dimension_send, requireextension, 0);
			sv.multicast.cursize = qwsize;
		}
		writedest = NULL;
	}
	bufferlen = 0;
	nullterms=0;
	protocollen=0;
	multicastpos=0;
	requireextension=0;
}
void NPP_QWCheckFlush(void)
{
	if (bufferlen >= protocollen && protocollen && !nullterms)
		NPP_QWFlush();
}

void NPP_QWCheckDest(int dest)
{
	if (dest == MSG_ONE)
	{
		client_t *cl = Write_GetClient();
		if (!cl)
		{
			Con_Printf("Not a client\n");
			return;
		}
		if (bufferlen && ((cldest && cldest != cl) || writedest))
		{
			Con_Printf("MSG destination changed in the middle of a packet %i.\n", (int)*buffer);
			NPP_QWFlush();
		}
		writedest = NULL;
		cldest = cl;
		destprim = &cldest->netchan.message.prim;
	}
	else
	{
		sizebuf_t	*ndest = NQWriteDest(dest);
		if (bufferlen && (cldest || (writedest && writedest != ndest)))
		{
			Con_DPrintf("QWCheckDest: MSG destination changed in the middle of a packet %i.\n", (int)*buffer);
			NPP_QWFlush();
		}
		cldest = NULL;
		writedest = ndest;
		destprim = &writedest->prim;
	}
}




void NPP_QWWriteByte(int dest, qbyte data)	//replacement write func (nq to qw)
{
	NPP_QWCheckDest(dest);

#ifdef NQPROT
	if (dest == MSG_ONE)
	{
		client_t *cl = Write_GetClient();
		if (!cl)
		{
			Con_Printf("msg_entity: not a client\n");
			return;
		}
		else
		{
			if (cl->protocol == SCP_BAD) // is a bot
				return;
			else if (ISQWCLIENT(cl))
			{
				ClientReliableCheckBlock(cl, sizeof(qbyte));
				ClientReliableWrite_Byte(cl, data);
				return;
			}
		}
	}
	else
		MSG_WriteByte (QWWriteDest(dest), data);
#endif
	if (!bufferlen)	//new message section
	{
		switch(data)
		{
		case svc_temp_entity:
			break;
		case svc_setangle:
			protocollen = sizeof(qbyte) + destprim->anglesize*3;
			break;
		case svc_setview:
			protocollen = sizeof(qbyte)*1 + sizeof(short);
			break;
		case svc_cdtrack:
			protocollen = sizeof(qbyte)*2;
			break;
		case svc_killedmonster:
			protocollen = 1;
			break;
		case svc_foundsecret:
			protocollen = 1;
			break;
		case svc_intermission:
			protocollen = 1 + destprim->coordsize*3 + destprim->anglesize*3;
			break;
		case svc_finale:
			nullterms = 1;
			protocollen = 2;
			break;
		case svc_updatepl:
		case svc_muzzleflash:
			protocollen = 3;
			break;
		case svc_smallkick:
		case svc_bigkick:
			protocollen = 1;
			break;
		case svc_print:
			protocollen = 2;
			nullterms=1;
			break;
		case svc_setinfo:
			protocollen = 2;
			nullterms = 2;
			break;
		case svc_centerprint:
		case svc_stufftext:
			protocollen = 1;
			nullterms=1;
			break;
		case svcqw_updatestatbyte:
			protocollen = 3;
			break;
		case svc_updateping:
		case svc_updatefrags:
			protocollen = 4;
			break;
		case svc_updateentertime:
			protocollen = 6;
			break;
		case svc_updateuserinfo:
			nullterms = 1;
			protocollen = 6+nullterms;
			break;
		case svcqw_updatestatlong:
			protocollen = 6;
			break;
		case svc_setpause:
			protocollen = 2;
			break;
		case svcfte_cgamepacket:
			protocollen = sizeof(buffer);
			break;
		default:
			Con_DPrintf("QWWriteByte: bad protocol %i\n", (int)data);
			protocollen = sizeof(buffer);
			break;
		}
		majortype = data;
	}
	if (bufferlen == 1 && !protocollen)	//some of them depend on the following bytes for size.
	{
		switch(majortype)
		{
		case svc_temp_entity:
			minortype = data;
			switch(data)
			{
			case TE_LIGHTNING1:
			case TE_LIGHTNING2:
			case TE_LIGHTNING3:
				multicastpos=4;
				multicasttype=MULTICAST_PHS;
				protocollen = destprim->coordsize*6+sizeof(short)+sizeof(qbyte)*2;
				break;
			case TEQW_BLOOD:		//needs to be converted to a particle
			case TE_GUNSHOT:	//needs qbyte 2 removed
				multicastpos=3;
				multicasttype=MULTICAST_PVS;
				protocollen = destprim->coordsize*3+sizeof(qbyte)*3;
				break;
			case TEQW_LIGHTNINGBLOOD:
			case TE_EXPLOSION:
			case TE_SPIKE:
			case TE_SUPERSPIKE:
				multicastpos=2;
				multicasttype=MULTICAST_PHS_R;
				protocollen = destprim->coordsize*3+sizeof(qbyte)*2;
				break;
			case TE_TAREXPLOSION:
			case TE_WIZSPIKE:
			case TE_KNIGHTSPIKE:
			case TE_LAVASPLASH:
			case TE_TELEPORT:
				multicastpos=2;
				multicasttype=MULTICAST_PVS;
				protocollen = destprim->coordsize*3+sizeof(qbyte)*2;
				break;
			case TE_RAILTRAIL:
				multicastpos=1;
				multicasttype=MULTICAST_PVS;
				protocollen = destprim->coordsize*3+sizeof(qbyte)*1;
				break;
			default:
				protocollen = sizeof(buffer);
				Con_Printf("QWWriteByte: bad tempentity - %i\n", data);
				break;
			}
			break;
		default:
			Con_Printf("QWWriteByte: Non-Implemented svc\n");
			protocollen = sizeof(buffer);
			break;
		}
	}

	NPP_AddData(&data, sizeof(qbyte));
	if (!data && bufferlen>=protocollen)
		if (nullterms)
			nullterms--;
	NPP_QWCheckFlush();
}

void NPP_QWWriteChar(int dest, char data)	//replacement write func (nq to qw)
{
	NPP_QWWriteByte(dest, (qbyte)data);
}

void NPP_QWWriteShort(int dest, short data)	//replacement write func (nq to qw)
{
	union {
		qbyte b[2];
		short s;
	} u;
	if (bufferlen == 2 && majortype == svc_temp_entity && (minortype == TE_LIGHTNING1 || minortype == TE_LIGHTNING2 || minortype == TE_LIGHTNING3))
		NPP_QWWriteEntity(dest, data);
	else
	{
		u.s = LittleShort(data);
		NPP_QWWriteByte(dest, u.b[0]);
		NPP_QWWriteByte(dest, u.b[1]);
	}
}

void NPP_QWWriteFloat(int dest, float data)	//replacement write func (nq to qw)
{
	union {
		qbyte b[4];
		float f;
	} u;
	u.f = LittleFloat(data);
	NPP_QWWriteByte(dest, u.b[0]);
	NPP_QWWriteByte(dest, u.b[1]);
	NPP_QWWriteByte(dest, u.b[2]);
	NPP_QWWriteByte(dest, u.b[3]);
}

void NPP_QWWriteLong(int dest, long data)	//replacement write func (nq to qw)
{
	union {
		qbyte b[4];
		int l;
	} u;
	u.l = LittleLong(data);
	NPP_QWWriteByte(dest, u.b[0]);
	NPP_QWWriteByte(dest, u.b[1]);
	NPP_QWWriteByte(dest, u.b[2]);
	NPP_QWWriteByte(dest, u.b[3]);
}
void NPP_QWWriteAngle(int dest, float in)	//replacement write func (nq to qw)
{
	if (destprim->anglesize==1)
	{
		char data = (int)(in*256/360) & 255;
		NPP_QWWriteChar(dest, data);
	}
	else
	{
		short data = (int)(in*0xffff/360) & 0xffff;
		NPP_QWWriteShort(dest, data);
	}
}
void NPP_QWWriteCoord(int dest, float in)	//replacement write func (nq to qw)
{
	if (destprim->coordsize==4)
	{
		NPP_QWWriteFloat(dest, in);
	}
	else
	{
		short datas = (int)(in*8);
		NPP_QWWriteShort(dest, datas);
	}
}
void NPP_QWWriteString(int dest, const char *data)	//replacement write func (nq to qw)
{
#if 0
	//the slow but guarenteed routine
	while(*data)
		NPP_QWWriteByte(dest, *data++);
	NPP_QWWriteByte(dest, 0);	//and the null terminator
#else
	//the fast-track, less reliable routine


	NPP_QWCheckDest(dest);

#ifdef NQPROT
	if (dest == MSG_ONE)
	{
		client_t *cl = Write_GetClient();
		if (!cl)
		{
			Con_Printf("msg_entity: not a client\n");
			return;
		}
		else
		{
			if (cl->protocol == SCP_BAD)
				return;
			else if (ISQWCLIENT(cl))
			{
				ClientReliableCheckBlock(cl, strlen(data)+1);
				ClientReliableWrite_String(cl, data);
				return;
			}
		}
	}
	else
		MSG_WriteString (QWWriteDest(dest), data);
#endif

	if (!bufferlen)
	{
		Con_Printf("QWWriteString: Messages should start with WriteByte (last was %i)\n", majortype);
		PR_StackTrace(svprogfuncs, false);
	}

	NPP_AddData(data, strlen(data)+1);
	if (nullterms)
		nullterms--;
	NPP_QWCheckFlush();
#endif
}
void NPP_QWWriteEntity(int dest, int data)	//replacement write func (nq to qw)
{
	if (data >= 0x8000)
	{
		NPP_QWWriteByte(dest, ((data>> 0) & 0xff));
		NPP_QWWriteByte(dest, ((data>> 8) & 0x7f) | 0x80);
		NPP_QWWriteByte(dest, ((data>>15) & 0xff));
	}
	else
	{
		NPP_QWWriteByte(dest, (data>>0) & 0xff);
		NPP_QWWriteByte(dest, (data>>8) & 0x7f);
	}
}
#endif



















#ifdef SERVER_DEMO_PLAYBACK





#define DF_ORIGIN	1
#define DF_ANGLES	(1<<3)
#define DF_EFFECTS	(1<<6)
#define DF_SKINNUM	(1<<7)
#define DF_DEAD		(1<<8)
#define DF_GIB		(1<<9)
#define DF_WEAPONFRAME (1<<10)
#define DF_MODEL	(1<<11)

#define	PF_MSEC			(1<<0)
#define	PF_COMMAND		(1<<1)
#define	PF_VELOCITY1	(1<<2)
#define	PF_VELOCITY2	(1<<3)
#define	PF_VELOCITY3	(1<<4)
#define	PF_MODEL		(1<<5)
#define	PF_SKINNUM		(1<<6)
#define	PF_EFFECTS		(1<<7)
#define	PF_WEAPONFRAME	(1<<8)		// only sent for view player
#define	PF_DEAD			(1<<9)		// don't block movement any more
#define	PF_GIB			(1<<10)		// offset the view height differently




int sv_demo_spikeindex;




void NPP_MVDFlush(void)
{
	if (!bufferlen)
		return;

	switch(majortype)
	{
	case svc_spawnbaseline:
		SV_FlushDemoSignon();
		cldest = NULL;
		writedest = &sv.demosignon;

		if (1)
		{
			int entnum, i;
			mvdentity_state_t *ent;

			if (!sv.demobaselines)
			{
				sv.demobaselines = (mvdentity_state_t*)BZ_Malloc(sizeof(mvdentity_state_t)*MAX_EDICTS);
				sv.demostatevalid = true;
			}
			entnum = buffer[1] + (buffer[2]<<8);
//			if (entnum < svs.allocated_client_slots)
//				break;
			ent = &sv.demobaselines[entnum];

			ent->modelindex = buffer[3];
			ent->frame = buffer[4];
			ent->colormap = buffer[5];
			ent->skinnum = buffer[6];

			for (i=0 ; i<3 ; i++)
			{
				ent->origin[i] = (short)(buffer[7+i*3] + (buffer[8+i*3]<<8))/8.0f;
				ent->angles[i] = buffer[9+i*3];
			}
		}
		break;
	case svc_spawnstatic:
		SV_FlushDemoSignon();
		cldest = NULL;
		writedest = &sv.demosignon;
		break;
	case svc_modellist:
		ignoreprotocol=true;
		{
			int i;
			int rpos, s;
			i = buffer[1];
			rpos = 2;
			while (1)
			{
				i++;
				s = rpos;
				while(buffer[rpos])
					rpos++;

				if (rpos == s)	//end
					break;

				strcpy(sv.demmodel_precache[i], buffer+s);
				if (!strcmp(sv.demmodel_precache[i], "progs/spike.mdl"))
					sv_demo_spikeindex = i;
				rpos++;
			}
		}
		break;
	case svc_soundlist:
		ignoreprotocol=true;
		{
			int i;
			int rpos, s;
			i = buffer[1];
			rpos = 2;
			while (1)
			{
				i++;
				s = rpos;
				while(buffer[rpos])
					rpos++;

				if (rpos == s)	//end
					break;

				strcpy(sv.demsound_precache[i], buffer+s);
				rpos++;
			}
		}
		break;
	case svc_serverdata:
		{
			int i;
			sv_demo_spikeindex = 0;	//new map, new precaches.

			i = 9;
			strcpy(sv.demgamedir, buffer+i);
			for(;i < bufferlen && buffer[i];i++)
				;
			i++;
			i+=4;
			Q_strncpyz(sv.demfullmapname, buffer+i, sizeof(sv.demfullmapname));
			for(;i < bufferlen && buffer[i];i++)
				;
			i+=4*10;
		}
		ignoreprotocol=true;
		break;
	case svc_lightstyle:
		sv.demolightstyles[buffer[1]] = Hunk_Alloc(strlen(buffer+2)+1);
		strcpy(sv.demolightstyles[buffer[1]], buffer+2);
		break;

	case svc_updatestat:
	case svc_updatestatlong:	//make sure we update the running players stats properly.
		{
			int v, s;
			if (majortype == svc_updatestat)
				v = buffer[2];
			else
				v = buffer[2] | (buffer[3]<<8) | (buffer[4]<<16) | (buffer[5]<<24);
			s = buffer[1];

			if (sv.lastto < 32)	//dem_multicast could be used at the wrong time...
				sv.recordedplayer[sv.lastto].stats[s] = v;

			ignoreprotocol=true;
		}
		break;
	case svc_packetentities:
	case svc_deltapacketentities:	//read the delta in to the array.
		ignoreprotocol=true;		//a bug exists in that the delta MUST have been reliably recorded.
		{
			int i;
			int entnum;
			mvdentity_state_t *ents;
			unsigned short s;
			if (!sv.demostate)
			{
				sv.demostate = BZ_Malloc(sizeof(mvdentity_state_t)*MAX_EDICTS);
				sv.demostatevalid = true;
			}
			i = majortype-svc_packetentities+1;
			while (1)
			{
				s = buffer[i] + buffer[i+1]*256;
				i+=2;
				if (!s)
				{
					break;
				}
				else
				{
					entnum = s&511;
					s &= ~511;

					if (entnum > sv.demomaxents)
						sv.demomaxents = entnum;

					ents = &sv.demostate[entnum];
					if (s & U_REMOVE)
					{	//this entity went from the last packet
						ents->modelindex = 0;
						ents->effects = 0;
						continue;
					}

					if (!ents->modelindex && !ents->effects && sv.demobaselines)
					{	//new entity, reset to baseline
						memcpy(ents, &sv.demobaselines[entnum], sizeof(mvdentity_state_t));
					}

					if (s & U_MOREBITS)
					{
						s |= buffer[i];
						i++;
					}

					if (s & U_MODEL)
					{
						ents->modelindex = buffer[i];
						i++;
					}

					if (s & U_FRAME)
					{
						ents->frame = buffer[i];
						i++;
					}

					if (s & U_COLORMAP)
					{
						ents->colormap = buffer[i];
						i++;
					}

					if (s & U_SKIN)
					{
						ents->skinnum = buffer[i];
						i++;
					}

					if (s & U_EFFECTS)
					{
						ents->effects = buffer[i];
						i++;
					}

					if (s & U_ORIGIN1)
					{
						ents->origin[0] = (short)(buffer[i]+buffer[i+1]*256) /8.0f;
						i+=2;
					}

					if (s & U_ANGLE1)
					{
						ents->angles[0] = (unsigned char)(buffer[i]);//	* (360.0/256);
						i++;
					}

					if (s & U_ORIGIN2)
					{
						ents->origin[1] = (short)(buffer[i]+buffer[i+1]*256) /8.0f;
						i+=2;
					}

					if (s & U_ANGLE2)
					{
						ents->angles[1] = (unsigned char)(buffer[i]);//	* (360.0/256);
						i++;
					}

					if (s & U_ORIGIN3)
					{
						ents->origin[2] = (short)(buffer[i]+buffer[i+1]*256) /8.0f;
						i+=2;
					}

					if (s & U_ANGLE3)
					{
						ents->angles[2] = (unsigned char)(buffer[i]);//	* (360.0/256);
						i++;
					}
				}
			}
		}
		break;
	case svc_playerinfo:
		ignoreprotocol=true;
		{
			int i, j;
			unsigned short flags;
			mvdentity_state_t *ents;
			int playernum;
			vec3_t oldang;

			if (!sv.demostate)
				sv.demostate = BZ_Malloc(sizeof(entity_state_t)*MAX_EDICTS);

			sv.demostatevalid = true;

			flags = buffer[2] + buffer[3]*256;

			playernum = buffer[1];
			ents = &sv.demostate[playernum+1];
			ents->frame = buffer[4];

//			ents->colormap=playernum+1;

			VectorCopy(ents->origin, sv.recordedplayer[playernum].oldorg);
			VectorCopy(ents->angles, sv.recordedplayer[playernum].oldang);

			i = 5;
			for (j=0 ; j<3 ; j++)
				if (flags & (DF_ORIGIN << j))
				{
					ents->origin[j] = (signed short)(buffer[i] + (buffer[i+1]<<8))/8.0f;
					i+=2;
				}

			VectorCopy(ents->angles, oldang);
			for (j=0 ; j<3 ; j++)
				if (flags & (DF_ANGLES << j))
				{
					//FIXME: angle truncation here.
					ents->angles[j] = (char)((int)(buffer[i] + (buffer[i+1]<<8))/256.0f);
					i+=2;
				}

			if (flags & (DF_ANGLES << 0))	//'stupid quake bug' I believe is the correct quote...
				ents->angles[0] = ents->angles[0]*-1/3.0f; //also scale pitch down as well as invert

			if (flags & DF_MODEL)
			{
				ents->modelindex = buffer[i];
				i+=1;
			}
			if (flags & DF_SKINNUM)
			{
				ents->skinnum = buffer[i];
				i+=1;
			}
			if (flags & DF_EFFECTS)
			{
				ents->effects = buffer[i];
				i+=1;
			}
			if (flags & DF_WEAPONFRAME)
			{	//mvds are deltas remember, this is really the only place where that fact is all that important.
				sv.recordedplayer[playernum].weaponframe = buffer[i];
				i+=1;
			}

			sv.recordedplayer[playernum].updatetime = realtime;

			ignoreprotocol=true;
		}
		break;

	case svc_nails:
	case svc_nails2:
		sv.numdemospikes = buffer[1];
		{
			qboolean hasid = (majortype==svc_nails2);
			char *bits;
			int i;
			bits = buffer+2;
			for (i = 0; i < sv.numdemospikes; i++)
			{
				if (hasid)
				{
					sv.demospikes[i].id = *bits;
					bits++;
				}
				else
					sv.demospikes[i].id = 0;
				sv.demospikes[i].modelindex = sv_demo_spikeindex;
				sv.demospikes[i].org[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096;
				sv.demospikes[i].org[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096;
				sv.demospikes[i].org[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096;
				sv.demospikes[i].pitch = (bits[4]>>4);
				sv.demospikes[i].yaw = bits[5];

				bits+=6;
			}
		}
		ignoreprotocol=true;
		break;

	case svc_stufftext:
		ignoreprotocol = true;
		Cmd_TokenizeString(buffer+1, false, false);
		if (!stricmp(Cmd_Argv(0), "fullserverinfo"))
		{
			Q_strncpyz(sv.demoinfo, Cmd_Argv(1), sizeof(sv.demoinfo));
			break;
		}
		break;
	case svc_updateping:
		{
			int j;
			j = buffer[1];
			sv.recordedplayer[j].ping = buffer[2] | (buffer[3]<<8);
		}
		break;
	case svc_updatepl:
		{
			int j;
			j = buffer[1];
			sv.recordedplayer[j].pl = buffer[2] | (buffer[3]<<8);
		}
		break;
	case svc_updatefrags:
		{
			int j;
			j = buffer[1];
			sv.recordedplayer[j].frags = buffer[2] | (buffer[3]<<8);
		}
		break;
	case svc_setinfo:
//		ignoreprotocol = true;
		{
			int j;
			j = buffer[1];
			Info_SetValueForStarKey(sv.recordedplayer[j].userinfo, buffer+2, buffer+2+strlen(buffer+2)+1, sizeof(sv.recordedplayer[j].userinfo));
		}
		break;
	case svc_updateuserinfo:
//		ignoreprotocol = true;
		{
			unsigned int j;
			j = buffer[1];
			if (j < sv.allocated_client_slots)
			{
				sv.recordedplayer[j].userid = buffer[2] | (buffer[3]<<8) | (buffer[4]<<16) | (buffer[5]<<24);
				Q_strncpyz(sv.recordedplayer[j].userinfo, buffer+6, sizeof(sv.recordedplayer[j].userinfo));
			}
		}
		break;
	case svc_setangle:	//FIXME: forward on to trackers.
		ignoreprotocol = true;
		break;

	case svc_disconnect:
		svs.spawncount++;
		SV_BroadcastCommand("changing\n");
		SV_BroadcastCommand ("reconnect\n");
		ignoreprotocol = true;
		break;
	}
	if (ignoreprotocol)
	{
		ignoreprotocol=false;
		bufferlen = 0;
	}




	if (cldest)
	{
		if (!requireextension || cldest->fteprotocolextensions & requireextension)
		if (bufferlen)
		{
			ClientReliableCheckBlock(cldest, bufferlen);
			ClientReliableWrite_SZ(cldest, buffer, bufferlen);
		}
		cldest = NULL;
	}
	else
	{
		if (multicastpos && (writedest == &sv.datagram || writedest == &sv.multicast))
			writedest = &sv.multicast;
		else
			multicastpos = 0;

		if (writedest == &sv.reliable_datagram)
		{
			writedest = &sv.multicast;
			multicasttype = MULTICAST_ALL_R;
		}

		if (bufferlen)
			SZ_Write(writedest, buffer, bufferlen);

		if (multicastpos)
		{
			vec3_t org;
			coorddata cd;

			memcpy(&cd, &buffer[multicastpos+sizeofcoord*0], sizeofcoord);
			org[0] = MSG_FromCoord(cd, sizeofcoord);
			memcpy(&cd, &buffer[multicastpos+sizeofcoord*1], sizeofcoord);
			org[1] = MSG_FromCoord(cd, sizeofcoord);
			memcpy(&cd, &buffer[multicastpos+sizeofcoord*2], sizeofcoord);
			org[2] = MSG_FromCoord(cd, sizeofcoord);

			SV_MulticastProtExt(org, multicasttype, FULLDIMENSIONMASK, requireextension, 0);
		}
		else if (writedest == &sv.multicast)
			SV_MulticastProtExt(vec3_origin, multicasttype, FULLDIMENSIONMASK, requireextension, 0);
		writedest = NULL;
	}
	bufferlen = 0;
	protocollen=0;
	multicastpos=0;
	requireextension=0;
}
void NPP_MVDForceFlush(void)
{
	if (bufferlen)
	{
		Con_Printf("Forcing flush mvd->qw prot\n");
		Con_Printf("(last was %i)\n", (int)majortype);
		NPP_MVDFlush();
	}
}
void NPP_MVDCheckFlush(void)
{
	if (bufferlen >= protocollen && protocollen)
		NPP_MVDFlush();
}

void NPP_MVDCheckDest(client_t *cl, int broadcast)
{
	if (!broadcast)
	{
		if (!cl)
		{
			Con_Printf("Not a client\n");
			return;
		}
		if ((cldest && cldest != cl) || writedest)
		{
			Con_Printf("MSG destination changed in the middle of a packet.\n");
			NPP_MVDFlush();
		}
		cldest = cl;
	}
	else
	{
		sizebuf_t	*ndest = &sv.reliable_datagram;
		if (cldest || (writedest && writedest != ndest))
		{
			Con_Printf("MSG destination changed in the middle of a packet.\n");
			NPP_MVDFlush();
		}
		writedest = ndest;
	}
}

void NPP_MVDWriteByte(qbyte data, client_t *to, int broadcast)	//replacement write func (nq to qw)
{
	int i;
	NPP_MVDCheckDest(to, broadcast);

	if (!bufferlen)	//new message section
	{
		switch(data)
		{
		case svc_temp_entity://depends on following bytes
			break;
		case svc_serverinfo:
		case svc_print:
		case svc_sound:
		case svc_serverdata:
		case svc_stufftext:
		case svc_modellist:
		case svc_soundlist:
		case svc_updateuserinfo:
		case svc_playerinfo:
		case svc_packetentities:
		case svc_deltapacketentities:
		case svc_lightstyle:
		case svc_nails:
		case svc_nails2:
		case svc_centerprint:
		case svc_setinfo:
			break;
		case svc_setangle:
			if (sv.mvdplayback)
				protocollen = sizeof(qbyte)*5;	//MVDSV writes an extra client num too.
			else
				protocollen = sizeof(qbyte)*4;	//MVDSV writes an extra client num too.
			break;
		case svc_setview:
			protocollen = sizeof(qbyte)*1 + sizeof(short);
			break;
		case svc_cdtrack:
			protocollen = sizeof(qbyte)*2;
			break;
		case svc_killedmonster:
			protocollen = 1;
			break;
		case svc_foundsecret:
			protocollen = 1;
			break;
//		case svc_intermission:
//			protocollen = 1;
//			break;
//		case svc_finale:
//			protocollen = 2;
//			break;
		case svc_muzzleflash:
			protocollen = 3;
			break;
		case svc_smallkick:
		case svc_bigkick:
			protocollen = 1;
			break;

		case svc_spawnstaticsound:
			protocollen = 10;
			break;
		case svc_spawnstatic:
			protocollen = 14;
			break;
		case svc_spawnbaseline:
			protocollen = 16;
			break;
		case svc_updateping:
		case svc_updatefrags:
			protocollen = 4;
			break;
		case svc_updatestat:
		case svc_updatepl:
			protocollen = 3;
			break;
		case svc_updatestatlong:
			protocollen = 6;
			break;
		case svc_updateentertime:
			protocollen = 6;
			break;
		case svc_intermission:
			protocollen = 10;
			break;
		case svc_disconnect:
			protocollen = 2+strlen("EndOfDemo");
			break;
		case svc_chokecount:
			protocollen = 2;
			break;
		case svc_damage:
			protocollen = 9;
			break;
		default:
			Con_Printf("mvd: bad protocol %i\n", (int)data);
			Con_Printf("(last was %i)\n", (int)majortype);
			protocollen = sizeof(buffer);
			net_message.cursize=0;
			data = svc_nop;
			protocollen = 1;
			break;
		}
		majortype = data;
	}
	else if (!protocollen)
	{
		switch(majortype)
		{
		case svc_temp_entity:
			if (bufferlen == 1)
			{
				minortype = data;
				switch(data)
				{
				case TE_LIGHTNING1:
				case TE_LIGHTNING2:
				case TE_LIGHTNING3:
					multicastpos=4;
					multicasttype=MULTICAST_PHS;
					protocollen = sizeof(short)*6+sizeof(short)+sizeof(qbyte)*2;
					break;
				case TE_BLOOD:		//needs to be converted to a particle
				case TE_GUNSHOT:	//needs qbyte 2 removed
					multicastpos=3;
					multicasttype=MULTICAST_PVS;
					protocollen = sizeof(short)*3+sizeof(qbyte)*3;
					break;
				case TE_LIGHTNINGBLOOD:
				case TE_EXPLOSION:
				case TE_SPIKE:
				case TE_SUPERSPIKE:
					multicastpos=2;
					multicasttype=MULTICAST_PHS_R;
					protocollen = sizeof(short)*3+sizeof(qbyte)*2;
					break;
				case TE_TAREXPLOSION:
				case TE_WIZSPIKE:
				case TE_KNIGHTSPIKE:
				case TE_LAVASPLASH:
				case TE_TELEPORT:
					multicastpos=2;
					multicasttype=MULTICAST_PVS;
					protocollen = sizeof(short)*3+sizeof(qbyte)*2;
					break;
				default:
					protocollen = sizeof(buffer);
					Con_Printf("bad tempentity\n");
					break;
				}
			}
			break;
		case svc_serverdata:
			if (bufferlen > 9)
			{
				i = 9;
				for(;i < bufferlen && buffer[i];i++)
					;
				i++;
				i+=4;
				for(;i < bufferlen && buffer[i];i++)
					;
				i+=4*10;
				if (i <= bufferlen)
					protocollen = i;
			}

			break;
		case svc_stufftext:
			if (!data)	//terminated by a null term
				protocollen = bufferlen;
			break;

		case svc_modellist:
		case svc_soundlist:
			if (!data)
				if (!buffer[bufferlen-1])	//two null bytes marks the last string
					protocollen = bufferlen+2;
			break;

		case svc_updateuserinfo:	//6 bytes then a string
			if (!data && bufferlen>=6)
				protocollen = bufferlen+1;
			break;

		case svc_playerinfo:
			if (bufferlen==4)
			{
				unsigned short pflags;
				int j;
				ignoreprotocol=true;
				pflags = buffer[2] + (buffer[3]*256);	//little endian

				protocollen = 4+1;

				for (j=0 ; j<3 ; j++)
					if (pflags & (DF_ORIGIN << j))
						protocollen += 2;

				for (j=0 ; j<3 ; j++)
					if (pflags & (DF_ANGLES << j))
						protocollen += 2;


				if (pflags & DF_MODEL)
					protocollen += 1;

				if (pflags & DF_SKINNUM)
					protocollen += 1;

				if (pflags & DF_EFFECTS)
					protocollen += 1;

				if (pflags & DF_WEAPONFRAME)
					protocollen += 1;

/*
				if (pflags & PF_MSEC)
					protocollen+=1;

				if (pflags & PF_COMMAND)
				{
					Con_Printf("svc_playerinfo PF_COMMAND not expected in mvd\n");
					protocollen+=500;
				}

				for (i=0 ; i<3 ; i++)
					if (pflags & (PF_VELOCITY1<<i) )
						protocollen+=2;

				if (pflags & PF_MODEL)
					protocollen+=1;

				if (pflags & PF_SKINNUM)
					protocollen+=1;

				if (pflags & PF_EFFECTS)
					protocollen+=1;

				if (pflags & PF_WEAPONFRAME)
					protocollen+=1;
*/
			}
			break;

		case svc_packetentities:
		case svc_deltapacketentities:
			if (!data)	//two last bytes are 0.
			{
				unsigned short s;
				i = majortype-svc_packetentities+1;
				buffer[bufferlen] = data;
				bufferlen++;
				while (1)
				{
					s = buffer[i] + buffer[i+1]*256;
					i+=2;
					if (i > bufferlen)
						break;
					if (!s)
					{
						if (i <= bufferlen)
							protocollen = bufferlen;
						break;
					}
					else
					{
						s &= ~511;
						if (s & U_MOREBITS)
						{
							s |= buffer[i];
							i++;
						}

						if (s & U_MODEL)
							i++;

						if (s & U_FRAME)
							i++;

						if (s & U_COLORMAP)
							i++;

						if (s & U_SKIN)
							i++;

						if (s & U_EFFECTS)
							i++;

						if (s & U_ORIGIN1)
							i+=2;

						if (s & U_ANGLE1)
							i++;

						if (s & U_ORIGIN2)
							i+=2;

						if (s & U_ANGLE2)
							i++;

						if (s & U_ORIGIN3)
							i+=2;

						if (s & U_ANGLE3)
							i++;
					}
				}
				bufferlen--;
			}
			break;

		case svc_lightstyle:
			if (!data && bufferlen>=2)
				protocollen = bufferlen+1;
			break;

		case svc_serverinfo:	//looking for two null terminators
			if (!data)	//gotta be the second.
			{
				for (i = 0; i < bufferlen; i++)
				{
					if (!buffer[i])	//we already wrote one.
					{
						protocollen = bufferlen+1;	//so this is the last qbyte
						break;
					}
				}
			}
			break;

		case svc_sound:
			if (bufferlen == 3) //decide after 3
			{
				unsigned short s;
				i = 10;
				s = buffer[1] + buffer[2]*256;
				if (s & SND_VOLUME)
					i++;
				if (s & SND_ATTENUATION)
					i++;
				protocollen = i;
			}
			break;

		case svc_setinfo:
			if (!data && bufferlen>2)
			{
				for (i = 2; i < bufferlen; i++)
				{
					if (!buffer[i])
					{
						protocollen = bufferlen+1;
						break;
					}
				}
			}
			break;

		case svc_centerprint:
			if (!data && bufferlen>=1)
				protocollen = bufferlen+1;
			break;
		case svc_print:
			if (!data && bufferlen>=2)
				protocollen = bufferlen+1;
			break;

		case svc_nails:
			if (bufferlen == 1)
			{
				i = data;	//first qbyte is id, second, (the current, about to be written) is number of nails.
				protocollen = (i * 6) + 2;
			}
			break;
		case svc_nails2:
			if (bufferlen == 1)
			{
				i = data;	//first qbyte is id, second, (the current, about to be written) is number of nails.
				protocollen = (i * 7) + 2;
			}
			break;
		default:
			Con_Printf("mvd: bad protocol %i\n", (int)data);
			protocollen = sizeof(buffer);
			net_message.cursize=0;
			data = svc_nop;
			protocollen = 1;
			break;
		}
	}


	NPP_AddData(&data, sizeof(qbyte));
	NPP_MVDCheckFlush();
}
#endif //SERVER_DEMO_PLAYBACK

void NPP_Flush(void)
{
	if (progstype != PROG_QW)
		NPP_NQFlush();
#ifdef NQPROT
	else
		NPP_QWFlush();
#endif
}
#endif
#endif
