/*
	client.c

	quakeworld client processing

	Copyright (C) 2004 Bill Currie <bill@taniwha.org>

	Author: Bill Currie
	Date: 2004/02/20

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#include <stdio.h>
#include <stdlib.h>

#include "QF/cbuf.h"
#include "QF/cmd.h"
#include "QF/checksum.h"
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/idparse.h"
#include "QF/info.h"
#include "QF/set.h"
#include "QF/sys.h"
#include "QF/va.h"

#include "QF/simd/vec4f.h"

#include "qw/bothdefs.h"
#include "qw/msg_ucmd.h"
#include "qw/protocol.h"

#include "qtv/include/client.h"
#include "qtv/include/connection.h"
#include "qtv/include/qtv.h"
#include "qtv/include/server.h"

int client_count;
static client_t *clients;

typedef struct ucmd_s {
	const char *name;
	void        (*func) (client_t *cl, void *userdata);
	unsigned    no_redirect:1;
	unsigned    overridable:1;
	unsigned    freeable:1;
	void        *userdata;
	void        (*on_free) (void *userdata);
} ucmd_t;

static void
client_drop (client_t *cl)
{
	MSG_WriteByte (&cl->netchan.message, svc_disconnect);
	qtv_printf ("Client %s removed\n", Info_ValueForKey (cl->userinfo, "name"));
	cl->drop = 1;
}

static void
cl_new_f (client_t *cl, void *unused)
{
	if (!cl->server) {
		qtv_printf ("\"cmd list\" for a list of servers\n");
		qtv_printf ("\"cmd connect <servername>\" to connect to a server\n");
	} else {
		Client_New (cl);
	}
}

static void
cl_modellist_f (client_t *cl, void *unused)
{
	server_t   *sv = cl->server;
	unsigned    n;
	char      **s;

	if (atoi (Cmd_Argv (1)) != sv->spawncount) {
		qtv_printf ("modellist from different level\n");
		Client_New (cl);
		return;
	}
	n = atoi (Cmd_Argv (2));
	if (n >= MAX_MODELS) {
		qtv_printf ("invalid modellist index\n");
		Client_New (cl);
		return;
	}

	MSG_WriteByte (&cl->netchan.message, svc_modellist);
	MSG_WriteByte (&cl->netchan.message, n);
	for (s = sv->modellist + n;
		 *s && cl->netchan.message.cursize < (MAX_MSGLEN /2);
		 s++, n++)
		MSG_WriteString (&cl->netchan.message, *s);
	MSG_WriteByte (&cl->netchan.message, 0);
	if (*s)
		MSG_WriteByte (&cl->netchan.message, n);
	else
		MSG_WriteByte (&cl->netchan.message, 0);
}

static void
cl_soundlist_f (client_t *cl, void *unused)
{
	server_t   *sv = cl->server;
	unsigned    n;
	char      **s;

	if (!cl->server)
		return;
	if (atoi (Cmd_Argv (1)) != sv->spawncount) {
		qtv_printf ("soundlist from different level\n");
		Client_New (cl);
		return;
	}
	n = atoi (Cmd_Argv (2));
	if (n >= MAX_SOUNDS) {
		qtv_printf ("invalid soundlist index\n");
		Client_New (cl);
		return;
	}

	MSG_WriteByte (&cl->netchan.message, svc_soundlist);
	MSG_WriteByte (&cl->netchan.message, n);
	for (s = sv->soundlist + n;
		 *s && cl->netchan.message.cursize < (MAX_MSGLEN /2);
		 s++, n++)
		MSG_WriteString (&cl->netchan.message, *s);
	MSG_WriteByte (&cl->netchan.message, 0);
	if (*s)
		MSG_WriteByte (&cl->netchan.message, n);
	else
		MSG_WriteByte (&cl->netchan.message, 0);
}

static void
cl_prespawn_f (client_t *cl, void *unused)
{
	const char *cmd;
	server_t   *sv = cl->server;
	int         buf, size;
	sizebuf_t  *msg;

	if (!cl->server)
		return;
	if (atoi (Cmd_Argv (1)) != sv->spawncount) {
		qtv_printf ("prespawn from different level\n");
		Client_New (cl);
		return;
	}
	buf = atoi (Cmd_Argv (2));
	if (buf >= sv->num_signon_buffers)
		buf = 0;
	if (buf == sv->num_signon_buffers - 1)
		cmd = va (0, "cmd spawn %i 0\n", cl->server->spawncount);
	else
		cmd = va (0, "cmd prespawn %i %i\n", cl->server->spawncount, buf + 1);
	size = sv->signon_buffer_size[buf] + 1 + strlen (cmd) + 1;
	msg = MSG_ReliableCheckBlock (&cl->backbuf, size);
	SZ_Write (msg, sv->signon_buffers[buf], sv->signon_buffer_size[buf]);
	MSG_WriteByte (msg, svc_stufftext);
	MSG_WriteString (msg, cmd);
}

static void
cl_spawn_f (client_t *cl, void *unused)
{
	const char *cmd;
	server_t   *sv = cl->server;
	const char *info;
	player_t   *pl;
	int         i;
	sizebuf_t  *msg;

	if (!cl->server)
		return;
	if (atoi (Cmd_Argv (1)) != sv->spawncount) {
		qtv_printf ("spawn from different level\n");
		Client_New (cl);
		return;
	}
	for (i = 0, pl = sv->players; i < MAX_SV_PLAYERS; i++, pl++) {
		if (!pl->info)
			continue;
		msg = MSG_ReliableCheckBlock (&cl->backbuf,
									  24 + Info_CurrentSize(pl->info));
		MSG_WriteByte (msg, svc_updatefrags);
		MSG_WriteByte (msg, i);
		MSG_WriteShort (msg, pl->frags);
		MSG_WriteByte (msg, svc_updateping);
		MSG_WriteByte (msg, i);
		MSG_WriteShort (msg, pl->ping);
		MSG_WriteByte (msg, svc_updatepl);
		MSG_WriteByte (msg, i);
		MSG_WriteByte (msg, pl->pl);
		MSG_WriteByte (msg, svc_updateentertime);
		MSG_WriteByte (msg, i);
		MSG_WriteFloat (msg, pl->time);
		info = pl->info ? Info_MakeString (pl->info, 0) : "";
		MSG_WriteByte (msg, svc_updateuserinfo);
		MSG_WriteByte (msg, i);
		MSG_WriteLong (msg, pl->uid);
		MSG_WriteString (msg, info);
		if (cl->backbuf.num_backbuf)
			MSG_Reliable_FinishWrite (&cl->backbuf);
	}
	for (i = 0; i < MAX_LIGHTSTYLES; i++) {
		MSG_ReliableWrite_Begin (&cl->backbuf, svc_lightstyle,
								 3 + (sv->lightstyles[i] ?
									  strlen (sv->lightstyles[i]) : 1));
		MSG_ReliableWrite_Byte (&cl->backbuf, i);
		MSG_ReliableWrite_String (&cl->backbuf, sv->lightstyles[i]);
	}
	MSG_ReliableWrite_Begin (&cl->backbuf, svc_updatestatlong, 6);
	MSG_ReliableWrite_Byte (&cl->backbuf, STAT_TOTALSECRETS);
	MSG_ReliableWrite_Long (&cl->backbuf,
							sv->players[0].stats[STAT_TOTALSECRETS]);
	MSG_ReliableWrite_Begin (&cl->backbuf, svc_updatestatlong, 6);
	MSG_ReliableWrite_Byte (&cl->backbuf, STAT_TOTALMONSTERS);
	MSG_ReliableWrite_Long (&cl->backbuf,
							sv->players[0].stats[STAT_TOTALMONSTERS]);
	MSG_ReliableWrite_Begin (&cl->backbuf, svc_updatestatlong, 6);
	MSG_ReliableWrite_Byte (&cl->backbuf, STAT_SECRETS);
	MSG_ReliableWrite_Long (&cl->backbuf, sv->players[0].stats[STAT_SECRETS]);
	MSG_ReliableWrite_Begin (&cl->backbuf, svc_updatestatlong, 6);
	MSG_ReliableWrite_Byte (&cl->backbuf, STAT_SECRETS);
	MSG_ReliableWrite_Long (&cl->backbuf, sv->players[0].stats[STAT_SECRETS]);
	cmd = "skins\n";
	MSG_ReliableWrite_Begin (&cl->backbuf, svc_stufftext, strlen (cmd) + 2);
	MSG_ReliableWrite_String (&cl->backbuf, cmd);
}

static void
cl_begin_f (client_t *cl, void *unused)
{
	server_t   *sv = cl->server;

	if (!cl->server)
		return;
	if (atoi (Cmd_Argv (1)) != sv->spawncount) {
		qtv_printf ("spawn from different level\n");
		Client_New (cl);
		return;
	}
	cl->connected = 1;
}

static void
cl_drop_f (client_t *cl, void *unused)
{
	if (cl->server)
		Server_Disconnect (cl);
	client_drop (cl);
}

static void
cl_pings_f (client_t *cl, void *unused)
{
}

static void
cl_rate_f (client_t *cl, void *unused)
{
}

static void
cl_say_f (client_t *cl, void *unused)
{
}

static void
cl_setinfo_f (client_t *cl, void *unused)
{
}

static void
cl_serverinfo_f (client_t *cl, void *unused)
{
	if (cl->server) {
		Info_Print (cl->server->info);
		return;
	}
	qtv_printf ("not connected to a server");
}

static void
cl_download_f (client_t *cl, void *unused)
{
	qtv_printf ("download denied\n");
	MSG_ReliableWrite_Begin (&cl->backbuf, svc_download, 4);
	MSG_ReliableWrite_Short (&cl->backbuf, -1);
	MSG_ReliableWrite_Byte (&cl->backbuf, 0);
}

static void
cl_nextdl_f (client_t *cl, void *unused)
{
}

static void
cl_ptrack_f (client_t *cl, void *unused)
{
	int         i;

	if (Cmd_Argc () != 2) {
		cl->spec_track = 0;
		return;
	}
	i = atoi (Cmd_Argv (1));
	if (i < 0 || i >= MAX_CLIENTS) {
		cl->spec_track = 0;
		qtv_printf ("Invalid client to track\n");
		return;
	}
	cl->spec_track = 1 << i;
}

static void
cl_list_f (client_t *cl, void *unused)
{
	Server_List ();
}

static void
cl_connect_f (client_t *cl, void *unused)
{
	if (cl->server) {
		qtv_printf ("already connected to server %s\n", cl->server->name);
		qtv_printf ("\"cmd disconnect\" first\n");
		return;
	}
	Server_Connect (Cmd_Argv (1), cl);
}

static void
cl_disconnect_f (client_t *cl, void *unused)
{
	if (!cl->server) {
		qtv_printf ("not connected to a server\n");
		return;
	}
	client_drop (cl);
}

static ucmd_t ucmds[] = {
	{"new",			cl_new_f,			0, 0},
	{"modellist",	cl_modellist_f,		0, 0},
	{"soundlist",	cl_soundlist_f,		0, 0},
	{"prespawn",	cl_prespawn_f,		0, 0},
	{"spawn",		cl_spawn_f,			0, 0},
	{"begin",		cl_begin_f,			1, 0},

	{"drop",		cl_drop_f,			1, 0},
	{"pings",		cl_pings_f,			0, 0},

// issued by hand at client consoles
	{"rate",		cl_rate_f,			0, 0},
	{"kill",		0,					1, 1},
	{"pause",		0,					1, 0},
	{"msg",			0,					0, 0},

	{"say",			cl_say_f,			1, 1},
	{"say_team",	cl_say_f,			1, 1},

	{"setinfo",		cl_setinfo_f,		1, 0},

	{"serverinfo",	cl_serverinfo_f,	0, 0},

	{"download",	cl_download_f,		1, 0},
	{"nextdl",		cl_nextdl_f,		0, 0},

	{"ptrack",		cl_ptrack_f,		0, 1},		// ZOID - used with autocam

	{"snap",		0,					0, 0},

	{"list",		cl_list_f,			0, 0},
	{"connect",		cl_connect_f,		0, 0},
	{"disconnect",	cl_disconnect_f,	0, 0},
};

static hashtab_t *ucmd_table;
int (*ucmd_unknown)(void);

static void
client_exec_command (client_t *cl, const char *s)
{
	ucmd_t     *u;

	COM_TokenizeString (s, qtv_args);
	cmd_args = qtv_args;

	u = (ucmd_t*) Hash_Find (ucmd_table, qtv_args->argv[0]->str);

	if (!u) {
		if (!ucmd_unknown || !ucmd_unknown ()) {
			qtv_begin_redirect (RD_CLIENT, cl);
			qtv_printf ("Bad user command: %s\n", qtv_args->argv[0]->str);
			qtv_end_redirect ();
		}
	} else {
		if (u->func) {
			if (!u->no_redirect)
				qtv_begin_redirect (RD_CLIENT, cl);
			u->func (cl, u->userdata);
			if (!u->no_redirect)
				qtv_end_redirect ();
		}
	}
}

static void
spectator_move (client_t *cl, usercmd_t *ucmd)
{
	float       frametime = ucmd->msec * 0.001;
	float       control, drop, friction, fmove, smove, speed, newspeed;
	float       currentspeed, addspeed, accelspeed, wishspeed;
	int         i;
	vec3_t      wishdir, wishvel;
	vec3_t      forward, right, up;
	server_t   *sv = cl->server;

	AngleVectors (cl->state.cmd.angles, forward, right, up);

	speed = DotProduct (cl->state.es.velocity, cl->state.es.velocity);
	if (speed < 1) {
		VectorZero (cl->state.es.velocity);
	} else {
		speed = sqrt (speed);
		drop = 0;

		friction = sv->movevars.friction * 1.5;
		control = speed < sv->movevars.stopspeed ? sv->movevars.stopspeed
												 : speed;
		drop += control * friction * frametime;
		newspeed = speed - drop;
		if (newspeed < 0)
			newspeed = 0;
		newspeed /= speed;

		VectorScale (cl->state.es.velocity, newspeed, cl->state.es.velocity);
	}

	fmove = ucmd->forwardmove;
	smove = ucmd->sidemove;

	VectorNormalize (forward);
	VectorNormalize (right);

	for (i = 0; i < 3; i++)
		wishvel[i] = forward[i] * fmove + right[i] * smove;
	wishvel[2] += ucmd->upmove;

	VectorCopy (wishvel, wishdir);
	wishspeed = VectorNormalize (wishdir);

	if (wishspeed > sv->movevars.spectatormaxspeed) {
		VectorScale (wishvel, sv->movevars.spectatormaxspeed / wishspeed,
					 wishvel);
		wishspeed = sv->movevars.spectatormaxspeed;
	}

	currentspeed = DotProduct (cl->state.es.velocity, wishdir);
	addspeed = wishspeed - currentspeed;
	if (addspeed <= 0)
		return;
	accelspeed = sv->movevars.accelerate * frametime * wishspeed;
	if (accelspeed > addspeed)
		accelspeed = addspeed;

	VectorMultAdd (cl->state.es.velocity, accelspeed, wishdir,
				   cl->state.es.velocity);
	VectorMultAdd (cl->state.es.origin, frametime, cl->state.es.velocity,
				   cl->state.es.origin);
}

static void
run_command (client_t *cl, usercmd_t *ucmd)
{
	if (ucmd->msec > 50) {
		usercmd_t   cmd = *ucmd;
		int         oldmsec = ucmd->msec;
		cmd.msec = oldmsec / 2;
		run_command (cl, &cmd);
		cmd.msec = oldmsec / 2;
		cmd.impulse = 0;
		run_command (cl, &cmd);
		return;
	}

	VectorCopy (ucmd->angles, cl->state.cmd.angles);

	spectator_move (cl, ucmd);
}

static void
client_parse_message (client_t *cl)
{
	int         c, size;
	vec3_t      o;
	const char *s;
	usercmd_t   oldest, oldcmd, newcmd;
	byte        checksum, calculatedChecksum;
	int         checksumIndex, seq_hash;
	qboolean    move_issued = false;

	// make sure the reply sequence number matches the incoming
	// sequence number
	if (cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence)
		cl->netchan.outgoing_sequence = cl->netchan.incoming_sequence;
	else
		cl->send_message = false;		// don't reply, sequences have slipped

	seq_hash = cl->netchan.incoming_sequence;
	cl->delta_sequence = -1;
	while (1) {
		if (net_message->badread) {
			qtv_printf ("SV_ReadClientMessage: badread\n");
			client_drop (cl);
			return;
		}

		c = MSG_ReadByte (net_message);
		if (c == -1)
			return;

		switch (c) {
			default:
				qtv_printf ("SV_ReadClientMessage: unknown command char\n");
				client_drop (cl);
				return;
			case clc_nop:
				break;
			case clc_delta:
				cl->delta_sequence = MSG_ReadByte (net_message);
				break;
			case clc_move:
				checksumIndex = MSG_GetReadCount (net_message);
				checksum = MSG_ReadByte (net_message);
				// read loss percentage
				/*cl->lossage = */MSG_ReadByte (net_message);
				MSG_ReadDeltaUsercmd (net_message, &nullcmd, &oldest);
				MSG_ReadDeltaUsercmd (net_message, &oldest, &oldcmd);
				MSG_ReadDeltaUsercmd (net_message, &oldcmd, &newcmd);
				if (!cl->server)
					break;
				if (move_issued)
					break;				// someone is trying to cheat...
				move_issued = true;
