/*
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 included (GNU.txt) 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.
*/


#include "qtv.h"


typedef struct {
	unsigned char msec;
	unsigned short angles[3];
	short forwardmove, sidemove, upmove;
	unsigned char buttons;
	unsigned char impulse;
} usercmd_t;
const usercmd_t nullcmd;

#define	CM_ANGLE1 	(1<<0)
#define	CM_ANGLE3 	(1<<1)
#define	CM_FORWARD	(1<<2)
#define	CM_SIDE		(1<<3)
#define	CM_UP		(1<<4)
#define	CM_BUTTONS	(1<<5)
#define	CM_IMPULSE	(1<<6)
#define	CM_ANGLE2 	(1<<7)
void ReadDeltaUsercmd (netmsg_t *m, const usercmd_t *from, usercmd_t *move)
{
	int bits;

	memcpy (move, from, sizeof(*move));

	bits = ReadByte (m);

// read current angles
	if (bits & CM_ANGLE1)
		move->angles[0] = ReadShort (m);
	if (bits & CM_ANGLE2)
		move->angles[1] = ReadShort (m);
	if (bits & CM_ANGLE3)
		move->angles[2] = ReadShort (m);

// read movement
	if (bits & CM_FORWARD)
		move->forwardmove = ReadShort(m);
	if (bits & CM_SIDE)
		move->sidemove = ReadShort(m);
	if (bits & CM_UP)
		move->upmove = ReadShort(m);

// read buttons
	if (bits & CM_BUTTONS)
		move->buttons = ReadByte (m);

	if (bits & CM_IMPULSE)
		move->impulse = ReadByte (m);

// read time to run command
	move->msec = ReadByte (m);		// always sent
}

void WriteDeltaUsercmd (netmsg_t *m, const usercmd_t *from, usercmd_t *move)
{
	int bits = 0;

	if (move->angles[0] != from->angles[0])
		bits |= CM_ANGLE1;
	if (move->angles[1] != from->angles[1])
		bits |= CM_ANGLE2;
	if (move->angles[2] != from->angles[2])
		bits |= CM_ANGLE3;

	if (move->forwardmove != from->forwardmove)
		bits |= CM_FORWARD;
	if (move->sidemove != from->sidemove)
		bits |= CM_SIDE;
	if (move->upmove != from->upmove)
		bits |= CM_UP;

	if (move->buttons != from->buttons)
		bits |= CM_BUTTONS;
	if (move->impulse != from->impulse)
		bits |= CM_IMPULSE;


	WriteByte (m, bits);

// read current angles
	if (bits & CM_ANGLE1)
		WriteShort (m, move->angles[0]);
	if (bits & CM_ANGLE2)
		WriteShort (m, move->angles[1]);
	if (bits & CM_ANGLE3)
		WriteShort (m, move->angles[2]);

// read movement
	if (bits & CM_FORWARD)
		WriteShort(m, move->forwardmove);
	if (bits & CM_SIDE)
		WriteShort(m, move->sidemove);
	if (bits & CM_UP)
		WriteShort(m, move->upmove);

// read buttons
	if (bits & CM_BUTTONS)
		WriteByte (m, move->buttons);

	if (bits & CM_IMPULSE)
		WriteByte (m, move->impulse);

// read time to run command
	WriteByte (m, move->msec);		// always sent
}










