/*
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.
=============
*/
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
			MSG_WriteDeltaEntity ( oldent, newent, msg, qfalse, newent->number <= sv_maxclients->integer 
				|| ( (EDICT_NUM(newent->number))->r.svflags & SVF_FORCEOLDORIGIN ) );
			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_NOOLDORIGIN));
			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
=============
*/
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 )
		bitmask |= MATCHSTATE_FLAG_STATE;

	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 )
		MSG_WriteByte( msg, (qbyte)ms->state );

	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
=============
*/
void SV_WritePlayerstateToClient( client_frame_t *from, client_frame_t *to, msg_t *msg )
{
	int				i;
	int				pflags;
	player_state_t	*ps, *ops;
	player_state_t	dummy;
	int				statbits;

	ps = &to->ps;
	if( !from )
	{
		memset( &dummy, 0, sizeof(dummy) );
		ops = &dummy;
	}
	else
		ops = &from->ps;

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

	//
	// 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, ps->pmove.origin[0] );
	if( pflags & PS_M_ORIGIN1 )
		MSG_WriteInt3( msg, ps->pmove.origin[1] );
	if( pflags & PS_M_ORIGIN2 )
		MSG_WriteInt3( msg, ps->pmove.origin[2] );

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

	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, (int)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] );
			}
		}
	}

	// 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_WriteClientSoundsDatagram

Write the accumulated sound commands buffer into the snap message
Note: Any command could go inside this message buffer, but I want
it restricted to sound commands, so, please, keep it like that.
Note2: The sounds datagram is not delta-compressed to older frame.
=======================
*/
void SV_WriteClientSoundsDatagram( client_t *client, msg_t *msg, msg_t *soundsMsg ) 
{
	if( !msg || !soundsMsg || !msg->data || !soundsMsg->data )
		return;
	
	if( !soundsMsg->cursize )
		return;
	
	if( soundsMsg->overflowed ) {
		Com_Printf( "WARNING: dropping sounds datagram for %s. SoundsMsg overflowed\n", client->name );
		MSG_Clear( soundsMsg );
		return;
	}
	
	// packet is already full
	if( msg->overflowed || msg->cursize >= msg->maxsize ) {
		Com_Printf( "WARNING: dropping sounds datagram for %s. Msg overflowed\n", client->name );
		MSG_Clear( soundsMsg );
		return;
	}

	// see if there's room for it
	if( (msg->maxsize - msg->cursize) < (soundsMsg->cursize + PACKET_HEADER) )
	{
		Com_Printf( "WARNING: dropping sounds datagram for %s. Not enough space in msg\n", client->name );
		MSG_Clear( soundsMsg );
		return;
	}

	// write it
	MSG_Write( msg, soundsMsg->data, soundsMsg->cursize );
	MSG_Clear( soundsMsg );
}


/*
==================
SV_WriteFrameSnapToClient
==================
*/
void SV_WriteFrameSnapToClient( client_t *client, msg_t *msg )
{
	client_frame_t		*frame, *oldframe;
	int					lastframe;

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

	if( client->lastframe <= 0 || client->lastframe > (int)sv.framenum )
	{	// client is asking for a not compressed retransmit
		oldframe = NULL;
		lastframe = -1;
	}
	else if( sv.framenum - client->lastframe >= (UPDATE_BACKUP - 3) )
	{	// client hasn't gotten a good message through in a long time
		oldframe = NULL;
		lastframe = -1;
	}
	else
	{	// we have a valid message to delta from
		oldframe = &client->frames[client->lastframe & UPDATE_MASK];
		lastframe = client->lastframe;
	}

	MSG_WriteByte( msg, svc_frame );
	MSG_WriteLong( msg, svs.realtime );	// server timeStamp
	MSG_WriteLong( msg, sv.framenum );
	MSG_WriteLong( msg, lastframe );	// what we are delta'ing from
#ifdef RATEKILLED
	MSG_WriteByte( msg, 0 );
#else
	MSG_WriteByte( msg, client->suppressCount );	// rate dropped packets
	client->suppressCount = 0;
#endif
	// send over the areabits
	MSG_WriteByte( msg, frame->areabytes );
	MSG_Write( msg, frame->areabits, frame->areabytes );

	SV_WriteDeltaMatchStateToClient( oldframe, frame, msg );

	// delta encode the playerstate
	SV_WritePlayerstateToClient( oldframe, frame, msg );

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

	// add the sound commands generated this frame
	SV_WriteClientSoundsDatagram( client, msg, &client->soundsmsg );
	client->lastSentFrameNum = sv.framenum;
}


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

