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

 */
// cl_ents.c -- entity parsing and management

#include "client.h"

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

   FRAME PARSING

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


/*
   =============
   CL_ParseDeltaMatchState
   =============
 */
static void CL_ParseDeltaMatchState( msg_t *msg, frame_t *oldframe, frame_t *newframe )
{
	match_state_t *matchstate;
	int bitmask;

	// start from old state or 0 if none
	matchstate = &newframe->match;
	if( oldframe )
	{
		*matchstate = oldframe->match;
	}
	else
	{
		memset( matchstate, 0, sizeof( match_state_t ) );
	}

	bitmask = MSG_ReadByte( msg );

	if( bitmask & MATCHSTATE_FLAG_STATE )
	{
		qbyte mstate = (qbyte)MSG_ReadByte( msg );
		matchstate->state = mstate & 127;
		matchstate->paused = ( mstate & ET_INVERSE ) ? qtrue : qfalse;
	}

	if( bitmask & MATCHSTATE_FLAG_ROUNDSTATE )
		matchstate->roundstate = MSG_ReadByte( msg );

	if( bitmask & MATCHSTATE_FLAG_TIMELIMIT )
	{
		int timelimitmask = MSG_ReadLong( msg );
		if( timelimitmask & MATCHSTATE_EXTENDEDTIME_BIT )
		{
			matchstate->extendedtime = qtrue;
			timelimitmask &= ~MATCHSTATE_EXTENDEDTIME_BIT;
		}
		else
		{
			matchstate->extendedtime = qfalse;
		}
		matchstate->timelimit = timelimitmask;
	}

	if( bitmask & MATCHSTATE_FLAG_CLOCK_MSECS )
		matchstate->clock_msecs = MSG_ReadByte( msg );

	if( bitmask & MATCHSTATE_FLAG_CLOCK_SECS )
		matchstate->clock_secs = MSG_ReadByte( msg );

	if( bitmask & MATCHSTATE_FLAG_CLOCK_MINUTES )
		matchstate->clock_mins = MSG_ReadShort( msg );
}

/*
   ===================
   CL_ParsePlayerstate
   ===================
 */