//				if (cl->state != cs_spawned)
//					break;
				// if the checksum fails, ignore the rest of the packet
				calculatedChecksum =
					COM_BlockSequenceCRCByte (net_message->message->data +
											  checksumIndex + 1,
											  MSG_GetReadCount (net_message) -
											  checksumIndex - 1, seq_hash);
				if (calculatedChecksum != checksum) {
					Sys_MaskPrintf (SYS_dev,
									"Failed command checksum for %s(%d) "
									"(%d != %d)\n",
									Info_ValueForKey (cl->userinfo, "name"),
									cl->netchan.incoming_sequence, checksum,
									calculatedChecksum);
					return;
				}
//				if (!sv.paused) {
//					SV_PreRunCmd ();
					if (cl->netchan.net_drop < 20) {
						while (cl->netchan.net_drop > 2) {
							run_command (cl, &cl->lastcmd);
							cl->netchan.net_drop--;
						}
						if (cl->netchan.net_drop > 1)
							run_command (cl, &oldest);
						if (cl->netchan.net_drop > 0)
							run_command (cl, &oldcmd);
					}
					run_command (cl, &newcmd);
//					SV_PostRunCmd ();
//				}
				cl->lastcmd = newcmd;
				cl->lastcmd.buttons = 0;	// avoid multiple fires on lag
				break;
			case clc_stringcmd:
				s = MSG_ReadString (net_message);
				client_exec_command (cl, s);
				break;
			case clc_tmove:
				MSG_ReadCoordV (net_message, o);
				VectorCopy (o, cl->state.es.origin);
				break;
			case clc_upload:
				size = MSG_ReadShort (net_message);
				MSG_ReadByte (net_message);
				net_message->readcount += size;
				break;
		}
	}
}

