/*
   Copyright (C) 1997-2001 Id Software, Inc.

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

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

   See the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */

#include "server.h"

//=============================================================================
//
//Encode a client frame onto the network channel
//
//=============================================================================

//=============
//SV_EmitPacketEntities
//
//Writes a delta update of an entity_state_t list to the message.
//=============
static void SV_EmitPacketEntities( client_frame_t *from, client_frame_t *to, msg_t *msg )
{
	entity_state_t *oldent, *newent;
	int oldindex, newindex;
	int oldnum, newnum;
	int from_num_entities;
	int bits;

	MSG_WriteByte( msg, svc_packetentities );

	if( !from )
		from_num_entities = 0;
	else
		from_num_entities = from->num_entities;

	newindex = 0;
	oldindex = 0;
	while( newindex < to->num_entities || oldindex < from_num_entities )
	{
		if( newindex >= to->num_entities )
		{
			newent = NULL;
			newnum = 9999;
		}
		else
		{
			newent = &svs.client_entities[( to->first_entity+newindex )%svs.num_client_entities];
			newnum = newent->number;
		}

		if( oldindex >= from_num_entities )
		{
			oldent = NULL;
			oldnum = 9999;
		}
		else
		{
			oldent = &svs.client_entities[( from->first_entity+oldindex )%svs.num_client_entities];
			oldnum = oldent->number;
		}

		if( newnum == oldnum )
		{ // delta update from old position
			// because the force parm is false, this will not result
			// in any bytes being emited if the entity has not changed at all
			// note that players are always 'newentities', this updates their oldorigin always
			// and prevents warping ( wsw : jal : I removed it from the players )
			MSG_WriteDeltaEntity( oldent, newent, msg, qfalse, ( ( EDICT_NUM( newent->number ) )->r.svflags & SVF_TRANSMITORIGIN2 ) );
			oldindex++;
			newindex++;
			continue;
		}

		if( newnum < oldnum )
		{ // this is a new entity, send it from the baseline
			MSG_WriteDeltaEntity( &sv.baselines[newnum], newent, msg, qtrue, !( ( EDICT_NUM( newent->number ) )->r.svflags & SVF_NOORIGIN2 ) );
			newindex++;
			continue;
		}

		if( newnum > oldnum )
		{ // the old entity isn't present in the new message
			bits = U_REMOVE;
			if( oldnum >= 256 )
				bits |= ( U_NUMBER16 | U_MOREBITS1 );

			MSG_WriteByte( msg, bits&255 );
			if( bits & 0x0000ff00 )
				MSG_WriteByte( msg, ( bits>>8 )&255 );

			if( bits & U_NUMBER16 )
				MSG_WriteShort( msg, oldnum );
			else
				MSG_WriteByte( msg, oldnum );

			oldindex++;
			continue;
		}
	}

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

//=============
//SV_WriteDeltaMatchStateToClient
//=============
static void SV_WriteDeltaMatchStateToClient( client_frame_t *from, client_frame_t *to, msg_t *msg )
{
	match_state_t *ms, *oms;
	match_state_t dummy;
	int bitmask;

	ms = &to->matchstate;
	if( !from )
	{
		memset( &dummy, 0, sizeof( dummy ) );
		oms = &dummy;
	}
	else
		oms = &from->matchstate;

	bitmask = 0;

	if( oms->state != ms->state || oms->paused != ms->paused )
		bitmask |= MATCHSTATE_FLAG_STATE;

	if( oms->roundstate != ms->roundstate )
		bitmask |= MATCHSTATE_FLAG_ROUNDSTATE;

	if( oms->timelimit != ms->timelimit || oms->extendedtime != ms->extendedtime )
		bitmask |= MATCHSTATE_FLAG_TIMELIMIT;

	if( oms->clock_msecs != ms->clock_msecs )
		bitmask |= MATCHSTATE_FLAG_CLOCK_MSECS;

	if( oms->clock_secs != ms->clock_secs )
		bitmask |= MATCHSTATE_FLAG_CLOCK_SECS;

	if( oms->clock_mins != ms->clock_mins )
		bitmask |= MATCHSTATE_FLAG_CLOCK_MINUTES;

	// write it
	MSG_WriteByte( msg, svc_match );
	MSG_WriteByte( msg, bitmask );

	if( bitmask & MATCHSTATE_FLAG_STATE )
	{
		qbyte mstate = ms->state & 127;
		if( ms->paused )
			mstate |= ET_INVERSE;
		MSG_WriteByte( msg, mstate );
	}

	if( bitmask & MATCHSTATE_FLAG_ROUNDSTATE )
		MSG_WriteByte( msg, (qbyte)ms->roundstate );

	if( bitmask & MATCHSTATE_FLAG_TIMELIMIT )
	{
		int timelimitmask = ms->timelimit;
		if( ms->extendedtime )
			timelimitmask |= MATCHSTATE_EXTENDEDTIME_BIT;
		MSG_WriteLong( msg, timelimitmask );
	}

	if( bitmask & MATCHSTATE_FLAG_CLOCK_MSECS )
		MSG_WriteByte( msg, (qbyte)( ms->clock_msecs * 0.1 ) );

	if( bitmask & MATCHSTATE_FLAG_CLOCK_SECS )
		MSG_WriteByte( msg, ms->clock_secs );

	if( bitmask & MATCHSTATE_FLAG_CLOCK_MINUTES )
		MSG_WriteShort( msg, ms->clock_mins );
}

//=============
//SV_WritePlayerstateToClient
//=============
static void SV_WritePlayerstateToClient( player_state_t *ops, player_state_t *ps, msg_t *msg )
{
	int i;
	int pflags;
	player_state_t dummy;
	int statbits;

	if( !ops )
	{
		memset( &dummy, 0, sizeof( dummy ) );
		ops = &dummy;
	}

	//
	// determine what needs to be sent
	//
	pflags = 0;

	if( ps->pmove.pm_type != ops->pmove.pm_type )
		pflags |= PS_M_TYPE;

	if( ps->pmove.origin[0] != ops->pmove.origin[0] )
		pflags |= PS_M_ORIGIN0;
	if( ps->pmove.origin[1] != ops->pmove.origin[1] )
		pflags |= PS_M_ORIGIN1;
	if( ps->pmove.origin[2] != ops->pmove.origin[2] )
		pflags |= PS_M_ORIGIN2;

	if( ps->pmove.velocity[0] != ops->pmove.velocity[0] )
		pflags |= PS_M_VELOCITY0;
	if( ps->pmove.velocity[1] != ops->pmove.velocity[1] )
		pflags |= PS_M_VELOCITY1;
	if( ps->pmove.velocity[2] != ops->pmove.velocity[2] )
		pflags |= PS_M_VELOCITY2;

	if( ps->pmove.pm_time != ops->pmove.pm_time )
		pflags |= PS_M_TIME;

	if( ps->pmove.pm_flags != ops->pmove.pm_flags )
		pflags |= PS_M_FLAGS;

	if( ps->pmove.delta_angles[0] != ops->pmove.delta_angles[0] )
		pflags |= PS_M_DELTA_ANGLES0;
	if( ps->pmove.delta_angles[1] != ops->pmove.delta_angles[1] )
		pflags |= PS_M_DELTA_ANGLES1;
	if( ps->pmove.delta_angles[2] != ops->pmove.delta_angles[2] )
		pflags |= PS_M_DELTA_ANGLES2;

	if( ps->event != ops->event )
		pflags |= PS_EVENT;

	if( ps->viewangles[0] != ops->viewangles[0]
	    || ps->viewangles[1] != ops->viewangles[1]
	    || ps->viewangles[2] != ops->viewangles[2] )
		pflags |= PS_VIEWANGLES;

	if( ps->pmove.gravity != ops->pmove.gravity )
		pflags |= PS_M_GRAVITY;

	if( ps->fov != ops->fov )
		pflags |= PS_FOV;

	if( ps->POVnum != ops->POVnum )
		pflags |= PS_POVNUM;

	if( ps->viewheight != ops->viewheight )
		pflags |= PS_VIEWHEIGHT;

	for( i = 0; i < PM_STAT_SIZE; i++ )
		if( ps->pmove.stats[i] != ops->pmove.stats[i] )
			pflags |= PS_PMOVESTATS;

	for( i = 0; i < MAX_WEAPLIST_STATS; i++ )
	{
		if( ps->weaponlist[i][0] != ops->weaponlist[i][0] ||
		    ps->weaponlist[i][1] != ops->weaponlist[i][1] ||
		    ps->weaponlist[i][2] != ops->weaponlist[i][2] )
		{
			pflags |= PS_WEAPONLIST;
			break;
		}
	}
	if( ps->plrkeys != ops->plrkeys )
		pflags |= PS_PLRKEYS;

	//
	// write it
	//
	MSG_WriteByte( msg, svc_playerinfo );

	if( pflags & 0xff000000 )
		pflags |= PS_MOREBITS3 | PS_MOREBITS2 | PS_MOREBITS1;
	else if( pflags & 0x00ff0000 )
		pflags |= PS_MOREBITS2 | PS_MOREBITS1;
	else if( pflags & 0x0000ff00 )
		pflags |= PS_MOREBITS1;

	MSG_WriteByte( msg, pflags&255 );

	if( pflags & 0xff000000 )
	{
		MSG_WriteByte( msg, ( pflags>>8 )&255 );
		MSG_WriteByte( msg, ( pflags>>16 )&255 );
		MSG_WriteByte( msg, ( pflags>>24 )&255 );
	}
	else if( pflags & 0x00ff0000 )
	{
		MSG_WriteByte( msg, ( pflags>>8 )&255 );
		MSG_WriteByte( msg, ( pflags>>16 )&255 );
	}
	else if( pflags & 0x0000ff00 )
	{
		MSG_WriteByte( msg, ( pflags>>8 )&255 );
	}

	//
	// write the pmove_state_t
	//
	if( pflags & PS_M_TYPE )
		MSG_WriteByte( msg, ps->pmove.pm_type );

	if( pflags & PS_M_ORIGIN0 )
		MSG_WriteInt3( msg, (int)( ps->pmove.origin[0]*PM_VECTOR_SNAP ) );
	if( pflags & PS_M_ORIGIN1 )
		MSG_WriteInt3( msg, (int)( ps->pmove.origin[1]*PM_VECTOR_SNAP ) );
	if( pflags & PS_M_ORIGIN2 )
		MSG_WriteInt3( msg, (int)( ps->pmove.origin[2]*PM_VECTOR_SNAP ) );

	if( pflags & PS_M_VELOCITY0 )
		MSG_WriteInt3( msg, (int)( ps->pmove.velocity[0]*PM_VECTOR_SNAP ) );
	if( pflags & PS_M_VELOCITY1 )
		MSG_WriteInt3( msg, (int)( ps->pmove.velocity[1]*PM_VECTOR_SNAP ) );
	if( pflags & PS_M_VELOCITY2 )
		MSG_WriteInt3( msg, (int)( ps->pmove.velocity[2]*PM_VECTOR_SNAP ) );

	if( pflags & PS_M_TIME )
		MSG_WriteByte( msg, ps->pmove.pm_time );

	if( pflags & PS_M_FLAGS )
		MSG_WriteShort( msg, ps->pmove.pm_flags );

	if( pflags & PS_M_DELTA_ANGLES0 )
		MSG_WriteShort( msg, ps->pmove.delta_angles[0] );
	if( pflags & PS_M_DELTA_ANGLES1 )
		MSG_WriteShort( msg, ps->pmove.delta_angles[1] );
	if( pflags & PS_M_DELTA_ANGLES2 )
		MSG_WriteShort( msg, ps->pmove.delta_angles[2] );

	if( pflags & PS_EVENT )
		MSG_WriteShort( msg, ps->event );

	if( pflags & PS_VIEWANGLES )
	{
		MSG_WriteAngle16( msg, ps->viewangles[0] );
		MSG_WriteAngle16( msg, ps->viewangles[1] );
		MSG_WriteAngle16( msg, ps->viewangles[2] );
	}

	if( pflags & PS_M_GRAVITY )
		MSG_WriteShort( msg, ps->pmove.gravity );

	if( pflags & PS_FOV )
		MSG_WriteByte( msg, (qbyte)ps->fov );

	if( pflags & PS_POVNUM )
		MSG_WriteByte( msg, (qbyte)ps->POVnum );

	if( pflags & PS_VIEWHEIGHT )
		MSG_WriteChar( msg, (char)ps->viewheight );

	if( pflags & PS_PMOVESTATS )
	{
		for( i = 0; i < PM_STAT_SIZE; i++ )
		{
			MSG_WriteShort( msg, ps->pmove.stats[i] );
		}
	}

	if( pflags & PS_WEAPONLIST )
	{
		// send weaponlist stats
		statbits = 0;
		for( i = 0; i < MAX_WEAPLIST_STATS; i++ )
		{
			if( ps->weaponlist[i][0] != ops->weaponlist[i][0] ||
			    ps->weaponlist[i][1] != ops->weaponlist[i][1] ||
			    ps->weaponlist[i][2] != ops->weaponlist[i][2] )
				statbits |= ( 1<<i );
		}

		MSG_WriteShort( msg, statbits );
		for( i = 0; i < MAX_WEAPLIST_STATS; i++ )
		{
			if( statbits & ( 1<<i ) )
			{
				MSG_WriteByte( msg, (qbyte)ps->weaponlist[i][0] );
				MSG_WriteByte( msg, (qbyte)ps->weaponlist[i][1] );
				MSG_WriteByte( msg, (qbyte)ps->weaponlist[i][2] );
			}
		}
	}

	if( pflags & PS_PLRKEYS )
		MSG_WriteByte( msg, ps->plrkeys );

	// send stats
	statbits = 0;
	for( i = 0; i < PS_MAX_STATS; i++ )
	{
		if( ps->stats[i] != ops->stats[i] )
			statbits |= 1<<i;
	}

	MSG_WriteLong( msg, statbits );
	for( i = 0; i < PS_MAX_STATS; i++ )
	{
		if( statbits & ( 1<<i ) )
			MSG_WriteShort( msg, ps->stats[i] );
	}
}

//=============
//SV_WriteSoundToClient
//=============
static void SV_WriteSoundToClient( const sound_t *sound, msg_t *msg )
{
	int v, flags, channel, sendchan;

	channel = sound->channel;
	if( channel & CHAN_NO_PHS_ADD )
		channel &= 7;
	sendchan = ( sound->entnum << 3 ) | ( channel & 7 );

	flags = 0;
	if( sound->volume != DEFAULT_SOUND_PACKET_VOLUME )
		flags |= SND_VOLUME;
	if( sound->attenuation != DEFAULT_SOUND_PACKET_ATTENUATION )
		flags |= SND_ATTENUATION;

	v = Q_rint( sound->pos[0] ); flags |= ( SND_POS0_8|SND_POS0_16 );
	if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS0_16;else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS0_8;

	v = Q_rint( sound->pos[1] ); flags |= ( SND_POS1_8|SND_POS1_16 );
	if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS1_16;else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS1_8;

	v = Q_rint( sound->pos[2] ); flags |= ( SND_POS2_8|SND_POS2_16 );
	if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS2_16;else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS2_8;

	MSG_WriteByte( msg, svc_sound );
	MSG_WriteByte( msg, flags );
	MSG_WriteByte( msg, sound->num );

	// always send the entity number for channel overrides
	MSG_WriteShort( msg, sendchan );

	if( ( flags & ( SND_POS0_8|SND_POS0_16 ) ) == SND_POS0_8 )
		MSG_WriteChar( msg, Q_rint( sound->pos[0] ) );
	else if( ( flags & ( SND_POS0_8|SND_POS0_16 ) ) == SND_POS0_16 )
		MSG_WriteShort( msg, Q_rint( sound->pos[0] ) );
	else
		MSG_WriteInt3( msg, Q_rint( sound->pos[0] ) );

	if( ( flags & ( SND_POS1_8|SND_POS1_16 ) ) == SND_POS1_8 )
		MSG_WriteChar( msg, Q_rint( sound->pos[1] ) );
	else if( ( flags & ( SND_POS1_8|SND_POS1_16 ) ) == SND_POS1_16 )
		MSG_WriteShort( msg, Q_rint( sound->pos[1] ) );
	else
		MSG_WriteInt3( msg, Q_rint( sound->pos[1] ) );

	if( ( flags & ( SND_POS2_8|SND_POS2_16 ) ) == SND_POS2_8 )
		MSG_WriteChar( msg, Q_rint( sound->pos[2] ) );
	else if( ( flags & ( SND_POS2_8|SND_POS2_16 ) ) == SND_POS2_16 )
		MSG_WriteShort( msg, Q_rint( sound->pos[2] ) );
	else
		MSG_WriteInt3( msg, Q_rint( sound->pos[2] ) );

	if( flags & SND_VOLUME )
		MSG_WriteByte( msg, sound->volume * 255 );
	if( flags & SND_ATTENUATION )
		MSG_WriteByte( msg, sound->attenuation );
}

//==================
//SV_WriteFrameSnapToClient
//==================
void SV_WriteFrameSnapToClient( client_t *client, msg_t *msg )
{
	client_frame_t *frame, *oldframe;
	int flags, i, index, pos, length;

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

	// for non-reliable clients we need to send nodelta frame until the client responds
	if( client->nodelta && !client->reliable )
	{
		if( !client->nodelta_frame )
			client->nodelta_frame = sv.framenum;
		else if( client->lastframe >= client->nodelta_frame )
			client->nodelta = qfalse;
	}

	if( client->lastframe <= 0 || (unsigned)client->lastframe > sv.framenum || client->nodelta )
	{ // client is asking for a not compressed retransmit
		oldframe = NULL;
	}
	//else if( sv.framenum >= client->lastframe + (UPDATE_BACKUP - 3) )
	else if( sv.framenum >= (unsigned)client->lastframe + UPDATE_MASK )
	{ // client hasn't gotten a good message through in a long time
		oldframe = NULL;
	}
	else // we have a valid message to delta from
	{
		oldframe = &client->frames[client->lastframe & UPDATE_MASK];
		if( oldframe->multipov != frame->multipov )
			oldframe = NULL;	// don't delta compress a frame of different POV type
	}

	if( client->nodelta && client->reliable )
		client->nodelta = qfalse;

	MSG_WriteByte( msg, svc_frame );

	pos = msg->cursize;
	MSG_WriteShort( msg, 0 );       // we will write length here

	MSG_WriteLong( msg, svs.gametime ); // serverTimeStamp
	MSG_WriteLong( msg, sv.framenum );
	MSG_WriteLong( msg, client->lastframe );
	MSG_WriteLong( msg, frame->UcmdExecuted );

	flags = 0;
	if( oldframe != NULL )
		flags |= FRAMESNAP_FLAG_DELTA;
	if( frame->allentities )
		flags |= FRAMESNAP_FLAG_ALLENTITIES;
	if( frame->multipov )
		flags |= FRAMESNAP_FLAG_MULTIPOV;
	MSG_WriteByte( msg, flags );

#ifdef RATEKILLED
	MSG_WriteByte( msg, 0 );
#else
	MSG_WriteByte( msg, client->suppressCount ); // rate dropped packets
	client->suppressCount = 0;
#endif

	// add game comands
	MSG_WriteByte( msg, svc_gamecommands );
	if( frame->multipov )
	{
		client_t *cl;
		int positions[MAX_CLIENTS];
		char *command;
		int maxnumtargets, numtargets;
		unsigned int framenum;
		int targets[MAX_CLIENTS];

		// find the first command to send from every client
		maxnumtargets = 0;
		for( i = 0; i < sv_maxclients->integer; i++ )
		{
			cl = &svs.clients[i];

			if( cl->state < CS_SPAWNED || ( ( !cl->edict || ( cl->edict->r.svflags & SVF_NOCLIENT ) ) && cl != client ) )
				continue;

			maxnumtargets++;
			for( positions[i] = cl->gameCommandCurrent - MAX_RELIABLE_COMMANDS + 1;
			     positions[i] <= cl->gameCommandCurrent; positions[i]++ )
			{
				index = positions[i] & ( MAX_RELIABLE_COMMANDS - 1 );

				// we need to check for too new commands too, because gamecommands for the next snap are generated
				// all the time, and we might want to create a server demo frame or something in between snaps
				if( cl->gameCommands[index].command[0] && cl->gameCommands[index].framenum + 256 >= sv.framenum &&
				   cl->gameCommands[index].framenum <= sv.framenum &&
				   ( client->lastframe >= 0 && cl->gameCommands[index].framenum > (unsigned int)client->lastframe ) )
					break;
			}
		}

		// send all messages, combining similar messages together to save space
		do
		{
			command = NULL;
			numtargets = 0;
			framenum = 0;

			// we find the message with the earliest framenum, and collect all recipients for that
			for( i = 0; i < sv_maxclients->integer; i++ )
			{
				cl = &svs.clients[i];

				if( cl->state < CS_SPAWNED || ( ( !cl->edict || ( cl->edict->r.svflags & SVF_NOCLIENT ) ) && cl != client ) )
					continue;

				if( positions[i] > cl->gameCommandCurrent )
					continue;

				index = positions[i] & ( MAX_RELIABLE_COMMANDS - 1 );

				if( command && !strcmp( cl->gameCommands[index].command, command ) &&
				    framenum == cl->gameCommands[index].framenum )
				{
					targets[numtargets++] = i;
				}
				else if( !command || cl->gameCommands[index].framenum < framenum )
				{
					command = cl->gameCommands[index].command;
					framenum = cl->gameCommands[index].framenum;
					targets[0] = i;
					numtargets = 1;
				}
				if( numtargets == maxnumtargets )
					break;
			}

			// send it
			if( command )
			{
				if( sv.framenum >= framenum )
				{               // jal : never write a command if it's of a higher framenum
					// do not allow the message buffer to overflow (can happen on flood updates)
					if( msg->cursize + strlen( command ) + 512 > msg->maxsize )
						continue;

					MSG_WriteShort( msg, sv.framenum - framenum );
					MSG_WriteString( msg, command );
					// 0 means everyone
					if( numtargets == maxnumtargets )
					{
						MSG_WriteByte( msg, 0 );
					}
					else
					{
						MSG_WriteByte( msg, numtargets );
						for( i = 0; i < numtargets; i++ )
							MSG_WriteByte( msg, targets[i] );
					}
				}
				for( i = 0; i < numtargets; i++ )
					positions[targets[i]]++;
			}
		}
		while( command );
	}
	else
	{
		for( i = client->gameCommandCurrent - MAX_RELIABLE_COMMANDS + 1; i <= client->gameCommandCurrent; i++ )
		{
			index = i & ( MAX_RELIABLE_COMMANDS - 1 );

			// check that it is valid command and that has not already been sent
			// we can only allow commands from certain amount of old frames, so the short won't overflow
			if( !client->gameCommands[index].command[0] || client->gameCommands[index].framenum + 256 < sv.framenum ||
			   client->gameCommands[index].framenum > sv.framenum ||
			   ( client->lastframe >= 0 && client->gameCommands[index].framenum <= (unsigned)client->lastframe ) )
				continue;

			// do not allow the message buffer to overflow (can happen on flood updates)
			if( msg->cursize + strlen( client->gameCommands[index].command ) + 512 > msg->maxsize )
				continue;

			// send it
			MSG_WriteShort( msg, sv.framenum - client->gameCommands[index].framenum );
			MSG_WriteString( msg, client->gameCommands[index].command );
		}
	}
	MSG_WriteShort( msg, -1 );

	// send over the areabits
	MSG_WriteByte( msg, frame->areabytes );
	MSG_WriteData( msg, frame->areabits, frame->areabytes );

	SV_WriteDeltaMatchStateToClient( oldframe, frame, msg );

	// delta encode the playerstate
	for( i = 0; i < frame->numplayers; i++ )
	{
		if( oldframe && oldframe->numplayers > i )
			SV_WritePlayerstateToClient( &oldframe->ps[i], &frame->ps[i], msg );
		else
			SV_WritePlayerstateToClient( NULL, &frame->ps[i], msg );
	}
	MSG_WriteByte( msg, 0 );

	// delta encode the entities
	SV_EmitPacketEntities( oldframe, frame, msg );

	// add the sound commands generated this frame
	for( i = 0; i < frame->numsounds; i++ )
		SV_WriteSoundToClient( &frame->sounds[i], msg );
	MSG_WriteByte( msg, 0 );

	// write length into reserved space
	length = msg->cursize - pos - 2;
	msg->cursize = pos;
	MSG_WriteShort( msg, length );
	msg->cursize += length;

	client->lastSentFrameNum = sv.framenum;
}


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

   Build a client frame structure

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

static qbyte fatpvs[MAX_MAP_LEAFS/8];
static qbyte fatphs[MAX_MAP_LEAFS/8];

//============
//SV_FatPVS
//
//The client will interpolate the view position,
//so we can't use a single PVS point
//===========
static void SV_FatPVS( vec3_t org )
{
	memset( fatpvs, 0, CM_ClusterSize( svs.cms ) );
	CM_MergePVS( svs.cms, org, fatpvs );
}

//============
//SV_FatPHS
//============
static void SV_FatPHS( int cluster )
{
	memset( fatphs, 0, CM_ClusterSize( svs.cms ) );
	CM_MergePHS( svs.cms, cluster, fatphs );
}

//============
//SV_BitsCullEntity
//===========
static qboolean SV_BitsCullEntity( cmodel_state_t *cms, edict_t *ent, qbyte *bits, int max_clusters )
{
	int i, l;

	// too many leafs for individual check, go by headnode
	if( ent->r.num_clusters == -1 )
	{
		if( !CM_HeadnodeVisible( cms, ent->r.headnode, bits ) )
			return qtrue;
		return qfalse;
	}

	// check individual leafs
	for( i = 0; i < max_clusters; i++ )
	{
		l = ent->r.clusternums[i];
		if( bits[l >> 3] & ( 1 << ( l&7 ) ) )
			return qfalse;
	}

	return qtrue;	// not visible/audible
}

#define SV_PVSCullEntity(ent) SV_BitsCullEntity(svs.cms,ent,fatpvs,ent->r.num_clusters)
#define SV_PHSCullEntity(ent) SV_BitsCullEntity(svs.cms,ent,fatphs,1)

//=====================================================================

#define	MAX_SNAPSHOT_ENTITIES	1024
typedef struct
{
	int numSnapshotEntities;
	int snapshotEntities[MAX_SNAPSHOT_ENTITIES];
	int entityAddedToSnapList[MAX_EDICTS];
} snapshotEntityNumbers_t;

static void SV_AddEntNumToSnapList( int entNum, snapshotEntityNumbers_t *entsList )
{
	if( entsList->numSnapshotEntities >= MAX_SNAPSHOT_ENTITIES )  // silent ignore of overflood
		return;

	// don't double add entities
	if( entsList->entityAddedToSnapList[entNum] )
		return;

	entsList->snapshotEntities[entsList->numSnapshotEntities] = entNum;
	entsList->numSnapshotEntities++;
	entsList->entityAddedToSnapList[entNum] = qtrue;
}

static qboolean SV_SnapCullEntity( edict_t *ent, edict_t *clent, client_frame_t *frame, vec3_t vieworg, int clientarea )
{
	// filters: this entity has been disabled for comunication
	if( ent->r.svflags & SVF_NOCLIENT )
		return qtrue;

	// send all entities
	if( frame->allentities )
		return qfalse;

	// filters: transmit only to clients in the same team as this entity
	// wsw: imp: moved if because broadcasting is less important than team specifics
	if( ( ent->r.svflags & SVF_ONLYTEAM ) && ( clent && ent->s.team != clent->s.team ) )
		return qtrue;

	if( ent->r.svflags & SVF_BROADCAST )  // send to everyone
		return qfalse;

	// if the entity is only a sound
	if( !ent->s.modelindex && !ent->s.events[0] && !ent->s.light && !ent->s.effects && ent->s.sound )
	{
		if( DistanceFast( ent->s.origin, vieworg ) > 400 )
			return qtrue;	// it would be attenuated
	}

	// this is the same as CM_AreasConnected but portal's visibility included
	if( !( frame->areabits[ent->r.areanum>>3] & ( 1<<( ent->r.areanum&7 ) ) ) )
	{
		// doors can legally straddle two areas, so we may need to check another one
		if( !ent->r.areanum2 || !( frame->areabits[ent->r.areanum2>>3] & ( 1<<( ent->r.areanum2&7 ) ) ) )
			return qtrue; // blocked by a door
	}

	if( (ent->r.svflags & SVF_TRANSMITORIGIN2) && !ent->s.linearProjectile )
		return SV_PHSCullEntity( ent );		// cull by PHS

	return SV_PVSCullEntity( ent );			// cull by PVS
}

static void SV_BuildSnapEntitiesList( edict_t *clent, vec3_t vieworg, client_frame_t *frame, snapshotEntityNumbers_t *entsList )
{
	int leafnum = 0, clusternum = 0, clientarea = 0;
	int entNum;
	edict_t	*ent;

	// find the client's PVS
	if( frame->allentities )
	{
		frame->areabytes = CM_WriteAreaBits( svs.cms, frame->areabits, -1 );
	}
	else
	{
		leafnum = CM_PointLeafnum( svs.cms, vieworg );
		clusternum = CM_LeafCluster( svs.cms, leafnum );
		clientarea = CM_LeafArea( svs.cms, leafnum );
		frame->areabytes = CM_WriteAreaBits( svs.cms, frame->areabits, clientarea );
	}

	if( clent )
	{
		SV_FatPVS( vieworg );
		SV_FatPHS( clusternum );

		// if the client is outside of the world, don't send him any entity (excepting himself)
		if( !frame->allentities && clusternum == -1 )
		{
			// FIXME we should send all the entities who's POV we are sending if frame->multipov
			SV_AddEntNumToSnapList( NUM_FOR_EDICT( clent ), entsList );
			return;
		}
	}

	// no need of merging when we are sending the whole level
	if( !frame->allentities )
	{
		// make a pass checking for sky portal and portal entities and merge PVS in case of finding any
		if( sv.configstrings[CS_SKYBOX][0] != '\0' )
		{
			vec3_t origin;
			if( sscanf( sv.configstrings[CS_SKYBOX], "%f %f %f", &origin[0], &origin[1], &origin[2] ) >= 3 )
				CM_MergeVisSets( svs.cms, origin, fatpvs, fatphs, frame->areabits );
		}

		for( entNum = 1; entNum < sv.num_edicts; entNum++ )
		{
			ent = EDICT_NUM( entNum );
			if( ent->r.svflags & SVF_PORTAL )
			{
				// merge visibility sets if portal
				if( SV_SnapCullEntity( ent, clent, frame, vieworg, clientarea ) )
					continue;

				if( !VectorCompare( ent->s.origin, ent->s.origin2 ) )
					CM_MergeVisSets( svs.cms, ent->s.origin2, fatpvs, fatphs, frame->areabits );
			}
		}
	}

	// add the entities to the list
	for( entNum = 1; entNum < sv.num_edicts; entNum++ )
	{
		ent = EDICT_NUM( entNum );

		// fix number if broken
		if( ent->s.number != entNum )
		{
			Com_DPrintf( "FIXING ENT->S.NUMBER!!!\n" );
			ent->s.number = entNum;
		}

		// always add the client entity, even if SVF_NOCLIENT
		if( ( ent != clent ) && SV_SnapCullEntity( ent, clent, frame, vieworg, clientarea ) )
			continue;

		// add it
		SV_AddEntNumToSnapList( entNum, entsList );
	}
}

//=============
//SV_SnapCullSound
//=============
static qboolean SV_SnapCullSound( sound_t *sound, edict_t *clent, client_frame_t *frame, int clientcluster, int clientarea )
{
	qbyte *mask;
	int leafnum, cluster;
	int area;

	if( frame->allentities )
		return qfalse;

	if( ( sound->channel & CHAN_NO_PHS_ADD ) || sound->attenuation == ATTN_NONE )
		return qfalse;

	leafnum = CM_PointLeafnum( svs.cms, sound->pos );
	area = CM_LeafArea( svs.cms, leafnum );
	cluster = CM_LeafCluster( svs.cms, leafnum );
	mask = CM_ClusterPHS( svs.cms, cluster );

	if( mask )
	{
		if( !( mask[clientcluster>>3] & ( 1<<( clientcluster&7 ) ) ) )
			return qtrue;
		if( !CM_AreasConnected( svs.cms, area, clientarea ) )
			return qtrue;
	}

	return qfalse;
}


//=============
//SV_BuildClientFrameSoundList
//=============
static void SV_BuildClientFrameSoundList( edict_t *clent, client_frame_t *frame )
{
	int i, leafnum, cluster, area;

	if( frame->allentities )
	{
		cluster = 0;
		area = 0;
	}
	else
	{
		leafnum = CM_PointLeafnum( svs.cms, clent->s.origin );
		cluster = CM_LeafCluster( svs.cms, leafnum );
		area = CM_LeafArea( svs.cms, leafnum );
	}

	frame->numsounds = 0;
	for( i = 0; i < sv.numframesounds && frame->numsounds < MAX_PARSE_SOUNDS; i++ )
	{
		if( SV_SnapCullSound( &sv.framesounds[i], clent, frame, cluster, area ) )
			continue;

		frame->numsounds++;
		frame->sounds[frame->numsounds - 1] = sv.framesounds[i];
	}
}

//=============
//SV_BuildClientFrameSnap
//
//Decides which entities are going to be visible to the client, and
//copies off the playerstat and areabits.
//=============
void SV_BuildClientFrameSnap( client_t *client )
{
	int e, i;
	vec3_t org;
	edict_t	*ent, *clent;
	client_frame_t *frame;
	entity_state_t *state;
	int numplayers;
	snapshotEntityNumbers_t entsList;

	clent = client->edict;
	if( clent && !clent->r.client )  // allow NULL ent for server record
		return; // not in game yet

	if( clent )
	{
		VectorSet( org, clent->s.origin[0], clent->s.origin[1],
		           clent->s.origin[2] + clent->r.client->ps.viewheight );
	}
	else
	{
		assert( client->mv );
		VectorClear( org );
	}

	// this is the frame we are creating
	frame = &client->frames[sv.framenum & UPDATE_MASK];
	frame->sentTimeStamp = svs.gametime;
	frame->UcmdExecuted = client->UcmdExecuted;

	if( client->mv )
	{
		frame->multipov = qtrue;
		frame->allentities = qtrue;
	}
	else
	{
		frame->multipov = qfalse;
		frame->allentities = qfalse;
	}

	// grab the current player_state_t
	if( frame->multipov )
	{
		frame->numplayers = 0;
		for( i = 0; i < sv_maxclients->integer; i++ )
		{
			if( ( clent && i == NUM_FOR_EDICT( clent ) - 1 ) ||
			   ( svs.clients[i].state >= CS_SPAWNED && svs.clients[i].edict &&
			    !( svs.clients[i].edict->r.svflags & SVF_NOCLIENT ) ) )
			{
				frame->numplayers++;
			}
		}
	}
	else
	{
		frame->numplayers = 1;
	}

	if( ( !frame->ps && frame->numplayers ) || frame->ps_size < frame->numplayers )
	{
		if( !frame->numplayers )
		{
			Mem_Free( frame->ps );
			frame->ps = NULL;
			frame->ps_size = 0;
		}
		else if( !frame->ps )
		{
			frame->ps = Mem_Alloc( sv_mempool, sizeof( player_state_t )*frame->numplayers );
			frame->ps_size = frame->numplayers;
		}
		else
		{
			frame->ps = Mem_Realloc( frame->ps, sizeof( player_state_t )*frame->numplayers );
			frame->ps_size = frame->numplayers;
		}
	}

	if( frame->multipov )
	{
		numplayers = 0;
		for( i = 0; i < sv_maxclients->integer; i++ )
		{
			if( ( clent && i == NUM_FOR_EDICT( clent ) - 1 ) ||
			   ( svs.clients[i].state >= CS_SPAWNED && svs.clients[i].edict &&
			    !( svs.clients[i].edict->r.svflags & SVF_NOCLIENT ) ) )
			{
				frame->ps[numplayers++] = svs.clients[i].edict->r.client->ps;
			}
		}
	}
	else
	{
		frame->ps[0] = clent->r.client->ps;
	}

	// grab current match state information
	ge->GetMatchState( &frame->matchstate );

	// build up the list of visible entities
	//=============================
	entsList.numSnapshotEntities = 0;
	memset( entsList.entityAddedToSnapList, 0, sizeof( entsList.entityAddedToSnapList ) );
	SV_BuildSnapEntitiesList( clent, org, frame, &entsList );
	//Com_Printf( "Snap NumEntities:%i\n", entsList.numSnapshotEntities );
	if( developer->integer )
	{
		int olde = -1;
		for( e = 0; e < entsList.numSnapshotEntities; e++ )
		{
			if( olde >= entsList.snapshotEntities[e] )
				Com_Printf( "WARNING 'SV_BuildClientFrameSnap': Unsorted entities list\n" );
			olde = entsList.snapshotEntities[e];
		}
	}
	//=============================

	// dump the entities list
	frame->num_entities = 0;
	frame->first_entity = svs.next_client_entities;

	for( e = 0; e < entsList.numSnapshotEntities; e++ )
	{
		ent = EDICT_NUM( entsList.snapshotEntities[e] );
		// add it to the circular client_entities array
		state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities];

		*state = ent->s;
		state->svflags = ent->r.svflags;

		// don't mark *any* missiles as solid
		if( ent->r.svflags & SVF_PROJECTILE )
			state->solid = 0;

		frame->num_entities++;
		svs.next_client_entities++;
	}

	// sounds
	SV_BuildClientFrameSoundList( clent, frame );
}