static void CL_ParsePlayerstate( msg_t *msg, player_state_t *oldstate, player_state_t *state )
{
	int flags;
	int i, b;
	int statbits;

	// clear to old value before delta parsing
	if( oldstate )
		memcpy( state, oldstate, sizeof( *state ) );
	else
		memset( state, 0, sizeof( *state ) );

	flags = (qbyte)MSG_ReadByte( msg );
	if( flags & PS_MOREBITS1 )
	{
		b = (qbyte)MSG_ReadByte( msg );
		flags |= b<<8;
	}
	if( flags & PS_MOREBITS2 )
	{
		b = (qbyte)MSG_ReadByte( msg );
		flags |= b<<16;
	}
	if( flags & PS_MOREBITS3 )
	{
		b = (qbyte)MSG_ReadByte( msg );
		flags |= b<<24;
	}

	//
	// parse the pmove_state_t
	//
	if( flags & PS_M_TYPE )
		state->pmove.pm_type = (qbyte)MSG_ReadByte( msg );

	if( flags & PS_M_ORIGIN0 )
		state->pmove.origin[0] = ( (float)MSG_ReadInt3( msg )*( 1.0/PM_VECTOR_SNAP ) );
	if( flags & PS_M_ORIGIN1 )
		state->pmove.origin[1] = ( (float)MSG_ReadInt3( msg )*( 1.0/PM_VECTOR_SNAP ) );
	if( flags & PS_M_ORIGIN2 )
		state->pmove.origin[2] = ( (float)MSG_ReadInt3( msg )*( 1.0/PM_VECTOR_SNAP ) );

	if( flags & PS_M_VELOCITY0 )
		state->pmove.velocity[0] = ( (float)MSG_ReadInt3( msg )*( 1.0/PM_VECTOR_SNAP ) );
	if( flags & PS_M_VELOCITY1 )
		state->pmove.velocity[1] = ( (float)MSG_ReadInt3( msg )*( 1.0/PM_VECTOR_SNAP ) );
	if( flags & PS_M_VELOCITY2 )
		state->pmove.velocity[2] = ( (float)MSG_ReadInt3( msg )*( 1.0/PM_VECTOR_SNAP ) );

	if( flags & PS_M_TIME )
		state->pmove.pm_time = (qbyte)MSG_ReadByte( msg );

	if( flags & PS_M_FLAGS )
		state->pmove.pm_flags = MSG_ReadShort( msg );

	if( flags & PS_M_DELTA_ANGLES0 )
		state->pmove.delta_angles[0] = MSG_ReadShort( msg );
	if( flags & PS_M_DELTA_ANGLES1 )
		state->pmove.delta_angles[1] = MSG_ReadShort( msg );
	if( flags & PS_M_DELTA_ANGLES2 )
		state->pmove.delta_angles[2] = MSG_ReadShort( msg );

	if( flags & PS_EVENT )
		state->event = MSG_ReadShort( msg );

	if( flags & PS_VIEWANGLES )
	{
		state->viewangles[0] = MSG_ReadAngle16( msg );
		state->viewangles[1] = MSG_ReadAngle16( msg );
		state->viewangles[2] = MSG_ReadAngle16( msg );
	}

	if( flags & PS_M_GRAVITY )
		state->pmove.gravity = MSG_ReadShort( msg );

	if( flags & PS_FOV )
		state->fov = (qbyte)MSG_ReadByte( msg );

	if( flags & PS_POVNUM )
		state->POVnum = (qbyte)MSG_ReadByte( msg );
	if( state->POVnum == 0 )
		Com_Error( ERR_DROP, "Invalid POVnum" );

	if( flags & PS_VIEWHEIGHT )
		state->viewheight = MSG_ReadChar( msg );

	if( flags & PS_PMOVESTATS )
	{
		for( i = 0; i < PM_STAT_SIZE; i++ )
			state->pmove.stats[i] = MSG_ReadShort( msg );
	}

	if( flags & PS_WEAPONLIST )
	{
		// parse weaponlist
		statbits = MSG_ReadShort( msg );
		for( i = 0; i < MAX_WEAPLIST_STATS; i++ )
		{
			if( statbits & ( 1<<i ) )
			{
				state->weaponlist[i][0] = MSG_ReadByte( msg );
				state->weaponlist[i][1] = MSG_ReadByte( msg );
				state->weaponlist[i][2] = MSG_ReadByte( msg );
			}
		}
	}

	if( flags & PS_PLRKEYS )
		state->plrkeys = MSG_ReadByte( msg );

	// parse stats
	statbits = MSG_ReadLong( msg );
	for( i = 0; i < PS_MAX_STATS; i++ )
	{
		if( statbits & ( 1<<i ) )
			state->stats[i] = MSG_ReadShort( msg );
	}
}

/*
   =================
   CL_ParseEntityBits

   Returns the entity number and the header bits
   =================
 */
int CL_ParseEntityBits( msg_t *msg, unsigned *bits )
{
	unsigned b, total;
	int number;

	total = (qbyte)MSG_ReadByte( msg );
	if( total & U_MOREBITS1 )
	{
		b = (qbyte)MSG_ReadByte( msg );
		total |= ( b<<8 )&0x0000FF00;
	}
	if( total & U_MOREBITS2 )
	{
		b = (qbyte)MSG_ReadByte( msg );
		total |= ( b<<16 )&0x00FF0000;
	}
	if( total & U_MOREBITS3 )
	{
		b = (qbyte)MSG_ReadByte( msg );
		total |= ( b<<24 )&0xFF000000;
	}

	if( total & U_NUMBER16 )
		number = MSG_ReadShort( msg );
	else
		number = (qbyte)MSG_ReadByte( msg );

	*bits = total;

	return number;
}

/*
   ==================
   CL_ParseDelta

   Can go from either a baseline or a previous packet_entity
   ==================
 */