static void
write_player (int num, plent_state_t *pl, server_t *sv, sizebuf_t *msg)
{
	int         i;
	int         pflags = (pl->es.flags & (PF_GIB | PF_DEAD))
						| PF_MSEC | PF_COMMAND;
	int         qf_bits = 0;

	if (pl->es.modelindex != sv->playermodel)
		pflags |= PF_MODEL;
	for (i = 0; i < 3; i++)
		if (pl->es.velocity[i])
			pflags |= PF_VELOCITY1 << i;
	if (pl->es.effects & 0xff)
		pflags |= PF_EFFECTS;
	if (pl->es.skinnum)
		pflags |= PF_SKINNUM;

	qf_bits = 0;
#if 0
	if (client->stdver > 1) {
		if (sv_fields.alpha != -1 && pl->alpha)
			qf_bits |= PF_ALPHA;
		if (sv_fields.scale != -1 && pl->scale)
			qf_bits |= PF_SCALE;
		if (pl->effects & 0xff00)
			qf_bits |= PF_EFFECTS2;
		if (sv_fields.glow_size != -1 && pl->glow_size)
			qf_bits |= PF_GLOWSIZE;
		if (sv_fields.glow_color != -1 && pl->glow_color)
			qf_bits |= PF_GLOWCOLOR;
		if (sv_fields.colormod != -1
			&& !VectorIsZero (pl->colormod))
			qf_bits |= PF_COLORMOD;
		if (pl->frame & 0xff00)
			qf_bits |= PF_FRAME2;
		if (qf_bits)
			pflags |= PF_QF;
	}
#endif

//	if (cl->spectator) {
//		// send only origin and velocity to spectators
//		pflags &= PF_VELOCITY1 | PF_VELOCITY2 | PF_VELOCITY3;
//	} else if (ent == clent) {
//		// don't send a lot of data on personal entity
//		pflags &= ~(PF_MSEC | PF_COMMAND);
//		if (pl->es.weaponframe)
//			pflags |= PF_WEAPONFRAME;
//	}

//	if (client->spec_track && client->spec_track - 1 == j
//		&& pl->es.weaponframe)
//		pflags |= PF_WEAPONFRAME;

	MSG_WriteByte (msg, svc_playerinfo);
	MSG_WriteByte (msg, num);
	MSG_WriteShort (msg, pflags);

	MSG_WriteCoordV (msg, (vec_t*)&pl->es.origin);//FIXME
	pl->es.origin[3] = 1;

	MSG_WriteByte (msg, pl->es.frame);

	if (pflags & PF_MSEC) {
		//msec = 1000 * (sv.time - cl->localtime);
		//if (msec > 255)
		//	msec = 255;
		MSG_WriteByte (msg, pl->msec);
	}

	if (pflags & PF_COMMAND) {
		MSG_WriteDeltaUsercmd (msg, &nullcmd, &pl->cmd);
	}

	for (i = 0; i < 3; i++)
		if (pflags & (PF_VELOCITY1 << i))
			MSG_WriteShort (msg, pl->es.velocity[i]);

	if (pflags & PF_MODEL)
		MSG_WriteByte (msg, pl->es.modelindex);

	if (pflags & PF_SKINNUM)
		MSG_WriteByte (msg, pl->es.skinnum);

	if (pflags & PF_EFFECTS)
		MSG_WriteByte (msg, pl->es.effects);

	if (pflags & PF_WEAPONFRAME)
		MSG_WriteByte (msg, pl->es.weaponframe);

	if (pflags & PF_QF) {
		MSG_WriteByte (msg, qf_bits);
		if (qf_bits & PF_ALPHA)
			MSG_WriteByte (msg, pl->es.alpha);
		if (qf_bits & PF_SCALE)
			MSG_WriteByte (msg, pl->es.scale);
		if (qf_bits & PF_EFFECTS2)
			MSG_WriteByte (msg, pl->es.effects >> 8);
		if (qf_bits & PF_GLOWSIZE)
			MSG_WriteByte (msg, pl->es.glow_size);
		if (qf_bits & PF_GLOWCOLOR)
			MSG_WriteByte (msg, pl->es.glow_color);
		if (qf_bits & PF_COLORMOD)
			MSG_WriteByte (msg, pl->es.colormod);
		if (qf_bits & PF_FRAME2)
			MSG_WriteByte (msg, pl->es.frame >> 8);
	}
}
#if 0
#define MAX_NAILS   32
edict_t    *nails[MAX_NAILS];
int         numnails;
int         nailcount;