SOCKET QW_InitUDPSocket(int port)
{
	int sock;

	struct sockaddr_in	address;
//	int fromlen;

	unsigned long nonblocking = true;

	address.sin_family = AF_INET;
	address.sin_addr.s_addr = INADDR_ANY;
	address.sin_port = htons((u_short)port);



	if ((sock = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
	{
		return INVALID_SOCKET;
	}

	if (ioctlsocket (sock, FIONBIO, &nonblocking) == -1)
	{
		closesocket(sock);
		return INVALID_SOCKET;
	}

	if( bind (sock, (void *)&address, sizeof(address)) == -1)
	{
		closesocket(sock);
		return INVALID_SOCKET;
	}
	return sock;
}

void BuildServerData(sv_t *tv, netmsg_t *msg, qboolean mvd, int servercount)
{
	WriteByte(msg, svc_serverdata);
	WriteLong(msg, PROTOCOL_VERSION);
	WriteLong(msg, servercount);

	if (!tv)
	{
		//dummy connection, for choosing a game to watch.
		WriteString(msg, "qw");

		if (mvd)
			WriteFloat(msg, 0);
		else
			WriteByte(msg, MAX_CLIENTS-1);
		WriteString(msg, "FTEQTV Proxy");


		// get the movevars
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);
		WriteFloat(msg, 0);



		WriteByte(msg, svc_stufftext);
		WriteString2(msg, "fullserverinfo \"");
		WriteString2(msg, "\\*QTV\\"VERSION);
		WriteString(msg, "\"\n");

	}
	else
	{
		WriteString(msg, tv->gamedir);

		if (mvd)
			WriteFloat(msg, 0);
		else
			WriteByte(msg, MAX_CLIENTS-1);
		WriteString(msg, tv->mapname);


		// get the movevars
		WriteFloat(msg, tv->movevars.gravity);
		WriteFloat(msg, tv->movevars.stopspeed);
		WriteFloat(msg, tv->movevars.maxspeed);
		WriteFloat(msg, tv->movevars.spectatormaxspeed);
		WriteFloat(msg, tv->movevars.accelerate);
		WriteFloat(msg, tv->movevars.airaccelerate);
		WriteFloat(msg, tv->movevars.wateraccelerate);
		WriteFloat(msg, tv->movevars.friction);
		WriteFloat(msg, tv->movevars.waterfriction);
		WriteFloat(msg, tv->movevars.entgrav);



		WriteByte(msg, svc_stufftext);
		WriteString2(msg, "fullserverinfo \"");
		WriteString2(msg, tv->serverinfo);
		WriteString(msg, "\"\n");
	}
}

void SendServerData(sv_t *tv, viewer_t *viewer)
{
	netmsg_t msg;
	char buffer[1024];
	InitNetMsg(&msg, buffer, sizeof(buffer));

	BuildServerData(tv, &msg, false, viewer->servercount);

	SendBufferToViewer(viewer, msg.data, msg.cursize, true);

	viewer->thinksitsconnected = false;

	memset(viewer->currentstats, 0, sizeof(viewer->currentstats));
}

int SendCurrentUserinfos(sv_t *tv, int cursize, netmsg_t *msg, int i)
{
	if (i < 0)
		return i;
	if (i >= MAX_CLIENTS)
		return i;


	for (; i < MAX_CLIENTS-1; i++)
	{
		if (msg->cursize+cursize+strlen(tv->players[i].userinfo) > 768)
		{
			return i;
		}
		WriteByte(msg, svc_updateuserinfo);
		WriteByte(msg, i);
		WriteLong(msg, i);
		WriteString(msg, tv->players[i].userinfo);
	}

	WriteByte(msg, svc_updateuserinfo);
	WriteByte(msg, MAX_CLIENTS-1);
	WriteLong(msg, MAX_CLIENTS-1);
	WriteString(msg, "\\*spectator\\1\\name\\YOU!");

	i++;

	return i;
}
void WriteEntityState(netmsg_t *msg, entity_state_t *es)
{
	int i;
	WriteByte(msg, es->modelindex);
	WriteByte(msg, es->frame);
	WriteByte(msg, es->colormap);
	WriteByte(msg, es->skinnum);
	for (i = 0; i < 3; i++)
	{
		WriteShort(msg, es->origin[i]);
		WriteByte(msg, es->angles[i]);
	}
}
int SendCurrentBaselines(sv_t *tv, int cursize, netmsg_t *msg, int i)
{

	if (i < 0 || i >= MAX_ENTITIES)
		return i;

	for (; i < MAX_ENTITIES; i++)
	{
		if (msg->cursize+cursize+16 > 768)
		{
			return i;
		}
		WriteByte(msg, svc_spawnbaseline);
		WriteShort(msg, i);
		WriteEntityState(msg, &tv->entity[i].baseline);
	}

	return i;
}
int SendCurrentLightmaps(sv_t *tv, int cursize, netmsg_t *msg, int i)
{
	if (i < 0 || i >= MAX_LIGHTSTYLES)
		return i;

	for (; i < MAX_LIGHTSTYLES; i++)
	{
		if (msg->cursize+cursize+strlen(tv->lightstyle[i].name) > 768)
		{
			return i;
		}
		WriteByte(msg, svc_lightstyle);
		WriteByte(msg, i);
		WriteString(msg, tv->lightstyle[i].name);
	}
	return i;
}
int SendStaticSounds(sv_t *tv, int cursize, netmsg_t *msg, int i)
{
	if (i < 0 || i >= MAX_STATICSOUNDS)
		return i;

	for (; i < MAX_STATICSOUNDS; i++)
	{
		if (msg->cursize+cursize+16 > 768)
		{
			return i;
		}
		if (!tv->staticsound[i].soundindex)
			continue;

		WriteByte(msg, svc_spawnstaticsound);
		WriteShort(msg, tv->staticsound[i].origin[0]);
		WriteShort(msg, tv->staticsound[i].origin[1]);
		WriteShort(msg, tv->staticsound[i].origin[2]);
		WriteByte(msg, tv->staticsound[i].soundindex);
		WriteByte(msg, tv->staticsound[i].volume);
		WriteByte(msg, tv->staticsound[i].attenuation);
	}

	return i;
}
int SendStaticEntities(sv_t *tv, int cursize, netmsg_t *msg, int i)
{
	if (i < 0 || i >= MAX_STATICENTITIES)
		return i;

	for (; i < MAX_STATICENTITIES; i++)
	{
		if (msg->cursize+cursize+16 > 768)
		{
			return i;
		}
		if (!tv->spawnstatic[i].modelindex)
			continue;

		WriteByte(msg, svc_spawnstatic);
		WriteEntityState(msg, &tv->spawnstatic[i]);
	}

	return i;
}

int SendList(sv_t *qtv, int first, const filename_t *list, int svc, netmsg_t *msg)
{
	int i;

	WriteByte(msg, svc);
	WriteByte(msg, first);
	for (i = first+1; i < 256; i++)
	{
//		printf("write %i: %s\n", i, list[i].name);
		WriteString(msg, list[i].name);
		if (!*list[i].name)	//fixme: this probably needs testing for where we are close to the limit
		{	//no more
			WriteByte(msg, 0);
			return -1;
		}

		if (msg->cursize > 768)
		{	//truncate
			i--;
			break;
		}
	}
	WriteByte(msg, 0);
	WriteByte(msg, i);

	return i;
}

//fixme: will these want to have state?..
int NewChallenge(netadr_t *addr)
{
	return 4;
}
qboolean ChallengePasses(netadr_t *addr, int challenge)
{
	if (challenge == 4)
		return true;
	return false;
}

void NewQWClient(cluster_t *cluster, netadr_t *addr, char *connectmessage)
{
	viewer_t *viewer;

	char qport[32];
	char challenge[32];
	char infostring[256];

	connectmessage+=11;

	connectmessage = COM_ParseToken(connectmessage, qport, sizeof(qport), "");
	connectmessage = COM_ParseToken(connectmessage, challenge, sizeof(challenge), "");
	connectmessage = COM_ParseToken(connectmessage, infostring, sizeof(infostring), "");

	if (!ChallengePasses(addr, atoi(challenge)))
	{
		Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "n" "Bad challenge");
		return;
	}


	viewer = malloc(sizeof(viewer_t));
	memset(viewer, 0, sizeof(viewer_t));

	viewer->trackplayer = -1;
	Netchan_Setup (cluster->qwdsocket, &viewer->netchan, *addr, atoi(qport));

	viewer->next = cluster->viewers;
	cluster->viewers = viewer;
	viewer->delta_frame = -1;

	if (cluster->numservers == 1)
	{
		viewer->server = cluster->servers;
		if (!viewer->server->modellist[1].name[0])
			viewer->server = NULL;	//damn, that server isn't ready
	}

	if (!viewer->server)
		viewer->menunum = 1;

	cluster->numviewers++;

	Info_ValueForKey(infostring, "name", viewer->name, sizeof(viewer->name));

	Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "j");
}