void CL_ParseDelta( msg_t *msg, entity_state_t *from, entity_state_t *to, int number, unsigned bits )
{
	// set everything to the state we are delta'ing from
	*to = *from;

	to->number = number;

	if( bits & U_TYPE )
	{
		qbyte ttype;
		ttype = (qbyte)MSG_ReadByte( msg );
		to->type = ttype & 127;
		to->linearProjectile = ( ttype & EV_INVERSE );
	}

	if( bits & U_SOLID )
		to->solid = MSG_ReadShort( msg );

	if( bits & U_MODEL )
		to->modelindex = (qbyte)MSG_ReadByte( msg );
	if( bits & U_MODEL2 )
		to->modelindex2 = (qbyte)MSG_ReadByte( msg );

	if( bits & U_FRAME8 )
		to->frame = (qbyte)MSG_ReadByte( msg );
	if( bits & U_FRAME16 )
		to->frame = MSG_ReadShort( msg );

	if( ( bits & U_SKIN8 ) && ( bits & U_SKIN16 ) )  //used for laser colors
		to->skinnum = MSG_ReadLong( msg );
	else if( bits & U_SKIN8 )
		to->skinnum = MSG_ReadByte( msg );
	else if( bits & U_SKIN16 )
		to->skinnum = MSG_ReadShort( msg );

	if( ( bits & ( U_EFFECTS8|U_EFFECTS16 ) ) == ( U_EFFECTS8|U_EFFECTS16 ) )
		to->effects = MSG_ReadLong( msg );
	else if( bits & U_EFFECTS8 )
		to->effects = (qbyte)MSG_ReadByte( msg );
	else if( bits & U_EFFECTS16 )
		to->effects = MSG_ReadShort( msg );

	if( ( bits & ( U_RENDERFX8|U_RENDERFX16 ) ) == ( U_RENDERFX8|U_RENDERFX16 ) )
		to->renderfx = MSG_ReadLong( msg );
	else if( bits & U_RENDERFX8 )
		to->renderfx = (qbyte)MSG_ReadByte( msg );
	else if( bits & U_RENDERFX16 )
		to->renderfx = MSG_ReadShort( msg );

	if( to->linearProjectile )
	{
		if( bits & U_ORIGIN1 )
			to->linearProjectileVelocity[0] = MSG_ReadCoord( msg );
		if( bits & U_ORIGIN2 )
			to->linearProjectileVelocity[1] = MSG_ReadCoord( msg );
		if( bits & U_ORIGIN3 )
			to->linearProjectileVelocity[2] = MSG_ReadCoord( msg );
	}
	else
	{
		if( bits & U_ORIGIN1 )
			to->origin[0] = MSG_ReadCoord( msg );
		if( bits & U_ORIGIN2 )
			to->origin[1] = MSG_ReadCoord( msg );
		if( bits & U_ORIGIN3 )
			to->origin[2] = MSG_ReadCoord( msg );
	}

	if( ( bits & U_ANGLE1 ) && ( to->solid == SOLID_BMODEL ) )
		to->angles[0] = MSG_ReadAngle16( msg );
	else if( bits & U_ANGLE1 )
		to->angles[0] = MSG_ReadAngle( msg );

	if( ( bits & U_ANGLE2 ) && ( to->solid == SOLID_BMODEL ) )
		to->angles[1] = MSG_ReadAngle16( msg );
	else if( bits & U_ANGLE2 )
		to->angles[1] = MSG_ReadAngle( msg );

	if( ( bits & U_ANGLE3 ) && ( to->solid == SOLID_BMODEL ) )
		to->angles[2] = MSG_ReadAngle16( msg );
	else if( bits & U_ANGLE3 )
		to->angles[2] = MSG_ReadAngle( msg );

	if( bits & U_OTHERORIGIN )
		MSG_ReadPos( msg, to->origin2 );

	if( bits & U_SOUND )
		to->sound = (qbyte)MSG_ReadByte( msg );

	if( bits & U_EVENT )
	{
		int event;
		event = (qbyte)MSG_ReadByte( msg );
		if( event & EV_INVERSE )
		{
			to->eventParms[0] = (qbyte)MSG_ReadByte( msg );
		}
		else
		{
			to->eventParms[0] = 0;
		}
		to->events[0] = ( event & ~EV_INVERSE );
	}
	else
	{
		to->events[0] = 0;
		to->eventParms[0] = 0;
	}

	if( bits & U_EVENT2 )
	{
		int event;
		event = (qbyte)MSG_ReadByte( msg );
		if( event & EV_INVERSE )
		{
			to->eventParms[1] = (qbyte)MSG_ReadByte( msg );
		}
		else
		{
			to->eventParms[1] = 0;
		}
		to->events[1] = ( event & ~EV_INVERSE );
	}
	else
	{
		to->events[1] = 0;
		to->eventParms[1] = 0;
	}

	if( bits & U_WEAPON )
	{
		qbyte tweapon;
		tweapon = (qbyte)MSG_ReadByte( msg );
		to->weapon = tweapon & 127;
		to->teleported = ( tweapon & EV_INVERSE );
	}

	if( bits & U_SVFLAGS )
		to->svflags = MSG_ReadByte( msg );

	if( bits & U_LIGHT )
	{
		if( to->linearProjectile )
			to->linearProjectileTimeStamp = (unsigned int)MSG_ReadLong( msg );
		else
			to->light = MSG_ReadLong( msg );
	}

	if( bits & U_TEAM )
		to->team = (qbyte)MSG_ReadByte( msg );
}