static void
emit_nails (sizebuf_t *msg, qboolean recorder)
{
	byte	   *buf;				// [48 bits] xyzpy 12 12 12 4 8
	int			n, p, x, y, z, yaw;
	int         bpn = recorder ? 7 : 6;	// bytes per nail
	edict_t	   *ent;

	if (!numnails)
		return;

	buf =  SZ_GetSpace (msg, numnails * bpn + 2);
	*buf++ = recorder ? svc_nails2 : svc_nails;
	*buf++ = numnails;

	for (n = 0; n < numnails; n++) {
		ent = nails[n];
		if (recorder) {
			if (!ent->colormap) {
				if (!((++nailcount) & 255))
					nailcount++;
				ent->colormap = nailcount&255;
			}
			*buf++ = (byte)ent->colormap;
		}

		x = ((int) (ent->origin[0] + 4096 + 1) >> 1) & 4095;
		y = ((int) (ent->origin[1] + 4096 + 1) >> 1) & 4095;
		z = ((int) (ent->origin[2] + 4096 + 1) >> 1) & 4095;
		p = (int) (ent->angles[0] * (16.0 / 360.0)) & 15;
		yaw = (int) (ent->angles[1] * (256.0 / 360.0)) & 255;

		*buf++ = x;
		*buf++ = (x >> 8) | (y << 4);
		*buf++ = (y >> 4);
		*buf++ = z;
		*buf++ = (z >> 8) | (p << 4);
		*buf++ = yaw;
	}
}
#endif
static void
write_delta (entity_state_t *from, entity_state_t *to, sizebuf_t *msg,
			 qboolean force)//, int stdver)
{
	int			bits, i;
	float		miss;

	// send an update
	bits = 0;

	for (i = 0; i < 3; i++) {
		miss = to->origin[i] - from->origin[i];
		if (miss < -0.1 || miss > 0.1)
			bits |= U_ORIGIN1 << i;
	}

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

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

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

	if (to->colormap != from->colormap)
		bits |= U_COLORMAP;

	if (to->skinnum != from->skinnum)
		bits |= U_SKIN;

	if (to->frame != from->frame)
		bits |= U_FRAME;

	if (to->effects != from->effects)
		bits |= U_EFFECTS;

	if (to->modelindex != from->modelindex)
		bits |= U_MODEL;

	// LordHavoc: cleaned up Endy's coding style, and added missing effects
// Ender (QSG - Begin)
#if 0
	if (stdver > 1) {
		if (to->alpha != from->alpha)
			bits |= U_ALPHA;

		if (to->scale != from->scale)
			bits |= U_SCALE;

		if (to->effects > 255)
			bits |= U_EFFECTS2;

		if (to->glow_size != from->glow_size)
			bits |= U_GLOWSIZE;

		if (to->glow_color != from->glow_color)
			bits |= U_GLOWCOLOR;

		if (to->colormod != from->colormod)
			bits |= U_COLORMOD;

		if (to->frame > 255)
			bits |= U_EFFECTS2;
	}
	if (bits >= 16777216)
		bits |= U_EXTEND2;

	if (bits >= 65536)
		bits |= U_EXTEND1;
#endif
	if (bits & 511)
		bits |= U_MOREBITS;

	if (to->flags & U_SOLID)
		bits |= U_SOLID;

	if (!bits && !force)
		return;							// nothing to send!
	i = to->number | (bits & ~511);
//	if (i & U_REMOVE)
//		Sys_Error ("U_REMOVE");
	MSG_WriteShort (msg, i);

	if (bits & U_MOREBITS)
		MSG_WriteByte (msg, bits & 255);

	// LordHavoc: cleaned up Endy's tabs
// Ender (QSG - Begin)
	if (bits & U_EXTEND1)
		MSG_WriteByte (msg, bits >> 16);
	if (bits & U_EXTEND2)
		MSG_WriteByte (msg, bits >> 24);
// Ender (QSG - End)

	if (bits & U_MODEL)
		MSG_WriteByte (msg, to->modelindex);
	if (bits & U_FRAME)
		MSG_WriteByte (msg, to->frame);
	if (bits & U_COLORMAP)
		MSG_WriteByte (msg, to->colormap);
	if (bits & U_SKIN)
		MSG_WriteByte (msg, to->skinnum);
	if (bits & U_EFFECTS)
		MSG_WriteByte (msg, to->effects);
	if (bits & U_ORIGIN1)
		MSG_WriteCoord (msg, to->origin[0]);
	if (bits & U_ANGLE1)
		MSG_WriteAngle (msg, to->angles[0]);
	if (bits & U_ORIGIN2)
		MSG_WriteCoord (msg, to->origin[1]);
	if (bits & U_ANGLE2)
		MSG_WriteAngle (msg, to->angles[1]);
	if (bits & U_ORIGIN3)
		MSG_WriteCoord (msg, to->origin[2]);
	if (bits & U_ANGLE3)
		MSG_WriteAngle (msg, to->angles[2]);

	// LordHavoc: cleaned up Endy's tabs, rearranged bytes, and implemented
	// missing effects
// Ender (QSG - Begin)
	if (bits & U_ALPHA)
		MSG_WriteByte (msg, to->alpha);
	if (bits & U_SCALE)
		MSG_WriteByte (msg, to->scale);
	if (bits & U_EFFECTS2)
		MSG_WriteByte (msg, (to->effects >> 8));
	if (bits & U_GLOWSIZE)
		MSG_WriteByte (msg, to->glow_size);
	if (bits & U_GLOWCOLOR)
		MSG_WriteByte (msg, to->glow_color);
	if (bits & U_COLORMOD)
		MSG_WriteByte (msg, to->colormod);
	if (bits & U_FRAME2)
		MSG_WriteByte (msg, (to->frame >> 8));
// Ender (QSG - End)
}

