/*
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"

#define MENU_NONE 0
#define MENU_SERVERS 1
#define MENU_ADMIN 2
#define MENU_ADMINSERVER 3

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

#ifdef _WIN32
int snprintf(char *buffer, int buffersize, char *format, ...)
{
	va_list		argptr;
	int ret;
	va_start (argptr, format);
	ret = _vsnprintf (buffer, buffersize, format, argptr);
	buffer[buffersize - 1] = '\0';
	va_end (argptr);

	return ret;
}
int vsnprintf(char *buffer, int buffersize, char *format, va_list		argptr)
{
	int ret;
	ret = _vsnprintf (buffer, buffersize, format, argptr);
	buffer[buffersize - 1] = '\0';

	return ret;
}
#endif

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, tv->thisplayer);
		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));

	if (tv && (tv->controller == viewer || !tv->controller))
		viewer->thisplayer = tv->thisplayer;
	else
		viewer->thisplayer = MAX_CLIENTS-1;
	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, int thisplayer)
{
	if (i < 0)
		return i;
	if (i >= MAX_CLIENTS)
		return i;

	for (; i < MAX_CLIENTS; i++)
	{
		if (i == thisplayer && !tv->controller)
		{
			WriteByte(msg, svc_updateuserinfo);
			WriteByte(msg, i);
			WriteLong(msg, i);
			WriteString2(msg, "\\*spectator\\1\\name\\");
			if (!tv || !*tv->hostname)
				WriteString(msg, "FTEQTV");
			else
				WriteString(msg, tv->hostname);

			WriteByte(msg, svc_updatefrags);
			WriteByte(msg, i);
			WriteShort(msg, 9999);

			WriteByte(msg, svc_updateping);
			WriteByte(msg, i);
			WriteShort(msg, 0);

			WriteByte(msg, svc_updatepl);
			WriteByte(msg, i);
			WriteByte(msg, 0);

			continue;
		}
		if (!tv)
			continue;
		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_updatefrags);
		WriteByte(msg, i);
		WriteShort(msg, tv->players[i].frags);

		WriteByte(msg, svc_updateping);
		WriteByte(msg, i);
		WriteShort(msg, tv->players[i].ping);

		WriteByte(msg, svc_updatepl);
		WriteByte(msg, i);
		WriteByte(msg, tv->players[i].packetloss);
	}

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

void QW_SetViewersServer(viewer_t *viewer, sv_t *sv)
{
	if (viewer->server)
		viewer->server->numviewers--;
	viewer->server = sv;
	if (viewer->server)
		viewer->server->numviewers++;
	if (!sv || !sv->parsingconnectiondata)
	{
		QW_StuffcmdToViewer(viewer, "cmd new\n");
	}
	viewer->servercount++;
	viewer->origin[0] = 0;
	viewer->origin[1] = 0;
	viewer->origin[2] = 0;
}

//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)
{
	sv_t *initialserver;
	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));
	if (!viewer)
	{
		Netchan_OutOfBandPrint(cluster, cluster->qwdsocket, *addr, "n" "Out of memory");
		return;
	}
	memset(viewer, 0, sizeof(viewer_t));

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

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

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

	viewer->server = initialserver;
	if (viewer->server)
		viewer->server->numviewers++;

	if (!initialserver)
	{
		QW_SetMenu(viewer, MENU_SERVERS);
	}
	else
	{
		viewer->menunum = -1;
		QW_SetMenu(viewer, MENU_NONE);
	}

	cluster->numviewers++;

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

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

	QW_PrintfToViewer(viewer, "Welcome to FTEQTV\n");
	QW_StuffcmdToViewer(viewer, "alias admin \"cmd admin\"\n");

		QW_StuffcmdToViewer(viewer, "alias \"proxy:up\" \"say proxy:menu up\"\n");
		QW_StuffcmdToViewer(viewer, "alias \"proxy:down\" \"say proxy:menu down\"\n");
		QW_StuffcmdToViewer(viewer, "alias \"proxy:right\" \"say proxy:menu right\"\n");
		QW_StuffcmdToViewer(viewer, "alias \"proxy:left\" \"say proxy:menu left\"\n");

		QW_StuffcmdToViewer(viewer, "alias \"proxy:select\" \"say proxy:menu select\"\n");

		QW_StuffcmdToViewer(viewer, "alias \"proxy:home\" \"say proxy:menu home\"\n");
		QW_StuffcmdToViewer(viewer, "alias \"proxy:end\" \"say proxy:menu end\"\n");
		QW_StuffcmdToViewer(viewer, "alias \"proxy:menu\" \"say proxy:menu\"\n");
		QW_StuffcmdToViewer(viewer, "alias \"proxy:backspace\" \"say proxy:menu backspace\"\n");

	QW_PrintfToViewer(viewer, "Type admin for the admin menu\n");

//	if 
}

void QW_SetMenu(viewer_t *v, int menunum)
{
	if ((v->menunum==MENU_NONE) != (menunum==MENU_NONE))
	{
		if (menunum != MENU_NONE)
		{
			QW_StuffcmdToViewer(v, "alias \"+proxfwd\" \"proxy:up\"\n");
			QW_StuffcmdToViewer(v, "alias \"+proxback\" \"proxy:down\"\n");
			QW_StuffcmdToViewer(v, "alias \"+proxleft\" \"proxy:left\"\n");
			QW_StuffcmdToViewer(v, "alias \"+proxright\" \"proxy:right\"\n");

			QW_StuffcmdToViewer(v, "alias \"-proxfwd\" \" \"\n");
			QW_StuffcmdToViewer(v, "alias \"-proxback\" \" \"\n");
			QW_StuffcmdToViewer(v, "alias \"-proxleft\" \" \"\n");
			QW_StuffcmdToViewer(v, "alias \"-proxright\" \" \"\n");
		}
		else
		{
			QW_StuffcmdToViewer(v, "alias \"+proxfwd\" \"+forward\"\n");
			QW_StuffcmdToViewer(v, "alias \"+proxback\" \"+back\"\n");
			QW_StuffcmdToViewer(v, "alias \"+proxleft\" \"+moveleft\"\n");
			QW_StuffcmdToViewer(v, "alias \"+proxright\" \"+moveright\"\n");

			QW_StuffcmdToViewer(v, "alias \"-proxfwd\" \"-forward\"\n");
			QW_StuffcmdToViewer(v, "alias \"-proxback\" \"-back\"\n");
			QW_StuffcmdToViewer(v, "alias \"-proxleft\" \"-moveleft\"\n");
			QW_StuffcmdToViewer(v, "alias \"-proxright\" \"-moveright\"\n");
		}
	}

	v->menunum = menunum;
	v->menuop = 0;
}

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; i++)
		{
			if (i == sv->thisplayer)
				continue;
			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 SendLocalPlayerState(sv_t *tv, viewer_t *v, int playernum, netmsg_t *msg)
{
	int flags;
	int j;

	WriteByte(msg, svc_playerinfo);
	WriteByte(msg, playernum);

	if (tv && tv->controller == v)
	{
		v->trackplayer = tv->thisplayer;
		flags = 0;
		if (tv->players[tv->thisplayer].current.weaponframe)
			flags |= PF_WEAPONFRAME;
		if (tv->players[tv->thisplayer].current.effects)
			flags |= PF_EFFECTS;
		for (j=0 ; j<3 ; j++)
			if (tv->players[tv->thisplayer].current.velocity[j])
				flags |= (PF_VELOCITY1<<j);

		WriteShort(msg, flags);
		WriteShort(msg, tv->players[tv->thisplayer].current.origin[0]);
		WriteShort(msg, tv->players[tv->thisplayer].current.origin[1]);
		WriteShort(msg, tv->players[tv->thisplayer].current.origin[2]);
		WriteByte(msg, tv->players[tv->thisplayer].current.frame);

		for (j=0 ; j<3 ; j++)
			if (flags & (PF_VELOCITY1<<j) )
				WriteShort (msg, tv->players[tv->thisplayer].current.velocity[j]);

		if (flags & PF_MODEL)
			WriteByte(msg, tv->players[tv->thisplayer].current.modelindex);
		if (flags & PF_SKINNUM)
			WriteByte(msg, tv->players[tv->thisplayer].current.skinnum);
		if (flags & PF_EFFECTS)
			WriteByte(msg, tv->players[tv->thisplayer].current.effects);
		if (flags & PF_WEAPONFRAME)
			WriteByte(msg, tv->players[tv->thisplayer].current.weaponframe);
	}
	else
	{
		WriteShort(msg, 0);
		WriteShort(msg, v->origin[0]*8);
		WriteShort(msg, v->origin[1]*8);
		WriteShort(msg, v->origin[2]*8);
		WriteByte(msg, 0);
	}
}

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


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

	if (tv)
	{
		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->simtime - tv->oldpackettime)/1000.0f) / ((tv->nextpackettime - tv->oldpackettime)/1000.0f);
		if (lerp < 0)
			lerp = 0;
		if (lerp > 1)
			lerp = 1;

		if (tv->controller == v)
			lerp = 1;

		for (i = 0; i < MAX_CLIENTS; i++)
		{
			if (i == v->thisplayer)
			{
				SendLocalPlayerState(tv, v, i, msg);
				continue;
			}
			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;

		SendLocalPlayerState(tv, v, v->thisplayer, msg);
	}


	e = &v->frame[v->netchan.outgoing_sequence&(ENTITY_FRAMES-1)];
	e->numents = 0;
	if (tv)
		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);

	if (tv && tv->nailcount)
	{
		WriteByte(msg, svc_nails);
		WriteByte(msg, tv->nailcount);
		for (i = 0; i < tv->nailcount; i++)
		{
			WriteByte(msg, tv->nails[i].bits[0]);
			WriteByte(msg, tv->nails[i].bits[1]);
			WriteByte(msg, tv->nails[i].bits[2]);
			WriteByte(msg, tv->nails[i].bits[3]);
			WriteByte(msg, tv->nails[i].bits[4]);
			WriteByte(msg, tv->nails[i].bits[5]);
		}
	}
}

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 (qtv && qtv->controller == v)
		stats = qtv->players[qtv->thisplayer].stats;
	else if (v->trackplayer != -1 || !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 thisplayer)
{
	int r, ni;
	r = bufnum;

	ni = SendCurrentUserinfos(qtv, curmsgsize, msg, bufnum, thisplayer);
	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];
	if (v->server && v->server->controller == v)
	{
		v->origin[0] = v->server->players[v->server->thisplayer].current.origin[0]/8.0f;
		v->origin[1] = v->server->players[v->server->thisplayer].current.origin[1]/8.0f;
		v->origin[2] = v->server->players[v->server->thisplayer].current.origin[2]/8.0f;
		return;
	}
	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, sv_t *qtv, viewer_t *v, char *message, qboolean noupwards)
{
	char buf[1024];
	netmsg_t msg;

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

	if (*v->expectcommand)
	{
		buf[sizeof(buf)-1] = '\0';
		if (!strcmp(v->expectcommand, "hostname"))
		{
			strncpy(cluster->hostname, message, sizeof(cluster->hostname));
			cluster->hostname[sizeof(cluster->hostname)-1] = '\0';
		}
		else if (!strcmp(v->expectcommand, "master"))
		{
			strncpy(cluster->master, message, sizeof(cluster->master));
			cluster->master[sizeof(cluster->master)-1] = '\0';
			if (!strcmp(cluster->master, "."))
				*cluster->master = '\0';
			cluster->mastersendtime = cluster->curtime;
		}
		else if (!strcmp(v->expectcommand, "addserver"))
		{
			snprintf(buf, sizeof(buf), "tcp:%s", message);
			qtv = QTV_NewServerConnection(cluster, buf, false, false, false);
			if (qtv)
			{
				QW_SetViewersServer(v, qtv);
				QW_PrintfToViewer(v, "Connected\n", message);
			}
			else
				QW_PrintfToViewer(v, "Failed to connect to server \"%s\", connection aborted\n", message);
		}
		else if (!strcmp(v->expectcommand, "admin"))
		{
			if (!strcmp(message, cluster->password))
			{
				QW_SetMenu(v, MENU_ADMIN);
				v->isadmin = true;
				Sys_Printf(cluster, "Player %s logs in as admin\n", v->name);
			}
			else
			{
				QW_PrintfToViewer(v, "Admin password incorrect\n");
				Sys_Printf(cluster, "Player %s gets incorrect admin password\n", v->name);
			}
		}
		else if (!strcmp(v->expectcommand, "adddemo"))
		{
			snprintf(buf, sizeof(buf), "file:%s", message);
			qtv = QTV_NewServerConnection(cluster, buf, false, false, false);
			if (!qtv)
				QW_PrintfToViewer(v, "Failed to play demo \"%s\"\n", message);
			else
			{
				QW_SetViewersServer(v, qtv);
				QW_PrintfToViewer(v, "Opened demo file.\n", message);
			}
		}
		else if (!strcmp(v->expectcommand, "setmvdport"))
		{
			int newp;
			int news;
			if (qtv)
			{
				newp = atoi(message);

				if (newp)
				{
					news = Net_MVDListen(newp);

					if (news != INVALID_SOCKET)
					{
						if (qtv->listenmvd != INVALID_SOCKET)
							closesocket(qtv->listenmvd);
						qtv->listenmvd = news;
						qtv->tcplistenportnum = newp;
						qtv->disconnectwhennooneiswatching = false;
					}
				}
				else if (qtv->listenmvd != INVALID_SOCKET)
				{
					closesocket(qtv->listenmvd);
					qtv->listenmvd = INVALID_SOCKET;
				}
			}
			else
				QW_PrintfToViewer(v, "You were disconnected from that stream\n");
		}
		else
		{
			QW_PrintfToViewer(v, "Command %s was not recognised\n", v->expectcommand);
		}

		*v->expectcommand = '\0';
		return;
	}
	if (!strncmp(message, ".help", 5))
	{
		QW_PrintfToViewer(v,	"Website: http://www.fteqw.com/\n"
								"Commands:\n"
								".qw qwserver:port\n"
								".qtv tcpserver:port\n"
								".demo gamedir/demoname.mvd\n"
								".disconnect\n");
	}
	else if (!strncmp(message, ".connect ", 9) || !strncmp(message, ".qw ", 4))
	{
		if (!strncmp(message, ".qw ", 4))
			message += 4;
		else
			message += 9;
		snprintf(buf, sizeof(buf), "udp:%s", message);
		qtv = QTV_NewServerConnection(cluster, buf, false, true, true);
		if (qtv)
		{
			QW_SetMenu(v, MENU_NONE);
			QW_SetViewersServer(v, qtv);
			QW_PrintfToViewer(v, "Connected\n", message);
		}
		else
			QW_PrintfToViewer(v, "Failed to connect to server \"%s\", connection aborted\n", message);
	}
	else if (!strncmp(message, ".join ", 6))
	{
		message += 6;
		snprintf(buf, sizeof(buf), "udp:%s", message);
		qtv = QTV_NewServerConnection(cluster, buf, false, true, false);
		if (qtv)
		{
			QW_SetMenu(v, MENU_NONE);
			QW_SetViewersServer(v, qtv);
			qtv->controller = v;
			QW_PrintfToViewer(v, "Connected\n", message);
		}
		else
			QW_PrintfToViewer(v, "Failed to connect to server \"%s\", connection aborted\n", message);
	}
	else if (!strncmp(message, ".qtv ", 5))
	{
		message += 5;
		snprintf(buf, sizeof(buf), "tcp:%s", message);
		qtv = QTV_NewServerConnection(cluster, buf, false, true, true);
		if (qtv)
		{
			QW_SetMenu(v, MENU_NONE);
			QW_SetViewersServer(v, qtv);
			QW_PrintfToViewer(v, "Connected\n", message);
		}
		else
			QW_PrintfToViewer(v, "Failed to connect to server \"%s\", connection aborted\n", message);
	}
	else if (!strncmp(message, ".demo ", 6))
	{
		message += 6;
		snprintf(buf, sizeof(buf), "file:%s", message);
		qtv = QTV_NewServerConnection(cluster, buf, false, true, true);
		if (qtv)
		{
			QW_SetMenu(v, MENU_NONE);
			QW_SetViewersServer(v, qtv);
			QW_PrintfToViewer(v, "Connected\n", message);
		}
		else
			QW_PrintfToViewer(v, "Failed to connect to server \"%s\", connection aborted\n", message);
	}
	else if (!strncmp(message, ".disconnect", 11))
	{
		QW_SetMenu(v, MENU_SERVERS);
		QW_SetViewersServer(v, NULL);
		QW_PrintfToViewer(v, "Connected\n", message);
	}
	else if (!strncmp(message, ".admin", 11))
	{
		QW_StuffcmdToViewer(v, "cmd admin\n");
	}
	else if (!strncmp(message, "proxy:menu up", 13))
	{
		v->menuop -= 1;
	}
	else if (!strncmp(message, "proxy:menu down", 15))
	{
		v->menuop += 1;
	}
	else if (!strncmp(message, "proxy:menu right", 16))
	{
		Menu_Enter(cluster, v, 1);
	}
	else if (!strncmp(message, "proxy:menu left", 15))
	{
		Menu_Enter(cluster, v, -1);
	}
	else if (!strncmp(message, "proxy:menu select", 17))
	{
		Menu_Enter(cluster, v, 0);
	}
	else if (!strncmp(message, "proxy:menu home", 15))
	{
		v->menuop -= 100000;
	}
	else if (!strncmp(message, "proxy:menu end", 14))
	{
		v->menuop += 100000;
	}
	else if (!strncmp(message, "proxy:menu back", 15))
	{
	}
	else if (!strncmp(message, "proxy:menu", 10))
	{
		if (v->menunum)
			Menu_Enter(cluster, v, 0);
		else
			QW_SetMenu(v, MENU_SERVERS);
	}
	else if (!strncmp(message, ".menu bind", 10) || !strncmp(message, "proxy:menu bindstd", 18))
	{
		QW_StuffcmdToViewer(v, "bind uparrow proxy:menu up\n");
		QW_StuffcmdToViewer(v, "bind downarrow proxy:menu down\n");
		QW_StuffcmdToViewer(v, "bind rightarrow proxy:menu right\n");
		QW_StuffcmdToViewer(v, "bind leftarrow proxy:menu left\n");

		QW_StuffcmdToViewer(v, "bind enter proxy:menu select\n");

		QW_StuffcmdToViewer(v, "bind home proxy:menu home\n");
		QW_StuffcmdToViewer(v, "bind end proxy:menu end\n");
		QW_StuffcmdToViewer(v, "bind pause proxy:menu\n");
		QW_StuffcmdToViewer(v, "bind backspace proxy:back up\n");
	}
	else
	{
		*v->expectcommand = '\0';

		if (cluster->notalking)
			return;

		if (qtv && qtv->usequkeworldprotocols && !noupwards)
		{
			if (qtv->controller == v || !*v->name)
				SendClientCommand(qtv, "say %s\n", message);
			else
				SendClientCommand(qtv, "say %s: %s\n", v->name, message);
		}
		else
		{

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

	va_start (argptr, format);
	vsnprintf (buf+2, sizeof(buf)-2, format, argptr);
	va_end (argptr);

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

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


void QW_StuffcmdToViewer(viewer_t *v, char *format, ...)
{
	va_list		argptr;
	char buf[1024];

	va_start (argptr, format);
	vsnprintf (buf+1, sizeof(buf)-1, format, argptr);
	va_end (argptr);

	buf[0] = svc_stufftext;
	SendBufferToViewer(v, buf, strlen(buf)+1, true);
}

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


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"))
			{
				if (qtv && qtv->parsingconnectiondata)
					QW_StuffcmdToViewer(v, "cmd new\n");
				else
				{
					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 crc;
					int r;
					char *s;
					s = buf+9;
					while((*s >= '0' && *s <= '9') || *s == '-')
						s++;
					while(*s == ' ')
						s++;
					r = atoi(s);

					if (r == 0)
					{
						while((*s >= '0' && *s <= '9') || *s == '-')
							s++;
						while(*s == ' ')
							s++;
						crc = atoi(s);

						if (qtv && qtv->controller == v)
						{
							if (!qtv->bsp)
							{
								QW_PrintfToViewer(v, "Proxy was unable to check your map version\n");
								qtv->drop = true;
							}
							else if (crc != BSP_Checksum(qtv->bsp))
							{
								QW_PrintfToViewer(v, "Your map (%s) does not match the servers\n", qtv->modellist[1].name);
								qtv->drop = true;
							}
						}
					}

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

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

					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;
					if (qtv && qtv->ispaused)
					{
						char msgb[] = {svc_setpause, 1};
						SendBufferToViewer(v, msgb, sizeof(msgb), 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(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))
				QTV_Say(cluster, qtv, v, buf+5, false);
			else if (!strncmp(buf, "say ", 4))
				QTV_Say(cluster, qtv, v, buf+4, false);

			else if (!strncmp(buf, "say_team \"", 10))
				QTV_Say(cluster, qtv, v, buf+10, true);
			else if (!strncmp(buf, "say_team ", 9))
				QTV_Say(cluster, qtv, v, buf+9, true);

			else if (!strncmp(buf, "servers", 7))
			{
				QW_SetMenu(v, MENU_SERVERS);
			}
			else if (!strncmp(buf, "reset", 5))
			{
				QW_SetViewersServer(v, NULL);
				QW_SetMenu(v, MENU_SERVERS);
			}
			else if (!strncmp(buf, "admin", 5))
			{
				if (!*cluster->password)
				{
					if (Netchan_IsLocal(v->netchan.remote_address))
					{
						Sys_Printf(cluster, "Local player %s logs in as admin\n", v->name);
						QW_SetMenu(v, MENU_ADMIN);
						v->isadmin = true;
					}
					else
						QW_PrintfToViewer(v, "There is no admin password set\nYou may not log in.\n");
				}
				else if (v->isadmin)
					QW_SetMenu(v, MENU_ADMIN);
				else
				{
					strcpy(v->expectcommand, "admin");
					QW_StuffcmdToViewer(v, "echo Please enter the rcon password\nmessagemode\n");
				}
			}

			else if (!strncmp(buf, "setinfo", 5))
			{
				#define TOKENIZE_PUNCTUATION ""
				#define MAX_ARGS 3
				#define ARG_LEN 256

				int i;
				char arg[MAX_ARGS][ARG_LEN];
				char *argptrs[MAX_ARGS];
				char *command = buf;

				for (i = 0; i < MAX_ARGS; i++)
				{
					command = COM_ParseToken(command, arg[i], ARG_LEN, TOKENIZE_PUNCTUATION);
					argptrs[i] = arg[i];
				}

				Info_SetValueForStarKey(v->userinfo, arg[1], arg[2], sizeof(v->userinfo));
				Info_ValueForKey(v->userinfo, "name", v->name, sizeof(v->name));

				if (v->server && v->server->controller == v)
					SendClientCommand(v->server, "%s", buf);
			}

			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
			{
				if (v->server && v->server->controller == v)
					SendClientCommand(v->server, "%s", buf);
				else
					Sys_Printf(cluster, "Client sent unknown string command: %s\n", buf);
			}

			break;

		case clc_move:
			v->lost = ReadByte(m);
			ReadByte(m);
			ReadDeltaUsercmd(m, &nullcmd, &v->ucmds[0]);
			ReadDeltaUsercmd(m, &v->ucmds[0], &v->ucmds[1]);
			ReadDeltaUsercmd(m, &v->ucmds[1], &v->ucmds[2]);
/*
			if (v->server && v->server->controller)
			{
				v->
			}
*/
/*			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;
					}
				}
			}
*/
			PMove(v, &v->ucmds[2]);
			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;

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

	case MENU_ADMINSERVER:
		if (viewer->server)
		{
			i = 0;
			sv = viewer->server;
			if (i++ == viewer->menuop)
			{	//mvd port
				QW_StuffcmdToViewer(viewer, "echo Please enter a new tcp port number\nmessagemode\n");
				strcpy(viewer->expectcommand, "setmvdport");
			}
			if (i++ == viewer->menuop)
			{	//disconnect
				QTV_Shutdown(viewer->server);
			}
			if (i++ == viewer->menuop)
			{	//back
				QW_SetMenu(viewer, MENU_ADMIN);
			}
			break;
		}
		//fallthrough
	case MENU_SERVERS:
		if (!cluster->servers)
		{
			QW_StuffcmdToViewer(viewer, "echo Please enter a server ip\nmessagemode\n");
			strcpy(viewer->expectcommand, "insecadddemo");
		}
		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])
					{
						QW_PrintfToViewer(viewer, "But that stream isn't connected\n");
					}
					else*/
					{
						QW_SetViewersServer(viewer, sv);
						QW_SetMenu(viewer, MENU_NONE);
						viewer->thinksitsconnected = false;
					}
					break;
				}
			}
		}
		break;
	case MENU_ADMIN:
		i = 0;
		if (i++ == viewer->menuop)
		{	//connection stuff
			QW_SetMenu(viewer, MENU_ADMINSERVER);
		}
		if (i++ == viewer->menuop)
		{	//qw port
			QW_StuffcmdToViewer(viewer, "echo You will need to reconnect\n");
			cluster->qwlistenportnum += buttonnum?-1:1;
		}
		if (i++ == viewer->menuop)
		{	//hostname
			strcpy(viewer->expectcommand, "hostname");
			QW_StuffcmdToViewer(viewer, "echo Please enter the new hostname\nmessagemode\n");
		}
		if (i++ == viewer->menuop)
		{	//master
			strcpy(viewer->expectcommand, "master");
			QW_StuffcmdToViewer(viewer, "echo Please enter the master dns or ip\necho Enter '.' for masterless mode\nmessagemode\n");
		}
		if (i++ == viewer->menuop)
		{	//password
			strcpy(viewer->expectcommand, "password");
			QW_StuffcmdToViewer(viewer, "echo Please enter the new rcon password\nmessagemode\n");
		}
		if (i++ == viewer->menuop)
		{	//add server
			strcpy(viewer->expectcommand, "messagemode");
			QW_StuffcmdToViewer(viewer, "echo Please enter the new qtv server dns or ip\naddserver\n");
		}
		if (i++ == viewer->menuop)
		{	//add demo
			strcpy(viewer->expectcommand, "adddemo");
			QW_StuffcmdToViewer(viewer, "echo Please enter the name of the demo to play\nmessagemode\n");
		}
		if (i++ == viewer->menuop)
		{	//choke
			cluster->chokeonnotupdated ^= 1;
		}
		if (i++ == viewer->menuop)
		{	//late forwarding
			cluster->lateforward ^= 1;
		}
		if (i++ == viewer->menuop)
		{	//no talking
			cluster->notalking ^= 1;
		}
		if (i++ == viewer->menuop)
		{	//nobsp
			cluster->nobsp ^= 1;
		}
		if (i++ == viewer->menuop)
		{	//back
			QW_SetMenu(viewer, MENU_NONE);
		}


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

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

	case 3:	//per-connection options
		if (viewer->server)
		{
			sv = viewer->server;
			WriteString2(&m, "\n\nConnection Admin\n");
			WriteString2(&m, sv->hostname);
			if (sv->file)
				WriteString2(&m, " (demo)");
			WriteString2(&m, "\n\n");

			if (viewer->menuop < 0)
				viewer->menuop = 0;

			i = 0;
			WriteString2(&m, "            port");
			WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
			if (sv->listenmvd == INVALID_SOCKET)
				sprintf(str, "!%-19i", sv->tcplistenportnum);
			else
				sprintf(str, "%-20i", sv->tcplistenportnum);
			WriteString2(&m, str);
			WriteString2(&m, "\n");

			WriteString2(&m, "      disconnect");
			WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
			sprintf(str, "%-20s", "...");
			WriteString2(&m, str);
			WriteString2(&m, "\n");

			WriteString2(&m, "            back");
			WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
			sprintf(str, "%-20s", "...");
			WriteString2(&m, str);
			WriteString2(&m, "\n");

			if (viewer->menuop >= i)
				viewer->menuop = i - 1;

			break;
		}
		//fallthrough
	case 1:	//connections list

		WriteString2(&m, "\n\nServers\n\n");

		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, "\n\nCluster Admin\n\n");

		if (viewer->menuop < 0)
			viewer->menuop = 0;
		i = 0;

		WriteString2(&m, " this connection");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", "...");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "            port");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20i", cluster->qwlistenportnum);
		WriteString2(&m, str);
		WriteString2(&m, "\n");

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

		WriteString2(&m, "          master");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", cluster->master);
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "        password");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", "...");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "      add server");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", "...");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "        add demo");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", "...");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "           choke");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", cluster->chokeonnotupdated?"yes":"no");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "delay forwarding");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", cluster->lateforward?"yes":"no");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "         talking");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", cluster->notalking?"no":"yes");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "           nobsp");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", cluster->nobsp?"yes":"no");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		WriteString2(&m, "            back");
		WriteString2(&m, (viewer->menuop==(i++))?" \r ":" : ");
		sprintf(str, "%-20s", "...");
		WriteString2(&m, str);
		WriteString2(&m, "\n");

		if (viewer->menuop >= i)
			viewer->menuop = i - 1;
		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);
	}

	if (viewer->server->controller == viewer)
		viewer->server->drop = true;
	if (viewer->server)
		viewer->server->numviewers--;

	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;

	sv_t *useserver;
	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, 27000))
		{
			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))
				{
					useserver = v->server;
					if (useserver && useserver->parsingconnectiondata)
						useserver = NULL;

					v->netchan.outgoing_sequence = v->netchan.incoming_sequence;	//compensate for client->server packetloss.
					if (v->server && v->server->controller == v)
					{
//						v->maysend = true;
						v->server->maysend =  true;
//						v->server->netchan.outgoing_sequence = v->netchan.incoming_sequence;
					}
					else
					{
						if (!v->server)
							v->maysend = true;
						else if (!v->chokeme || !cluster->chokeonnotupdated)
						{
							v->maysend = true;
							v->chokeme = cluster->chokeonnotupdated;
						}
					}

					ParseQWC(cluster, useserver, v, &m);

					if (v->server && v->server->controller == v)
					{
						QTV_Run(v->server);
					}
				}
				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)	//don't send incompleate connection data.
		{
			useserver = v->server;
			if (useserver && useserver->parsingconnectiondata)
				useserver = NULL;

			v->maysend = false;
			m.cursize = 0;
			if (v->thinksitsconnected)
			{
				SendPlayerStates(useserver, v, &m);
				UpdateStats(useserver, v);

				if (v->menunum)
					Menu_Draw(cluster, v);
				else if (v->server && v->server->parsingconnectiondata)
				{
					WriteByte(&m, svc_centerprint);
					WriteString(&m, v->server->status);
				}
			}
			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;
					}
				}
			}
		}
	}
}