/*
   ==================
   CL_DeltaEntity

   Parses deltas from the given base and adds the resulting entity
   to the current frame
   ==================
 */
static void CL_DeltaEntity( msg_t *msg, frame_t *frame, int newnum, entity_state_t *old, unsigned bits )
{
	entity_state_t *state;

	state = &frame->parsedEntities[frame->numEntities & ( MAX_PARSE_ENTITIES-1 )];
	frame->numEntities++;
	CL_ParseDelta( msg, old, state, newnum, bits );
}

//==================
//CL_ParsePacketEntities
//
//An svc_packetentities has just been parsed, deal with the
//rest of the data stream.
//==================
static void CL_ParsePacketEntities( msg_t *msg, frame_t *oldframe, frame_t *newframe )
{
	int newnum;
	unsigned bits;
	entity_state_t *oldstate = NULL;
	int oldindex, oldnum;

	newframe->numEntities = 0;

	// delta from the entities present in oldframe
	oldindex = 0;
	if( !oldframe )
		oldnum = 99999;
	else if( oldindex >= oldframe->numEntities )
	{
		oldnum = 99999;
	}
	else
	{
		oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
		oldnum = oldstate->number;
	}

	while( qtrue )
	{
		newnum = CL_ParseEntityBits( msg, &bits );
		if( newnum >= MAX_EDICTS )
			Com_Error( ERR_DROP, "CL_ParsePacketEntities: bad number:%i", newnum );
		if( msg->readcount > msg->cursize )
			Com_Error( ERR_DROP, "CL_ParsePacketEntities: end of message" );

		if( !newnum )
			break;

		while( oldnum < newnum )
		{ // one or more entities from the old packet are unchanged
			if( cl_shownet->integer == 3 )
				Com_Printf( "   unchanged: %i\n", oldnum );

			CL_DeltaEntity( msg, newframe, oldnum, oldstate, 0 );

			oldindex++;
			if( oldindex >= oldframe->numEntities )
			{
				oldnum = 99999;
			}
			else
			{
				oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
				oldnum = oldstate->number;
			}
		}

		// delta from baseline
		if( oldnum > newnum )
		{
			if( bits & U_REMOVE )
			{
				Com_Printf( "U_REMOVE: oldnum > newnum (can't remove from baseline!)\n" );
				continue;
			}

			// delta from baseline
			if( cl_shownet->integer == 3 )
				Com_Printf( "   baseline: %i\n", newnum );

			CL_DeltaEntity( msg, newframe, newnum, &cl_baselines[newnum], bits );
			continue;
		}

		if( oldnum == newnum )
		{
			if( bits & U_REMOVE )
			{ // the entity present in oldframe is not in the current frame
				if( cl_shownet->integer == 3 )
					Com_Printf( "   remove: %i\n", newnum );

				if( oldnum != newnum )
					Com_Printf( "U_REMOVE: oldnum != newnum\n" );

				oldindex++;
				if( oldindex >= oldframe->numEntities )
				{
					oldnum = 99999;
				}
				else
				{
					oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
					oldnum = oldstate->number;
				}

				continue;
			}

			// delta from previous state
			if( cl_shownet->integer == 3 )
				Com_Printf( "   delta: %i\n", newnum );

			CL_DeltaEntity( msg, newframe, newnum, oldstate, bits );

			oldindex++;
			if( oldindex >= oldframe->numEntities )
			{
				oldnum = 99999;
			}
			else
			{
				oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
				oldnum = oldstate->number;
			}
			continue;
		}
	}

	// any remaining entities in the old frame are copied over
	while( oldnum != 99999 )
	{ // one or more entities from the old packet are unchanged
		if( cl_shownet->integer == 3 )
			Com_Printf( "   unchanged: %i\n", oldnum );

		CL_DeltaEntity( msg, newframe, oldnum, oldstate, 0 );

		oldindex++;
		if( oldindex >= oldframe->numEntities )
		{
			oldnum = 99999;
		}
		else
		{
			oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
			oldnum = oldstate->number;
		}
	}
}