static void
emit_entities (client_t *client, packet_entities_t *to, sizebuf_t *msg)
{
	int         newindex, oldindex, newnum, oldnum, oldmax;
	entity_state_t *ent;
	frame_t    *fromframe;
	packet_entities_t *from;
	server_t   *sv = client->server;

	// this is the frame that we are going to delta update from
	if (client->delta_sequence != -1) {
		fromframe = &client->frames[client->delta_sequence & UPDATE_MASK];
		from = &fromframe->entities;
		oldmax = from->num_entities;

		MSG_WriteByte (msg, svc_deltapacketentities);
		MSG_WriteByte (msg, client->delta_sequence);
	} else {
		oldmax = 0;						// no delta update
		from = NULL;

		MSG_WriteByte (msg, svc_packetentities);
	}

	newindex = 0;
	oldindex = 0;
	while (newindex < to->num_entities || oldindex < oldmax) {
		newnum = newindex >= to->num_entities ? 9999 :
			to->entities[newindex].number;
		oldnum = oldindex >= oldmax ? 9999 : from->entities[oldindex].number;

		if (newnum == oldnum) {			// delta update from old position
			write_delta (&from->entities[oldindex], &to->entities[newindex],
						 msg, false);//, client->stdver);
			oldindex++;
			newindex++;
			continue;
		}

		if (newnum < oldnum) {
			// this is a new entity, send it from the baseline
			ent = sv->baselines + newnum;
			write_delta (ent, &to->entities[newindex], msg, true);//,
						 //client->stdver);
			newindex++;
			continue;
		}

		if (newnum > oldnum) {			// the old entity isn't present in
										// the new message
			MSG_WriteShort (msg, oldnum | U_REMOVE);
			oldindex++;
			continue;
		}
	}

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

static void
add_to_fat_pvs (vec4f_t org, int node_id, server_t *sv)
{
	while (1) {
		// if this is a leaf, accumulate the pvs bits
		if (node_id < 0) {
			mleaf_t    *leaf = sv->worldmodel->brush.leafs + ~node_id;
			if (leaf->contents != CONTENTS_SOLID) {
				set_union (sv->fatpvs, Mod_LeafPVS (leaf, sv->worldmodel));
			}
			return;
		}

		mnode_t    *node = sv->worldmodel->brush.nodes + node_id;
		float       d = dotf (node->plane, org)[0];
		if (d > 8)
			node_id = node->children[0];
		else if (d < -8)
			node_id = node->children[1];
		else {							// go down both
			add_to_fat_pvs (org, node->children[0], sv);
			node_id = node->children[1];
		}
	}
}

static set_t *
fat_pvs (vec4f_t org, server_t *sv)
{
	if (!sv->fatpvs) {
		sv->fatpvs = set_new_size (sv->worldmodel->brush.visleafs);
	}
	set_expand (sv->fatpvs, sv->worldmodel->brush.visleafs);
	set_empty (sv->fatpvs);

	add_to_fat_pvs (org, 0, sv);
	return sv->fatpvs;
}

static void
write_entities (client_t *client, sizebuf_t *msg)
{
	set_t      *pvs = 0;
	int         e;
	vec4f_t     org;
	frame_t    *frame;
	qtv_entity_t *ent;
	qtv_leaf_t *el;
	entity_state_t *state;
	packet_entities_t *pack;
	server_t   *sv = client->server;

	// this is the frame we are creating
	frame = &client->frames[client->netchan.incoming_sequence & UPDATE_MASK];

	// find the client's PVS
	org = client->state.es.origin;
	org[2] += 22;	//XXX standard spectator view offset
	pvs = fat_pvs (org, sv);

	// put other visible entities into either a packet_entities or a nails
	// message
	pack = &frame->entities;
	pack->num_entities = 0;

	//numnails = 0;

	for (e = MAX_CLIENTS + 1, ent = sv->entities + e; e < MAX_SV_ENTITIES;
		 e++, ent++) {
		if (!sv->ent_valid[e])
			continue;
		if (ent->e.number && ent->e.number != e)
			qtv_printf ("%d %d\n", e, ent->e.number);
		if (pvs) {
			// ignore if not touching a PV leaf
			for (el = ent->leafs; el; el = el->next) {
				if (set_is_member (pvs, el->num))
					break;
			}

			if (!el)
				continue;					// not visible
		}
//		if (SV_AddNailUpdate (ent))
//			continue;					// added to the special update list

		// add to the packetentities
		if (pack->num_entities == MAX_PACKET_ENTITIES) {
			qtv_printf ("mpe overflow\n");
			continue;					// all full
		}

		state = &pack->entities[pack->num_entities];
		pack->num_entities++;
		*state = ent->e;
		state->flags = 0;
	}
	// encode the packet entities as a delta from the
	// last packetentities acknowledged by the client
	emit_entities (client, pack, msg);

	// now add the specialized nail update
	//emit_nails (msg, 0);
}

static void
write_players (client_t *client, sizebuf_t *msg)
{
	server_t   *sv = client->server;
	int         i;

	for (i = 0; i < MAX_SV_PLAYERS; i++) {
		if (!sv->players[i].info)
			continue;
		write_player (i, &sv->players[i].ent, sv, msg);
	}
}

void
Client_SendMessages (client_t *cl)
{
	byte        buf[MAX_DATAGRAM];
	sizebuf_t   msg;

	memset (&msg, 0, sizeof (msg));
	msg.allowoverflow = true;
	msg.maxsize = sizeof (buf);
	msg.data = buf;

	if (cl->backbuf.num_backbuf)
		MSG_Reliable_Send (&cl->backbuf);
	if (cl->connected) {
		write_players (cl, &msg);
		write_player (31, &cl->state, cl->server, &msg);
		write_entities (cl, &msg);
		if (cl->datagram.cursize) {
			SZ_Write (&msg, cl->datagram.data, cl->datagram.cursize);
			SZ_Clear (&cl->datagram);
		}
	}
	Netchan_Transmit (&cl->netchan, msg.cursize, msg.data);
}

static void
client_handler (connection_t *con, void *object)
{
	client_t   *cl = (client_t *) object;

	if (net_message->message->cursize < 11) {
		qtv_printf ("%s: Runt packet\n", NET_AdrToString (net_from));
		return;
	}
	if (Netchan_Process (&cl->netchan)) {
		// this is a valid, sequenced packet, so process it
		//svs.stats.packets++;
		cl->send_message = true;
		//if (cl->state != cs_zombie)
			client_parse_message (cl);
	}
}

static void
client_connect (connection_t *con, void *object)
{
	challenge_t *ch = (challenge_t *) object;
	client_t   *cl;
	const char *str;
	info_t     *userinfo;
	int         version, qport, challenge, seq;
	int         i;

	MSG_BeginReading (net_message);
	seq = MSG_ReadLong (net_message);
	if (seq != -1) {
		qtv_printf ("unexpected connected packet\n");
		return;
	}
	str = MSG_ReadString (net_message);
	COM_TokenizeString (str, qtv_args);
	cmd_args = qtv_args;
	if (strcmp (Cmd_Argv (0), "connect")) {
		qtv_printf ("unexpected connected packet\n");
		return;
	}
	version = atoi (Cmd_Argv (1));
	if (version != PROTOCOL_VERSION) {
		Netchan_OutOfBandPrint (net_from, "%c\nServer is version %s.\n",
								A2C_PRINT, QW_VERSION);
		qtv_printf ("* rejected connect from version %i\n", version);
		return;
	}
	qport = atoi (Cmd_Argv (2));
	challenge = atoi (Cmd_Argv (3));
	if (!(con = Connection_Find (&net_from))
		|| (ch = con->object)->challenge != challenge) {
		Netchan_OutOfBandPrint (net_from, "%c\nBad challenge.\n",
								A2C_PRINT);
		return;
	}
	free (con->object);
	userinfo = Info_ParseString (Cmd_Argv (4), 0, 0);
	if (!userinfo) {
		Netchan_OutOfBandPrint (net_from, "%c\nInvalid userinfo string.\n",
								A2C_PRINT);
		return;
	}

	cl = calloc (1, sizeof (client_t));
	client_count++;
	Netchan_Setup (&cl->netchan, con->address, qport, NC_QPORT_READ);
	cl->clnext = clients;
	clients = cl;
	cl->userinfo = userinfo;
	cl->name = Info_ValueForKey (userinfo, "name");
	cl->backbuf.name = cl->name;
	cl->backbuf.netchan = &cl->netchan;
	cl->con = con;
	con->object = cl;
	con->handler = client_handler;

	cl->datagram.allowoverflow = true;
	cl->datagram.maxsize = sizeof (cl->datagram_buf);
	cl->datagram.data = cl->datagram_buf;

	for (i = 0; i < UPDATE_BACKUP; i++)
		cl->frames[i].entities.entities = cl->packet_entities[i];

	qtv_printf ("client %s (%s) connected\n",
				Info_ValueForKey (userinfo, "name"),
				NET_AdrToString (con->address));

	Netchan_OutOfBandPrint (net_from, "%c", S2C_CONNECTION);
}

void
Client_NewConnection (void)
{
	challenge_t *ch;
	connection_t *con;

	if ((con = Connection_Find (&net_from))) {
		if (con->handler == client_handler)
			return;
		ch = con->object;
	} else {
		ch = malloc (sizeof (challenge_t));
	}
	ch->challenge = (rand () << 16) ^ rand ();
	ch->time = realtime;
	if (!con)
		con = Connection_Add (&net_from, ch, 0);
	Netchan_OutOfBandPrint (net_from, "%c%i QF", S2C_CHALLENGE, ch->challenge);
	con->handler = client_connect;
}

static const char *
ucmds_getkey (const void *_a, void *unused)
{
	ucmd_t *a = (ucmd_t*)_a;
	return a->name;
}

void
Client_Init (void)
{
	size_t      i;

	ucmd_table = Hash_NewTable (251, ucmds_getkey, 0, 0, 0);
	for (i = 0; i < sizeof (ucmds) / sizeof (ucmds[0]); i++)
		Hash_Add (ucmd_table, &ucmds[i]);
}

void
Client_New (client_t *cl)
{
	server_t   *sv = cl->server;

	MSG_WriteByte (&cl->netchan.message, svc_serverdata);
	MSG_WriteLong (&cl->netchan.message, PROTOCOL_VERSION);
	MSG_WriteLong (&cl->netchan.message, sv->spawncount);
	MSG_WriteString (&cl->netchan.message, sv->gamedir);
	MSG_WriteByte (&cl->netchan.message, 0x80 | 31);
	MSG_WriteString (&cl->netchan.message, sv->message);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.gravity);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.stopspeed);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.maxspeed);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.spectatormaxspeed);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.accelerate);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.airaccelerate);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.wateraccelerate);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.friction);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.waterfriction);
	MSG_WriteFloat (&cl->netchan.message, sv->movevars.entgravity);
	MSG_WriteByte (&cl->netchan.message, svc_cdtrack);
	MSG_WriteByte (&cl->netchan.message, sv->cdtrack);
	MSG_WriteByte (&cl->netchan.message, svc_stufftext);
	MSG_WriteString (&cl->netchan.message,
					 va (0, "fullserverinfo \"%s\"\n",
						 Info_MakeString (sv->info, 0)));
}

static void
delete_client (client_t *cl)
{
	if (cl->server)
		Server_Disconnect (cl);
	Connection_Del (cl->con);
	Info_Destroy (cl->userinfo);
	client_count--;
	free (cl);
}

void
Client_Frame (void)
{
	client_t  **c;

	for (c = &clients; *c; ) {
		client_t   *cl = *c;

		if (realtime - cl->netchan.last_received > 60) {
			qtv_printf ("client %s timed out\n", (*c)->name);
			client_drop (cl);
		}
		if (cl->netchan.message.overflowed) {
			qtv_printf ("client %s overflowed\n", cl->name);
			client_drop (cl);
		}
		if (cl->drop) {
			qtv_printf ("client %s dropped\n", cl->name);
			*c = cl->clnext;
			delete_client (cl);
			continue;
		}
		if (cl->send_message) {
			Client_SendMessages (cl);
			cl->send_message = false;
		}
		c = &(*c)->clnext;
	}
}