void QTV_Rcon(cluster_t *cluster, char *message, netadr_t *from)
{
	char buffer[8192];

	char *command;
	int passlen;

	if (!*cluster->password)
	{
		Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Bad rcon_password.\n");
		return;
	}

	while(*message > '\0' && *message <= ' ')
		message++;

	command = strchr(message, ' ');
	passlen = command-message;
	if (passlen != strlen(cluster->password) || strncmp(message, cluster->password, passlen))
	{
		Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Bad rcon_password.\n");
		return;
	}

	Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n%s", Rcon_Command(cluster, NULL, command, buffer, sizeof(buffer), false));
}

void QTV_Status(cluster_t *cluster, netadr_t *from)
{
	int i;
	char buffer[8192];
	sv_t *sv;

	netmsg_t msg;
	char elem[256];
	InitNetMsg(&msg, buffer, sizeof(buffer));
	WriteLong(&msg, -1);
	WriteByte(&msg, 'n');

	if (cluster->numservers==1)
	{	//show this server's info
		sv = cluster->servers;

		WriteString2(&msg, sv->serverinfo);
		WriteString2(&msg, "\n");

		for (i = 0;i < MAX_CLIENTS-1; i++)
		{
			if (!sv->players[i].active)
				continue;
			//userid
			sprintf(elem, "%i", i);
			WriteString2(&msg, elem);
			WriteString2(&msg, " ");

			//frags
			sprintf(elem, "%i", sv->players[i].frags);
			WriteString2(&msg, elem);
			WriteString2(&msg, " ");

			//time (minuites)
			sprintf(elem, "%i", 0);
			WriteString2(&msg, elem);
			WriteString2(&msg, " ");

			//ping
			sprintf(elem, "%i", sv->players[i].ping);
			WriteString2(&msg, elem);
			WriteString2(&msg, " ");

			//name
			Info_ValueForKey(sv->players[i].userinfo, "name", elem, sizeof(elem));
			WriteString2(&msg, "\"");
			WriteString2(&msg, elem);
			WriteString2(&msg, "\" ");

			//skin
			Info_ValueForKey(sv->players[i].userinfo, "skin", elem, sizeof(elem));
			WriteString2(&msg, "\"");
			WriteString2(&msg, elem);
			WriteString2(&msg, "\" ");
			WriteString2(&msg, " ");

			//tc
			Info_ValueForKey(sv->players[i].userinfo, "topcolor", elem, sizeof(elem));
			WriteString2(&msg, elem);
			WriteString2(&msg, " ");

			//bc
			Info_ValueForKey(sv->players[i].userinfo, "bottomcolor", elem, sizeof(elem));
			WriteString2(&msg, elem);
			WriteString2(&msg, " ");

			WriteString2(&msg, "\n");
		}
	}
	else
	{
		WriteString2(&msg, "\\hostname\\");
		WriteString2(&msg, cluster->hostname);

		for (sv = cluster->servers, i = 0; sv; sv = sv->next, i++)
		{
			sprintf(elem, "\\%i\\", i);
			WriteString2(&msg, elem);
			WriteString2(&msg, sv->serveraddress);
			sprintf(elem, " (%s)", sv->serveraddress);
			WriteString2(&msg, sv->serveraddress);
		}
	}

	WriteByte(&msg, 0);
	NET_SendPacket(cluster, cluster->qwdsocket, msg.cursize, msg.data, *from);
}


void ConnectionlessPacket(cluster_t *cluster, netadr_t *from, netmsg_t *m)
{
	char buffer[MAX_MSGLEN];
	int i;

	ReadLong(m);
	ReadString(m, buffer, sizeof(buffer));

	if (!strncmp(buffer, "rcon ", 5))
	{
		QTV_Rcon(cluster, buffer+5, from);
		return;
	}
	if (!strncmp(buffer, "ping", 4))
	{	//ack
		NET_SendPacket (cluster, cluster->qwdsocket, 1, "l", *from);
		return;
	}
	if (!strncmp(buffer, "status", 6))
	{
		QTV_Status(cluster, from);
		return;
	}
	if (!strncmp(buffer, "getchallenge", 12))
	{
		i = NewChallenge(from);
		Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "c%i", i);
		return;
	}
	if (!strncmp(buffer, "connect 28 ", 11))
	{
		if (cluster->numviewers >= cluster->maxviewers && cluster->maxviewers)
			Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *from, "n" "Sorry, proxy is full.\n");
		else
			NewQWClient(cluster, from, buffer);
		return;
	}
	if (!strncmp(buffer, "l\n", 2))
	{
		Sys_Printf(cluster, "Ack\n");
	}
}


void SV_WriteDelta(int entnum, const entity_state_t *from, const entity_state_t *to, netmsg_t *msg, qboolean force)
{
	unsigned int i;
	unsigned int bits;

	bits = 0;
	if (from->angles[0] != to->angles[0])
		bits |= U_ANGLE1;
	if (from->angles[1] != to->angles[1])
		bits |= U_ANGLE2;
	if (from->angles[2] != to->angles[2])
		bits |= U_ANGLE3;

	if (from->origin[0] != to->origin[0])
		bits |= U_ORIGIN1;
	if (from->origin[1] != to->origin[1])
		bits |= U_ORIGIN2;
	if (from->origin[2] != to->origin[2])
		bits |= U_ORIGIN3;

	if (from->colormap != to->colormap)
		bits |= U_COLORMAP;
	if (from->skinnum != to->skinnum)
		bits |= U_SKIN;
	if (from->modelindex != to->modelindex)
		bits |= U_MODEL;
	if (from->frame != to->frame)
		bits |= U_FRAME;
	if (from->effects != to->effects)
		bits |= U_EFFECTS;

	if (bits & 255)
		bits |= U_MOREBITS;



	if (!bits && !force)
		return;

	i = (entnum&511) | (bits&~511);
	WriteShort (msg, i);

	if (bits & U_MOREBITS)
		WriteByte (msg, bits&255);
/*
#ifdef PROTOCOLEXTENSIONS
	if (bits & U_EVENMORE)
		WriteByte (msg, evenmorebits&255);
	if (evenmorebits & U_YETMORE)
		WriteByte (msg, (evenmorebits>>8)&255);
#endif
*/
	if (bits & U_MODEL)
		WriteByte (msg,	to->modelindex&255);
	if (bits & U_FRAME)
		WriteByte (msg, to->frame);
	if (bits & U_COLORMAP)
		WriteByte (msg, to->colormap);
	if (bits & U_SKIN)
		WriteByte (msg, to->skinnum);
	if (bits & U_EFFECTS)
		WriteByte (msg, to->effects&0x00ff);
	if (bits & U_ORIGIN1)
		WriteShort (msg, to->origin[0]);
	if (bits & U_ANGLE1)
		WriteByte(msg, to->angles[0]);
	if (bits & U_ORIGIN2)
		WriteShort (msg, to->origin[1]);
	if (bits & U_ANGLE2)
		WriteByte(msg, to->angles[1]);
	if (bits & U_ORIGIN3)
		WriteShort (msg, to->origin[2]);
	if (bits & U_ANGLE3)
		WriteByte(msg, to->angles[2]);
}