/*
   ==================
   CL_ParseStartSoundPacket
   ==================
 */
static void CL_ParseStartSoundPacket( frame_t *frame, msg_t *msg )
{
	vec3_t pos;
	int channel, entnum;
	int sound_num;
	float volume;
	float attenuation;
	int flags;
	sound_t	*sound;

	flags = MSG_ReadByte( msg );
	sound_num = MSG_ReadByte( msg );

	// entity relative
	channel = MSG_ReadShort( msg );
	entnum = channel>>3;
	if( entnum > MAX_EDICTS )
		Com_Error( ERR_DROP, "CL_ParseStartSoundPacket: entnum = %i", entnum );
	channel &= 7;

	// positioned in space
	if( ( flags & ( SND_POS0_8|SND_POS0_16 ) ) == SND_POS0_8 )
		pos[0] = MSG_ReadChar( msg );
	else if( ( flags & ( SND_POS0_8|SND_POS0_16 ) ) == SND_POS0_16 )
		pos[0] = MSG_ReadShort( msg );
	else
		pos[0] = MSG_ReadInt3( msg );

	if( ( flags & ( SND_POS1_8|SND_POS1_16 ) ) == SND_POS1_8 )
		pos[1] = MSG_ReadChar( msg );
	else if( ( flags & ( SND_POS1_8|SND_POS1_16 ) ) == SND_POS1_16 )
		pos[1] = MSG_ReadShort( msg );
	else
		pos[1] = MSG_ReadInt3( msg );

	if( ( flags & ( SND_POS2_8|SND_POS2_16 ) ) == SND_POS2_8 )
		pos[2] = MSG_ReadChar( msg );
	else if( ( flags & ( SND_POS2_8|SND_POS2_16 ) ) == SND_POS2_16 )
		pos[2] = MSG_ReadShort( msg );
	else
		pos[2] = MSG_ReadInt3( msg );

	if( flags & SND_VOLUME )
		volume = MSG_ReadByte( msg ) / 255.0;
	else
		volume = DEFAULT_SOUND_PACKET_VOLUME;

	if( flags & SND_ATTENUATION )
		attenuation = MSG_ReadByte( msg );
	else
		attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;

	if( frame->numsounds >= MAX_PARSE_SOUNDS )
	{
		Com_DPrintf( "MAX_PARSE_SOUNDS reached, dropping a sound\n" );
		return;
	}

	sound = &frame->sounds[frame->numsounds];
	VectorCopy( pos, sound->pos );
	sound->entnum = entnum;
	sound->channel = channel;
	sound->num = sound_num;
	sound->volume = volume;
	sound->attenuation = attenuation;

	frame->numsounds++;
}