Build a client frame structure

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

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

/*
============
SV_FatPVS

The client will interpolate the view position,
so we can't use a single PVS point
===========
*/
void SV_FatPVS( vec3_t org )
{
	int		leafs[128];
	int		i, j, count;
	int		longs;
	qbyte	*src;
	vec3_t	mins, maxs;

	for (i=0 ; i<3 ; i++)
	{
		mins[i] = org[i] - 8;
		maxs[i] = org[i] + 8;
	}

	count = CM_BoxLeafnums (mins, maxs, leafs, 128, NULL);
	if (count < 1)
		Com_Error (ERR_FATAL, "SV_FatPVS: count < 1");
	longs = CM_ClusterSize()>>2;

	// convert leafs to clusters
	for (i=0 ; i<count ; i++)
		leafs[i] = CM_LeafCluster(leafs[i]);

	memcpy (fatpvs, CM_ClusterPVS(leafs[0]), longs<<2);

	// or in all the other leaf bits
	for (i=1 ; i<count ; i++)
	{
		for (j=0 ; j<i ; j++)
			if (leafs[i] == leafs[j])
				break;
		if (j != i)
			continue;		// already have the cluster we want
		src = CM_ClusterPVS(leafs[i]);
		for (j=0 ; j<longs ; j++)
			((long *)fatpvs)[j] |= ((long *)src)[j];
	}
}

/*
============
SV_FatPHS
===========
*/
void SV_FatPHS( int cluster )
{
	memcpy( fatphs, CM_ClusterPHS(cluster), CM_ClusterSize() );
}

/*
============
SV_MergePVS

Portal entities add a second PVS at origin2 to fatpvs 
===========
*/
void SV_MergePVS( vec3_t org )
{
	int		leafs[128];
	int		i, j, count;
	int		longs;
	qbyte	*src;
	vec3_t	mins, maxs;

	for( i = 0; i < 3; i++ )
	{
		mins[i] = org[i] - 1;
		maxs[i] = org[i] + 1;
	}

	count = CM_BoxLeafnums( mins, maxs, leafs, 128, NULL );
	if( count < 1 )
		Com_Error( ERR_FATAL, "SV_FatPVS: count < 1" );
	longs = CM_ClusterSize()>>2;

	// convert leafs to clusters
	for( i = 0; i < count; i++ )
		leafs[i] = CM_LeafCluster( leafs[i] );

	// or in all the other leaf bits
	for( i = 0; i < count; i++ )
	{
		for( j = 0; j < i; j++ )
			if( leafs[i] == leafs[j] )
				break;
		if( j != i )
			continue;		// already have the cluster we want
		src = CM_ClusterPVS( leafs[i] );
		for( j = 0; j < longs; j++ )
			((long *)fatpvs)[j] |= ((long *)src)[j];
	}
}

/*
============
SV_MergePHS
===========
*/
void SV_MergePHS( int cluster )
{
	int		i, longs;
	qbyte	*src;

	longs = CM_ClusterSize()>>2;

	// or in all the other leaf bits
	src = CM_ClusterPHS (cluster);
	for (i=0 ; i<longs ; i++)
		((long *)fatphs)[i] |= ((long *)src)[i];
}