const entity_state_t nullentstate;
void SV_EmitPacketEntities (const sv_t *qtv, const viewer_t *v, const packet_entities_t *to, netmsg_t *msg)
{
	const entity_state_t *baseline;
	const packet_entities_t *from;
	int		oldindex, newindex;
	int		oldnum, newnum;
	int		oldmax;

	// this is the frame that we are going to delta update from
	if (v->delta_frame != -1)
	{
		from = &v->frame[v->delta_frame & (ENTITY_FRAMES-1)];
		oldmax = from->numents;

		WriteByte (msg, svc_deltapacketentities);
		WriteByte (msg, v->delta_frame);
	}
	else
	{
		oldmax = 0;	// no delta update
		from = NULL;

		WriteByte (msg, svc_packetentities);
	}

	newindex = 0;
	oldindex = 0;
//Con_Printf ("---%i to %i ----\n", client->delta_sequence & UPDATE_MASK
//			, client->netchan.outgoing_sequence & UPDATE_MASK);
	while (newindex < to->numents || oldindex < oldmax)
	{
		newnum = newindex >= to->numents ? 9999 : to->entnum[newindex];
		oldnum = oldindex >= oldmax ? 9999 : from->entnum[oldindex];

		if (newnum == oldnum)
		{	// delta update from old position
//Con_Printf ("delta %i\n", newnum);
			SV_WriteDelta (newnum, &from->ents[oldindex], &to->ents[newindex], msg, false);

			oldindex++;
			newindex++;
			continue;
		}

		if (newnum < oldnum)
		{	// this is a new entity, send it from the baseline
			baseline = &qtv->entity[newnum].baseline;
//Con_Printf ("baseline %i\n", newnum);
			SV_WriteDelta (newnum, baseline, &to->ents[newindex], msg, true);

			newindex++;
			continue;
		}

		if (newnum > oldnum)
		{	// the old entity isn't present in the new message
//Con_Printf ("remove %i\n", oldnum);
			WriteShort (msg, oldnum | U_REMOVE);
			oldindex++;
			continue;
		}
	}

	WriteShort (msg, 0);	// end of packetentities
}

void Prox_SendInitialEnts(sv_t *qtv, oproxy_t *prox, netmsg_t *msg)
{
	int i;
	WriteByte(msg, svc_packetentities);
	for (i = 0; i < qtv->maxents; i++)
	{
		if (qtv->entity[i].current.modelindex)
			SV_WriteDelta(i, &nullentstate, &qtv->entity[i].current, msg, true);
	}
	WriteShort(msg, 0);
}

static float InterpolateAngle(float current, float ideal, float fraction)
{
	float move;

	move = ideal - current;
	if (move >= 32767)
		move -= 65535;
	else if (move <= -32767)
		move += 65535;

	return current + fraction * move;
}