//================
//CL_ParseFrameSnap
//================
static frame_t *CL_ParseFrameSnap( msg_t *msg )
{
	int cmd, len;
	frame_t	*deltaframe;
	int numplayers, flags;
	char *text;
	int framediff, i, numtargets, target;
	gcommand_t *gcmd;
	frame_t	*newframe;
	unsigned int serverTime;
	int snapNum;

	// get total length
	len = MSG_ReadShort( msg );

	// get the snapshot id
	serverTime = (unsigned)MSG_ReadLong( msg );
	snapNum = MSG_ReadLong( msg );
	newframe = &cl.frames[snapNum & UPDATE_MASK];
	memset( newframe, 0, sizeof( frame_t ) );

	assert( cl.baseServerTime <= serverTime );

	newframe->serverTime = serverTime - cl.baseServerTime;
	newframe->serverFrame = snapNum;
	newframe->deltaFrameNum = MSG_ReadLong( msg );
	newframe->ucmdExecuted = MSG_ReadLong( msg );

	flags = MSG_ReadByte( msg );
	newframe->delta = ( flags & FRAMESNAP_FLAG_DELTA );
	newframe->multipov = ( flags & FRAMESNAP_FLAG_MULTIPOV );
	newframe->allentities = ( flags & FRAMESNAP_FLAG_ALLENTITIES );

	cl.suppressCount = MSG_ReadByte( msg );
#ifdef RATEKILLED
	cl.suppressCount = 0;
#endif

	if( cl_shownet->integer == 3 )
	{
		Com_Printf( "   frame:%i  old:%i%s\n", newframe->serverFrame, newframe->deltaFrameNum,
		           ( newframe->delta ? "" : " no delta" ) );
	}

	// validate the new frame
	newframe->valid = qfalse;

	// If the frame is delta compressed from data that we
	// no longer have available, we must suck up the rest of
	// the frame, but not use it, then ask for a non-compressed
	// message
	if( !newframe->delta )
	{
		newframe->valid = qtrue; // uncompressed frame
		deltaframe = NULL;
		cls.demowaiting = qfalse; // we can start recording now
	}
	else
	{
		if( newframe->deltaFrameNum <= 0 )
		{
			Com_Printf( "Invalid delta frame (not supposed to happen!).\n" );
			deltaframe = NULL;
		}
		else
		{
			deltaframe = &cl.frames[newframe->deltaFrameNum & UPDATE_MASK];
			if( !deltaframe->valid )
			{
				// should never happen
				Com_Printf( "Delta from invalid frame (not supposed to happen!).\n" );
			}
			else if( deltaframe->serverFrame != newframe->deltaFrameNum )
			{
				// The frame that the server did the delta from
				// is too old, so we can't reconstruct it properly.
				Com_Printf( "Delta frame too old.\n" );
			}
			else
			{
				newframe->valid = qtrue; // valid delta parse
			}
		}
	}
	// read game commands
	cmd = MSG_ReadByte( msg );
	if( cmd != svc_gamecommands )
		Com_Error( ERR_DROP, "CL_ParseFrame: not gamecommands" );

	numtargets = 0;
	while( ( framediff = MSG_ReadShort( msg ) ) != -1 )
	{
		text = MSG_ReadString( msg );
		// see if it's valid and not yet handled
		if( newframe->valid &&
		   ( cl.receivedSnapNum <= 0 || !cl.frames[cl.receivedSnapNum&UPDATE_MASK].valid
		     || newframe->serverFrame - framediff > cl.frames[cl.receivedSnapNum&UPDATE_MASK].serverFrame ) )
		{
			newframe->numgamecommands++;
			if( newframe->numgamecommands > MAX_PARSE_GAMECOMMANDS )
				Com_Error( ERR_DROP, "CL_ParseFrame: too many gamecommands" );
			if( newframe->gamecommandsDataHead + strlen( text ) >= sizeof( newframe->gamecommandsData ) )
				Com_Error( ERR_DROP, "CL_ParseFrame: too much gamecommands" );

			gcmd = &newframe->gamecommands[newframe->numgamecommands - 1];
			Q_strncpyz( newframe->gamecommandsData + newframe->gamecommandsDataHead, text,
			            sizeof( newframe->gamecommandsData ) - newframe->gamecommandsDataHead );
			gcmd->commandOffset = newframe->gamecommandsDataHead;
			newframe->gamecommandsDataHead += strlen( text ) + 1;

			if( newframe->multipov )
			{
				numtargets = MSG_ReadByte( msg );
				if( !numtargets )
				{
					gcmd->all = qtrue;
				}
				else
				{
					gcmd->all = qfalse;
					for( i = 0; i < numtargets; i++ )
					{
						target = MSG_ReadByte( msg );
						gcmd->targets[target] = qtrue;
					}
				}
			}
			else
			{
				gcmd->all = qtrue;
			}
		}
		else if( newframe->multipov ) // otherwise, ignore it
		{
			numtargets = MSG_ReadByte( msg );
			for( i = 0; i < numtargets; i++ )
				MSG_ReadByte( msg );
		}
	}

	// read areabits
	len = MSG_ReadByte( msg );
	if( len > sizeof( newframe->areabits ) )
		Com_Error( ERR_DROP, "Invalid areabits size" );
	memset( newframe->areabits, 0, sizeof( newframe->areabits ) );
	MSG_ReadData( msg, &newframe->areabits, len );

	// read match info
	cmd = MSG_ReadByte( msg );
	SHOWNET( msg, svc_strings[cmd] );
	if( cmd != svc_match )
		Com_Error( ERR_DROP, "CL_ParseFrame: not match info" );
	CL_ParseDeltaMatchState( msg, deltaframe, newframe );

	// read playerinfos
	numplayers = 0;
	while( ( cmd = MSG_ReadByte( msg ) ) )
	{
		SHOWNET( msg, svc_strings[cmd] );
		if( cmd != svc_playerinfo )
			Com_Error( ERR_DROP, "CL_ParseFrame: not playerinfo" );
		if( deltaframe && deltaframe->numplayers >= numplayers )
			CL_ParsePlayerstate( msg, &deltaframe->playerStates[numplayers], &newframe->playerStates[numplayers] );
		else
			CL_ParsePlayerstate( msg, NULL, &newframe->playerStates[numplayers] );
		numplayers++;
	}
	newframe->numplayers = numplayers;
	newframe->playerState = newframe->playerStates[0];

	// read packet entities
	cmd = MSG_ReadByte( msg );
	SHOWNET( msg, svc_strings[cmd] );
	if( cmd != svc_packetentities )
		Com_Error( ERR_DROP, "CL_ParseFrame: not packetentities" );
	CL_ParsePacketEntities( msg, deltaframe, newframe );

	while( ( cmd = MSG_ReadByte( msg ) ) )
	{
		SHOWNET( msg, svc_strings[cmd] );
		if( cmd != svc_sound )
			Com_Error( ERR_DROP, "CL_ParseFrame: not sound" );
		CL_ParseStartSoundPacket( newframe, msg );
	}

	return newframe;
}