/*
=============
SV_CullEntity
=============
*/
qboolean SV_CullEntity( edict_t *ent )
{
	int i, l;

	// wsw : jal : force sending entities with SVF_BROADCAST
	if( ent->r.svflags & SVF_BROADCAST )
		return qfalse;

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

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

/*
=============
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, l;
	vec3_t			org;
	edict_t			*ent, *clent;
	edict_t			*pedicts[MAX_EDICTS];
	client_frame_t	*frame;
	entity_state_t	*state;
	int				clientarea, portalarea;
	int				leafnum, clusternum;
	int				numedicts;
	qboolean		portalview;

	clent = client->edict;
	if( !clent->r.client )
		return;		// not in game yet

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

	// find the client's PVS
	VectorSet( org, clent->s.origin[0], clent->s.origin[1],
		clent->s.origin[2] + clent->r.client->ps.viewheight );

	leafnum = CM_PointLeafnum( org );
	clusternum = CM_LeafCluster( leafnum );
	clientarea = CM_LeafArea( leafnum );

	// calculate the visible areas
	frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea );

	// grab the current player_state_t
	frame->ps = clent->r.client->ps;
	
	// grab current match state information
	ge->GetMatchState( &frame->matchstate );

	// build up the list of visible entities
	frame->num_entities = 0;
	frame->first_entity = svs.next_client_entities;

	// the client is outside the world
	if( clusternum == -1 ) {
		for( e = 1; e < sv.num_edicts; e++ ) {
			ent = EDICT_NUM( e );

			// ignore ents without visible models
			if( ent->r.svflags & SVF_NOCLIENT )
				continue;
			if( ent->r.visclent && (ent->r.visclent != clent) )
				continue;

			// ignore ents without visible models unless they have an effect
			if( !ent->s.modelindex && !ent->s.effects && !ent->s.sound && !ent->s.events[0] && !ent->s.light )
				continue;
			// wsw : jal : transmit to same team only
			if( ent->r.svflags & SVF_ONLYTEAM && ent->s.team != clent->s.team )
				continue;

			if( !(ent->r.svflags & SVF_BROADCAST) || ent != clent )
				continue;

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

			// add it to the circular client_entities array
			state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities];
			*state = ent->s;

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

	// save client's PVS so it can be later compared with fatpvs
	SV_FatPVS( org );
	SV_FatPHS( clusternum );

	portalview = qfalse;

	// portal entities are the first to be checked so we can merge PV sets
	for( e = 1, numedicts = 0; e < sv.num_edicts; e++ ) {
		ent = EDICT_NUM( e );

		// ignore ents without visible models
		if( ent->r.svflags & SVF_NOCLIENT )
			continue;
		if( ent->r.visclent && (ent->r.visclent != clent) )
			continue;

		// ignore ents without visible models unless they have an effect
		if( !ent->s.modelindex && !ent->s.effects && !ent->s.sound && !ent->s.events[0] && !ent->s.light )
			continue;

		// wsw : jal : transmit to same team only
		if( ent->r.svflags & SVF_ONLYTEAM && ent->s.team != clent->s.team )
			continue;

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

		// ignore if not touching a PV leaf
		//if( ent != clent || !(ent->r.svflags & SVF_BROADCAST) ) {
		if( ent != clent && !(ent->r.svflags & SVF_BROADCAST) ) { // wsw : jal : never filter broadcast entities
			if( !(ent->r.svflags & SVF_PORTAL) ) {
				if( !ent->s.modelindex && !ent->s.events[0] && !ent->s.light && !ent->s.effects ) {
					// don't send sounds if they will be attenuated away
					vec3_t	delta;
					float	len;

					VectorSubtract( org, ent->s.origin, delta );
					len = VectorLength( delta );
					if( len > 400 )
						continue;
				}

				pedicts[numedicts++] = ent;
				continue;
			}

			// check area
			if( !CM_AreasConnected( clientarea, ent->r.areanum ) && !(ent->r.svflags & SVF_BROADCAST) )
				continue;
			if( SV_CullEntity( ent ) )
				continue;

			// merge PV sets if portal 
			if( !VectorCompare( ent->s.origin, ent->s.origin2 ) ) {
				SV_MergePVS( ent->s.old_origin );

				portalarea = CM_PointLeafnum( ent->s.origin2 );
				SV_MergePHS( CM_LeafCluster( portalarea ) );

				portalarea = CM_LeafArea( portalarea );
				CM_MergeAreaBits( frame->areabits, portalarea );

				portalview = qtrue;
			}
		}

		// add it to the circular client_entities array
		state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities];
		*state = ent->s;

		// don't mark players missiles as solid
		if( ent->r.owner == client->edict )
			state->solid = 0;

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

	for( e = 0; e < numedicts; e++ ) {
		ent = pedicts[e];

		// check area
		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)) ) )
				continue;		// blocked by a door
		}

		// just check one point for PHS
		if( ent->r.svflags & SVF_FORCEOLDORIGIN ) {
			if( ent->r.num_clusters == -1 ) {
				if( !CM_HeadnodeVisible( ent->r.headnode, fatphs ) && !(ent->r.svflags & SVF_BROADCAST) )
					continue;
			} else {
				l = ent->r.clusternums[0];
				if( !(fatphs[l >> 3] & (1 << (l&7) )) )
					continue;
			}
		} else if( SV_CullEntity( ent ) ) {
			continue;
		}

		// add it to the circular client_entities array
		state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities];
		*state = ent->s;

		// don't mark players missiles as solid
		if( ent->r.owner == client->edict )
			state->solid = 0;

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