void SendPlayerStates(sv_t *tv, viewer_t *v, netmsg_t *msg)
{
	packet_entities_t *e;
	int i;
	usercmd_t to;
	unsigned short flags;
	short interp;
	float lerp;


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

	if (v->server)
	{
		if (tv->physicstime != v->settime && tv->cluster->chokeonnotupdated)
		{
			WriteByte(msg, svc_updatestatlong);
			WriteByte(msg, STAT_TIME);
			WriteLong(msg, v->settime);

			v->settime = tv->physicstime;
		}

		BSP_SetupForPosition(tv->bsp, v->origin[0], v->origin[1], v->origin[2]);

		lerp = ((tv->curtime - tv->oldpackettime)/1000.0f) / ((tv->nextpackettime - tv->oldpackettime)/1000.0f);
		if (lerp < 0)
			lerp = 0;
		if (lerp > 1)
			lerp = 1;

		for (i = 0; i < MAX_CLIENTS-1; i++)
		{
			if (!tv->players[i].active)
				continue;

			if (v->trackplayer != i && !BSP_Visible(tv->bsp, tv->players[i].leafcount, tv->players[i].leafs))
				continue;

			flags = PF_COMMAND;
			if (v->trackplayer == i && tv->players[i].current.weaponframe)
				flags |= PF_WEAPONFRAME;

			WriteByte(msg, svc_playerinfo);
			WriteByte(msg, i);
			WriteShort(msg, flags);

			interp = (lerp)*tv->players[i].current.origin[0] + (1-lerp)*tv->players[i].old.origin[0];
			WriteShort(msg, interp);
			interp = (lerp)*tv->players[i].current.origin[1] + (1-lerp)*tv->players[i].old.origin[1];
			WriteShort(msg, interp);
			interp = (lerp)*tv->players[i].current.origin[2] + (1-lerp)*tv->players[i].old.origin[2];
			WriteShort(msg, interp);

			WriteByte(msg, tv->players[i].current.frame);

			if (flags & PF_MSEC)
			{
				WriteByte(msg, 0);
			}
			if (flags & PF_COMMAND)
			{
	//			to.angles[0] = tv->players[i].current.angles[0];
	//			to.angles[1] = tv->players[i].current.angles[1];
	//			to.angles[2] = tv->players[i].current.angles[2];

				to.angles[0] = InterpolateAngle(tv->players[i].old.angles[0], tv->players[i].current.angles[0], lerp);
				to.angles[1] = InterpolateAngle(tv->players[i].old.angles[1], tv->players[i].current.angles[1], lerp);
				to.angles[2] = InterpolateAngle(tv->players[i].old.angles[2], tv->players[i].current.angles[2], lerp);
				WriteDeltaUsercmd(msg, &nullcmd, &to);
			}
			//vel
			//model
			//skin
			//effects
			//weaponframe
			if (flags & PF_WEAPONFRAME)
				WriteByte(msg, tv->players[i].current.weaponframe);
		}
	}
	else
	{
		lerp = 1;
	}

	WriteByte(msg, svc_playerinfo);
	WriteByte(msg, MAX_CLIENTS-1);
	WriteShort(msg, 0);

	WriteShort(msg, v->origin[0]*8);
	WriteShort(msg, v->origin[1]*8);
	WriteShort(msg, v->origin[2]*8);

	WriteByte(msg, 0);





	e = &v->frame[v->netchan.outgoing_sequence&(ENTITY_FRAMES-1)];
	e->numents = 0;
	if (v->server)
		for (i = 0; i < tv->maxents; i++)
		{
			if (!tv->entity[i].current.modelindex)
				continue;

			if (tv->entity[i].current.modelindex >= tv->numinlines && !BSP_Visible(tv->bsp, tv->entity[i].leafcount, tv->entity[i].leafs))
				continue;

			e->entnum[e->numents] = i;
			memcpy(&e->ents[e->numents], &tv->entity[i].current, sizeof(entity_state_t));

			if (tv->entity[i].updatetime == tv->oldpackettime)
			{
				e->ents[e->numents].origin[0] = (lerp)*tv->entity[i].current.origin[0] + (1-lerp)*tv->entity[i].old.origin[0];
				e->ents[e->numents].origin[1] = (lerp)*tv->entity[i].current.origin[1] + (1-lerp)*tv->entity[i].old.origin[1];
				e->ents[e->numents].origin[2] = (lerp)*tv->entity[i].current.origin[2] + (1-lerp)*tv->entity[i].old.origin[2];
			}

			e->numents++;

			if (e->numents == ENTS_PER_FRAME)
				break;
		}

	SV_EmitPacketEntities(tv, v, e, msg);
}

void UpdateStats(sv_t *qtv, viewer_t *v)
{
	netmsg_t msg;
	char buf[6];
	int i;
	const static unsigned int nullstats[MAX_STATS] = {1000};

	const unsigned int *stats;

	InitNetMsg(&msg, buf, sizeof(buf));

	if (v->trackplayer < 0 || !qtv)
		stats = nullstats;
	else
		stats = qtv->players[v->trackplayer].stats;

	for (i = 0; i < MAX_STATS; i++)
	{
		if (v->currentstats[i] != stats[i])
		{
			if (stats[i] < 256)
			{
				WriteByte(&msg, svc_updatestat);
				WriteByte(&msg, i);
				WriteByte(&msg, stats[i]);
			}
			else
			{
				WriteByte(&msg, svc_updatestatlong);
				WriteByte(&msg, i);
				WriteLong(&msg, stats[i]);
			}
			SendBufferToViewer(v, msg.data, msg.cursize, true);
			msg.cursize = 0;
			v->currentstats[i] = stats[i];
		}
	}
}

//returns the next prespawn 'buffer' number to use, or -1 if no more
int Prespawn(sv_t *qtv, int curmsgsize, netmsg_t *msg, int bufnum)
{
	int r, ni;
	r = bufnum;

	ni = SendCurrentUserinfos(qtv, curmsgsize, msg, bufnum);
	r += ni - bufnum;
	bufnum = ni;
	bufnum -= MAX_CLIENTS;

	ni = SendCurrentBaselines(qtv, curmsgsize, msg, bufnum);
	r += ni - bufnum;
	bufnum = ni;
	bufnum -= MAX_ENTITIES;

	ni = SendCurrentLightmaps(qtv, curmsgsize, msg, bufnum);
	r += ni - bufnum;
	bufnum = ni;
	bufnum -= MAX_LIGHTSTYLES;

	ni = SendStaticSounds(qtv, curmsgsize, msg, bufnum);
	r += ni - bufnum;
	bufnum = ni;
	bufnum -= MAX_STATICSOUNDS;

	ni = SendStaticEntities(qtv, curmsgsize, msg, bufnum);
	r += ni - bufnum;
	bufnum = ni;
	bufnum -= MAX_STATICENTITIES;

	if (bufnum == 0)
		return -1;

	return r;
}

#define M_PI 3.1415926535897932384626433832795
#include <math.h>
void AngleVectors (short angles[3], float *forward, float *right, float *up)
{
	float		angle;
	float		sr, sp, sy, cr, cp, cy;

	angle = angles[1] * (M_PI*2 / 65535);
	sy = sin(angle);
	cy = cos(angle);
	angle = angles[0] * (M_PI*2 / 65535);
	sp = sin(angle);
	cp = cos(angle);
	angle = angles[2] * (M_PI*2 / 65535);
	sr = sin(angle);
	cr = cos(angle);

	forward[0] = cp*cy;
	forward[1] = cp*sy;
	forward[2] = -sp;
	right[0] = (-1*sr*sp*cy+-1*cr*-sy);
	right[1] = (-1*sr*sp*sy+-1*cr*cy);
	right[2] = -1*sr*cp;
	up[0] = (cr*sp*cy+-sr*-sy);
	up[1] = (cr*sp*sy+-sr*cy);
	up[2] = cr*cp;
}

void PMove(viewer_t *v, usercmd_t *cmd)
{
	int i;
	float fwd[3], rgt[3], up[3];
	AngleVectors(cmd->angles, fwd, rgt, up);

	for (i = 0; i < 3; i++)
		v->origin[i] += (cmd->forwardmove*fwd[i] + cmd->sidemove*rgt[i] + cmd->upmove*up[i])*(cmd->msec/1000.0f);
}