//================
//CL_ParseFrame
//================
void CL_ParseFrame( msg_t *msg )
{
	frame_t *snap;

	snap = CL_ParseFrameSnap( msg );
	if( snap->valid )
	{
		cl.receivedSnapNum = snap->serverFrame;

		if( !cls.demoplaying )
		{
			// the first snap, fill all the timedeltas with the same value
			if( cl.currentSnapNum <= 0 || ( cl.serverTime + 250 < snap->serverTime ) )
			{
				int i;
				cl.serverTimeDelta = cl.newServerTimeDelta = snap->serverTime - cls.gametime - 1;
				for( i = 0; i < MAX_TIMEDELTAS_BACKUP; i++ )
				{
					cl.serverTimeDeltas[i] = cl.newServerTimeDelta;
				}
			}
			else
			{
				cl.serverTimeDeltas[cl.receivedSnapNum & MASK_TIMEDELTAS_BACKUP] = cl.frames[cl.currentSnapNum & UPDATE_MASK].serverTime - cls.gametime;
			}
		}
	}
}

//================
//CL_ParseFrameServerTime
//================
unsigned int CL_ParseFrameServerTime( msg_t *msg )
{
	unsigned int len;
	unsigned int serverTime;

	assert( cls.demoplaying );

	// get total length
	len = MSG_ReadShort( msg );

	// get the snapshot id
	serverTime = (unsigned)MSG_ReadLong( msg );

	MSG_SkipData( msg, len - 4 );

	return serverTime;
}