void QTV_Say(cluster_t *cluster, viewer_t *v, char *message)
{
	char buf[1024];
	netmsg_t msg;

	if (message[strlen(message)-1] == '\"')
		message[strlen(message)-1] = '\0';

	InitNetMsg(&msg, buf, sizeof(buf));

	WriteByte(&msg, svc_print);
	WriteByte(&msg, 3);	//PRINT_CHAT
	WriteString2(&msg, v->name);
	WriteString2(&msg, "\x8d ");
	WriteString2(&msg, message);
	WriteString(&msg, "\n");

	Broadcast(cluster, msg.data, msg.cursize);
}

viewer_t *QW_IsOn(cluster_t *cluster, char *name)
{
	viewer_t *v;
	for (v = cluster->viewers; v; v = v->next)
		if (!strcmp(v->name, name))		//this needs to allow dequakified names.
			return v;

	return NULL;
}

void QW_PrintfToViewer(viewer_t *v, char *format, ...)
{
	va_list		argptr;
	char buf[1024];
	netmsg_t msg;
	InitNetMsg(&msg, buf, sizeof(buf));

	va_start (argptr, format);
#ifdef _WIN32
	_vsnprintf (buf+2, sizeof(buf) - 3, format, argptr);
	buf[sizeof(buf) - 3] = '\0';
#else
	vsnprintf (buf+2, sizeof(buf)-2, format, argptr);
#endif // _WIN32
	va_end (argptr);

	buf[0] = svc_print;
	buf[1] = 2;	//PRINT_HIGH

	SendBufferToViewer(v, buf, strlen(buf)+1, true);
}

static const filename_t ConnectionlessModelList[] = {{""}, {"maps/start.bsp"}, {"progs/player.mdl"}, {""}};
static const filename_t ConnectionlessSoundList[] = {{""}, {""}};

void Menu_Enter(cluster_t *cluster, viewer_t *viewer, int buttonnum);


void ParseQWC(cluster_t *cluster, sv_t *qtv, viewer_t *v, netmsg_t *m)
{
	usercmd_t	oldest, oldcmd, newcmd;
	char buf[1024];
	netmsg_t msg;

	v->delta_frame = -1;

	while (m->readpos < m->cursize)
	{
		switch (ReadByte(m))
		{
		case clc_nop:
			return;
		case clc_delta:
			v->delta_frame = ReadByte(m);
			break;
		case clc_stringcmd:
			ReadString (m, buf, sizeof(buf));
//			printf("stringcmd: %s\n", buf);

			if (!strcmp(buf, "new"))
				SendServerData(qtv, v);
			else if (!strncmp(buf, "modellist ", 10))
			{
				char *cmd = buf+10;
				int svcount = atoi(cmd);
				int first;

				while((*cmd >= '0' && *cmd <= '9') || *cmd == '-')
					cmd++;
				first = atoi(cmd);

				InitNetMsg(&msg, buf, sizeof(buf));

				if (svcount != v->servercount)
				{	//looks like we changed map without them.
					SendServerData(qtv, v);
					return;
				}

				if (!qtv)
					SendList(qtv, first, ConnectionlessModelList, svc_modellist, &msg);
				else
					SendList(qtv, first, qtv->modellist, svc_modellist, &msg);
				SendBufferToViewer(v, msg.data, msg.cursize, true);
			}
			else if (!strncmp(buf, "soundlist ", 10))
			{
				char *cmd = buf+10;
				int svcount = atoi(cmd);
				int first;

				while((*cmd >= '0' && *cmd <= '9') || *cmd == '-')
					cmd++;
				first = atoi(cmd);

				InitNetMsg(&msg, buf, sizeof(buf));

				if (svcount != v->servercount)
				{	//looks like we changed map without them.
					SendServerData(qtv, v);
					return;
				}

				if (!qtv)
					SendList(qtv, first, ConnectionlessSoundList, svc_soundlist, &msg);
				else
					SendList(qtv, first, qtv->soundlist, svc_soundlist, &msg);
				SendBufferToViewer(v, msg.data, msg.cursize, true);
			}
			else if (!strncmp(buf, "prespawn", 8))
			{
				char skin[128];

				if (atoi(buf + 9) != v->servercount)
					SendServerData(qtv, v);	//we're old.
				else
				{
					int r;
					char *s;
					s = buf+9;
					while((*s >= '0' && *s <= '9') || *s == '-')
						s++;
					while(*s == ' ')
						s++;
					r = atoi(s);

					InitNetMsg(&msg, buf, sizeof(buf));

					if (v->server)
					{
						r = Prespawn(qtv, v->netchan.message.cursize, &msg, r);
						SendBufferToViewer(v, msg.data, msg.cursize, true);
					}
					else r = -1;

					if (r < 0)
						sprintf(skin, "%ccmd spawn\n", svc_stufftext);
					else
						sprintf(skin, "%ccmd prespawn %i %i\n", svc_stufftext, v->servercount, r);

					SendBufferToViewer(v, skin, strlen(skin)+1, true);
				}
			}
			else if (!strncmp(buf, "spawn", 5))
			{
				char skin[64];
				sprintf(skin, "%cskins\n", svc_stufftext);
				SendBufferToViewer(v, skin, strlen(skin)+1, true);
			}
			else if (!strncmp(buf, "begin", 5))
			{
				if (atoi(buf+6) != v->servercount)
					SendServerData(qtv, v);	//this is unfortunate!
				else
					v->thinksitsconnected = true;
			}
			else if (!strncmp(buf, "download", 8))
			{
				netmsg_t m;
				InitNetMsg(&m, buf, sizeof(buf));
				WriteByte(&m, svc_download);
				WriteShort(&m, -1);
				WriteByte(&m, 0);
				SendBufferToViewer(v, m.data, m.cursize, true);
			}
			else if (!strncmp(buf, "drop", 4))
				v->drop = true;
			else if (!strncmp(buf, "ison", 4))
			{
				viewer_t *other;
				if ((other = QW_IsOn(qtv->cluster, buf+5)))
				{
					if (!other->server)
						QW_PrintfToViewer(v, "%s is on the proxy, but not yet watching a game\n");
					else
						QW_PrintfToViewer(v, "%s is watching %s\n", buf+5, other->server->server);
				}
				else
					QW_PrintfToViewer(v, "%s is not on the proxy, sorry\n", buf+5);	//the apology is to make the alternatives distinct.
			}
			else if (!strncmp(buf, "ptrack ", 7))
				v->trackplayer = atoi(buf+7);
			else if (!strncmp(buf, "ptrack", 6))
				v->trackplayer = -1;
			else if (!strncmp(buf, "pings", 5))
			{
			}
			else if (!strncmp(buf, "say \"", 5) && !cluster->notalking)
				QTV_Say(cluster, v, buf+5);
			else if (!strncmp(buf, "say ", 4) && !cluster->notalking)
				QTV_Say(cluster, v, buf+4);

			else if (!strncmp(buf, "servers", 7))
			{
				v->menunum = 1;
			}
			else if (!strncmp(buf, "reset", 7))
			{
				v->server = NULL;
				v->menunum = 1;
			}

			else if (!qtv)
			{
				//all the other things need an active server.
				QW_PrintfToViewer(v, "Choose a server first\n");
			}
			else if (!strncmp(buf, "serverinfo", 5))
			{
				char *key, *value, *end;
				int len;
				netmsg_t m;
				InitNetMsg(&m, buf, sizeof(buf));
				WriteByte(&m, svc_print);
				WriteByte(&m, 2);
				end = qtv->serverinfo;
				for(;;)
				{
					if (!*end)
						break;
					key = end;
					value = strchr(key+1, '\\');
					if (!value)
						break;
					end = strchr(value+1, '\\');
					if (!end)
						end = value+strlen(value);

					len = value-key;

					key++;
					while(*key != '\\' && *key)
						WriteByte(&m, *key++);

					for (; len < 20; len++)
						WriteByte(&m, ' ');

					value++;
					while(*value != '\\' && *value)
						WriteByte(&m, *value++);
					WriteByte(&m, '\n');
				}
				WriteByte(&m, 0);

//				WriteString(&m, qtv->serverinfo);
				SendBufferToViewer(v, m.data, m.cursize, true);
			}
			else
			{
				Sys_Printf(cluster, "Client sent unknown string command: %s\n", buf);
			}

			break;

		case clc_move:
			ReadByte(m);
			ReadByte(m);
			ReadDeltaUsercmd(m, &nullcmd, &oldest);
			ReadDeltaUsercmd(m, &oldest, &oldcmd);
			ReadDeltaUsercmd(m, &oldcmd, &newcmd);

			if (v->menunum)
			{
				if (newcmd.buttons&1 && !(oldcmd.buttons&1))
					Menu_Enter(cluster, v, 0);
				if (newcmd.buttons&2 && !(oldcmd.buttons&2))
					Menu_Enter(cluster, v, 1);
				if (newcmd.sidemove && !oldcmd.sidemove)
					Menu_Enter(cluster, v, newcmd.sidemove<0);

				if (newcmd.forwardmove && !oldcmd.forwardmove)
				{	//they pressed the button...
					if (newcmd.forwardmove < 0)
					{
						v->menuop+=1;
					}
					else
					{
						v->menuop-=1;
					}
				}
			}
			else
				PMove(v, &newcmd);
			break;
		case clc_tmove:
			v->origin[0] = ((signed short)ReadShort(m))/8.0f;
			v->origin[1] = ((signed short)ReadShort(m))/8.0f;
			v->origin[2] = ((signed short)ReadShort(m))/8.0f;
			break;

		case clc_upload:
			v->drop = true;
			return;

		default:
			v->drop = true;
			return;
		}
	}
}

void Menu_Enter(cluster_t *cluster, viewer_t *viewer, int buttonnum)
{
	//build a possible message, even though it'll probably not be sent

	sv_t *sv;
	int i, min;
	char buffer[2048];
	netmsg_t m;
	InitNetMsg(&m, buffer, sizeof(buffer));
	WriteByte(&m, svc_stufftext);

	switch(viewer->menunum)
	{
	default:
		break;

	case 1:
		if (!cluster->servers)
		{
			WriteString(&m, "messagemode\n");
			SendBufferToViewer(viewer, m.data, m.cursize, true);
		}
		else
		{
			if (viewer->menuop < 0)
				viewer->menuop = 0;
			i = 0;
			min = viewer->menuop - 10;
			if (min < 0)
				min = 0;
			for (sv = cluster->servers; sv && i<min; sv = sv->next, i++)
			{//skip over the early connections.
			}
			min+=20;
			for (; sv && i < min; sv = sv->next, i++)
			{
				if (i == viewer->menuop)
				{
					if (sv->parsingconnectiondata || !sv->modellist[1].name[0])
					{
						WriteString(&m, "echo But that stream isn't connected\n");
						SendBufferToViewer(viewer, m.data, m.cursize, true);
					}
					else
					{
						WriteString(&m, "cmd new\n");
						viewer->servercount++;
						viewer->server = sv;
						SendBufferToViewer(viewer, m.data, m.cursize, true);
						viewer->menunum = 0;
						viewer->thinksitsconnected = false;
					}
					break;
				}
			}
		}
		break;
	}
}

void Menu_Draw(cluster_t *cluster, viewer_t *viewer)
{
	char buffer[2048];
	char str[64];
	sv_t *sv;
	int i, min;
	unsigned char *s;

	netmsg_t m;
	InitNetMsg(&m, buffer, sizeof(buffer));

	WriteByte(&m, svc_centerprint);

	WriteString2(&m, "FTEQTV\n");
	if (strcmp(cluster->hostname, DEFAULT_HOSTNAME))
		WriteString2(&m, cluster->hostname);
	WriteString2(&m, "\n\n");

	switch(viewer->menunum)
	{
	default:
		WriteString2(&m, "bad menu");
		break;

	case 1:	//connections list

		if (!cluster->servers)
		{
			WriteString2(&m, "No active connections");
		}
		else
		{
			if (viewer->menuop < 0)
				viewer->menuop = 0;
			i = 0;
			min = viewer->menuop - 10;
			if (min < 0)
				min = 0;
			for (sv = cluster->servers; sv && i<min; sv = sv->next, i++)
			{//skip over the early connections.
			}
			min+=20;
			for (; sv && i < min; sv = sv->next, i++)
			{
				Info_ValueForKey(sv->serverinfo, "hostname", str, sizeof(str));
				if (sv->parsingconnectiondata || !sv->modellist[1].name[0])
					snprintf(str, sizeof(str), "%s", sv->server);

				if (i == viewer->menuop)
					for (s = (unsigned char *)str; *s; s++)
					{
						if ((unsigned)*s >= ' ')
							*s = 128 | (*s&~128);
					}
				WriteString2(&m, str);
				WriteString2(&m, "\n");
			}
		}
		break;

	case 2:	//admin menu
		WriteString2(&m, "    port");
		WriteString2(&m, (viewer->menuop==0)?" \r ":" : ");
		sprintf(str, "%-20i", cluster->qwlistenportnum);
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "hostname");
		WriteString2(&m, (viewer->menuop==1)?" \r ":" : ");
		sprintf(str, "%-20s", cluster->hostname);
		WriteString2(&m, str);
		WriteString2(&m, "\n");
		break;
	}


	WriteByte(&m, 0);
	SendBufferToViewer(viewer, m.data, m.cursize, true);
}

static const char dropcmd[] = {svc_stufftext, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', '\n', '\0'};

void QW_FreeViewer(cluster_t *cluster, viewer_t *viewer)
{
	int i;
	//note: unlink them yourself.

	Sys_Printf(cluster, "Dropping viewer %s\n", viewer->name);

	//spam them thrice, then forget about them
	Netchan_Transmit(cluster, &viewer->netchan, strlen(dropcmd)+1, dropcmd);
	Netchan_Transmit(cluster, &viewer->netchan, strlen(dropcmd)+1, dropcmd);
	Netchan_Transmit(cluster, &viewer->netchan, strlen(dropcmd)+1, dropcmd);

	for (i = 0; i < MAX_BACK_BUFFERS; i++)
	{
		if (viewer->backbuf[i].data)
			free(viewer->backbuf[i].data);
	}

	free(viewer);

	cluster->numviewers--;
}

void QW_UpdateUDPStuff(cluster_t *cluster)
{
	char buffer[MAX_MSGLEN*2];
	netadr_t from;
	int fromsize = sizeof(from);
	int read;
	int qport;
	netmsg_t m;

	viewer_t *v, *f;

	if (*cluster->master && (cluster->curtime > cluster->mastersendtime || cluster->mastersendtime > cluster->curtime + 4*1000*60))	//urm... time wrapped?
	{
		if (NET_StringToAddr(cluster->master, &from))
		{
			sprintf(buffer, "a\n%i\n0\n", cluster->mastersequence++);	//fill buffer with a heartbeat
//why is there no \xff\xff\xff\xff ?..
			NET_SendPacket(cluster, cluster->qwdsocket, strlen(buffer), buffer, from);
		}
		else
			Sys_Printf(cluster, "Cannot resolve master %s\n", cluster->master);

		cluster->mastersendtime = cluster->curtime + 3*1000*60;	//3 minuites.
	}

	m.data = buffer;
	m.cursize = 0;
	m.maxsize = MAX_MSGLEN;
	m.readpos = 0;

	for (;;)
	{
		read = recvfrom(cluster->qwdsocket, buffer, sizeof(buffer), 0, (struct sockaddr*)from, &fromsize);

		if (read <= 5)	//otherwise it's a runt or bad.
		{
			if (read < 0)	//it's bad.
				break;
			continue;
		}

		m.data = buffer;
		m.cursize = read;
		m.maxsize = MAX_MSGLEN;
		m.readpos = 0;

		if (*(int*)buffer == -1)
		{	//connectionless message
			ConnectionlessPacket(cluster, &from, &m);
			continue;
		}

		//read the qport
		ReadLong(&m);
		ReadLong(&m);
		qport = ReadShort(&m);

		for (v = cluster->viewers; v; v = v->next)
		{
			if (Net_CompareAddress(&v->netchan.remote_address, &from, v->netchan.qport, qport))
			{
				if (Netchan_Process(&v->netchan, &m))
				{
					v->netchan.outgoing_sequence = v->netchan.incoming_sequence;	//compensate for client->server packetloss.
					if (!v->server)
						v->maysend = true;
					else if (!v->chokeme || !cluster->chokeonnotupdated)
					{
						v->maysend = true;
						v->chokeme = cluster->chokeonnotupdated;
					}

					ParseQWC(cluster, v->server, v, &m);
				}
				break;
			}
		}
	}


	if (cluster->viewers && cluster->viewers->drop)
	{
		Sys_Printf(cluster, "Dropping client\n");
		f = cluster->viewers;
		cluster->viewers = f->next;

		QW_FreeViewer(cluster, f);
	}

	for (v = cluster->viewers; v; v = v->next)
	{
		if (v->next && v->next->drop)
		{	//free the next/
			Sys_Printf(cluster, "Dropping client\n");
			f = v->next;
			v->next = f->next;

			QW_FreeViewer(cluster, f);
		}

		if (v->maysend && (!v->server || !v->server->parsingconnectiondata))	//don't send incompleate connection data.
		{
			v->maysend = false;
			m.cursize = 0;
			if (v->thinksitsconnected)
			{
				SendPlayerStates(v->server, v, &m);
				UpdateStats(v->server, v);

				if (v->menunum)
					Menu_Draw(cluster, v);
			}
			Netchan_Transmit(cluster, &v->netchan, m.cursize, m.data);

			if (!v->netchan.message.cursize && v->backbuffered)
			{//shift the backbuffers around
				memcpy(v->netchan.message.data, v->backbuf[0].data,  v->backbuf[0].cursize);
				v->netchan.message.cursize = v->backbuf[0].cursize;
				for (read = 0; read < v->backbuffered; read++)
				{
					if (read == v->backbuffered-1)
					{
						v->backbuf[read].cursize = 0;
					}
					else
					{
						memcpy(v->backbuf[read].data, v->backbuf[read+1].data,  v->backbuf[read+1].cursize);
						v->backbuf[read].cursize = v->backbuf[read+1].cursize;
					}
				}
			}
		}
	}
}