/*
   Copyright (C) 2002-2003 Victor Luchits

   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 "cg_local.h"

static void CG_UpdateEntities( void );

/*
   ==================
   CG_FixVolumeCvars
   Don't let the user go too far away with volumes
   ==================
 */
static void CG_FixVolumeCvars( void )
{
	if( developer->integer )
		return;

	if( cg_volume_players->value < 0.0f )
		trap_Cvar_SetValue( "cg_volume_players", 0.0f );
	else if( cg_volume_players->value > 2.0f )
		trap_Cvar_SetValue( "cg_volume_players", 2.0f );

	if( cg_volume_effects->value < 0.0f )
		trap_Cvar_SetValue( "cg_volume_effects", 0.0f );
	else if( cg_volume_effects->value > 2.0f )
		trap_Cvar_SetValue( "cg_volume_effects", 2.0f );

	if( cg_volume_announcer->value < 0.0f )
		trap_Cvar_SetValue( "cg_volume_announcer", 0.0f );
	else if( cg_volume_announcer->value > 2.0f )
		trap_Cvar_SetValue( "cg_volume_announcer", 2.0f );

	if( cg_volume_voicechats->value < 0.0f )
		trap_Cvar_SetValue( "cg_volume_voicechats", 0.0f );
	else if( cg_volume_voicechats->value > 2.0f )
		trap_Cvar_SetValue( "cg_volume_voicechats", 2.0f );

	if( cg_volume_hitsound->value < 0.0f )
		trap_Cvar_SetValue( "cg_volume_hitsound", 0.0f );
	else if( cg_volume_hitsound->value > 10.0f )
		trap_Cvar_SetValue( "cg_volume_hitsound", 10.0f );
}

/*
   ==================
   CG_FireEvents
   ==================
 */
static void CG_FireEvents( void )
{
	int pnum, j;
	entity_state_t *state;

	for( pnum = 0; pnum < cg.frame.numEntities; pnum++ )
	{
		state = &cg.frame.parsedEntities[pnum&( MAX_PARSE_ENTITIES-1 )];
		for( j = 0; j < 2; j++ )
		{
			CG_EntityEvent( state, state->events[j], state->eventParms[j], qfalse );
		}
	}
}

//==================
//CG_NewPacketEntityState
//==================
static void CG_NewPacketEntityState( entity_state_t *state )
{
	centity_t *cent;
	float dist;

	cent = &cg_entities[state->number];

	if( state->linearProjectile )
	{
		unsigned int serverTime = cg.time + cgs.baseServerTime;

		if( cg.frame.match.paused )
			serverTime = cg.frame.serverTime  + cgs.baseServerTime;

		serverTime += state->modelindex2; // add a time offset to counter antilag visualization

		// linear projectiles do not have a real origin, it is extrapolated from static data
		if( serverTime > state->linearProjectileTimeStamp )
		{
			float flyTime = (float)( serverTime - state->linearProjectileTimeStamp ) * 0.001f;
			VectorMA( state->origin2, flyTime, state->linearProjectileVelocity, state->origin );
		}
		else
			VectorCopy( state->origin2, state->origin );

		VectorCopy( state->linearProjectileVelocity, cent->velocity );

		if( ( cent->serverFrame != cg.oldFrame.serverFrame ) || state->teleported )
			cent->prev = *state;
		else
			cent->prev = cent->current;
	}
	else
	{
		// some data changes will force no lerping
		VectorSubtract( state->origin, cent->current.origin, cent->velocity );
		dist = VectorNormalize( cent->velocity );
		if( ( state->modelindex != cent->current.modelindex ) || ( dist > 512 ) || state->teleported )
		{
			cent->serverFrame = -99;
			dist = 0;
		}

		if( dist > 0.0f )
			VectorScale( cent->velocity, dist/(float)( cg.frame.serverTime - cg.oldFrame.serverTime ), cent->velocity );
		else
			VectorClear( cent->velocity );

		if( cent->serverFrame != cg.oldFrame.serverFrame )
		{ // wasn't in last update, so initialize some things
			// duplicate the current state so lerping doesn't hurt anything
			cent->prev = *state;

			// fixme : jal : Ok, there's a bug with state->origin2 in occasions,
			// but having 0.4 behind the door is a mess to track, so use the secure
			// way. It won't be noticable in 99.9% of cases since this is happening behind
			// a wall. Proper fix needed after 0.4 release.

			//if( state->teleported )
			//{
			//	VectorCopy( state->origin, cent->prev.origin );
			//}
			//else
			//{
			//	VectorCopy( state->origin2, cent->prev.origin );
			//}

			VectorClear( cent->velocity );

			// Init the animation when new into PVS
			if( cg.frame.valid && ( state->type == ET_PLAYER || state->type == ET_CORPSE ) )
			{
				CG_ClearEventAnimations( state->number );
				cent->lastAnims = 0;
				memset( cent->lastVelocities, 0, sizeof( cent->lastVelocities ) );

				if( state->type == ET_CORPSE )
				{                // get the old animation from the owner
					cg_entPModels[state->number].anim = cg_entPModels[state->bodyOwner].anim;
					cg_entPModels[state->number].anim.nextframetime = cg.time;
				}
				CG_AddPModelAnimation( state->number, ( state->frame )&0x3F, ( state->frame>>6 )&0x3F, ( state->frame>>12 )&0xF, BASIC_CHANNEL );

				// if it's a player and new in PVS, remove the old power time
				// This is way far from being the right thing. But will make it less bad for now
				cg_entPModels[state->number].pweapon.flashtime = cg.time;
				cg_entPModels[state->number].pweapon.barreltime = cg.time;
				cg_entPModels[state->number].pweapon.reloadedtime = cg.time;
			}
		}
		else
		{ // shuffle the last state to previous
			cent->prev = cent->current;
		}
	}

	VectorCopy( state->origin, cent->trailOrigin );

	cent->serverFrame = cg.frame.serverFrame;
	cent->current = *state;
}

int CG_LostMultiviewPOV( void )
{
	int best, value, i, curPlayer;
	best = MAX_CLIENTS + 1;
	curPlayer = -1;
	for( i = 0; i < cg.frame.numplayers; i++ )
	{
		value = abs( (int)cg.frame.playerStates[i].POVnum - (int)cg.multiviewPOVnum );
		if( value == best && i > curPlayer )
			continue;
		if( value < best )
		{
			best = value;
			curPlayer = i;
		}
	}

	return curPlayer;
}

static void CG_SetFramePlayerState( frame_t *frame, int pov )
{
	frame->playerState = frame->playerStates[pov];
	if( ( cgs.demoPlaying || cg.frame.multipov ) )
	{
		frame->playerState.pmove.pm_flags |= PMF_NO_PREDICTION;
		if( frame->playerState.pmove.pm_type != PM_SPECTATOR )
		{
			frame->playerState.pmove.pm_type = PM_CHASECAM;
			frame->playerState.stats[STAT_CHASING] = cg.frame.playerState.POVnum;
		}
	}

	if( ( cgs.tv || frame->playerState.POVnum != cgs.playerNum + 1 ) &&
		!(frame->playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_ZOOM) )
	{
		frame->playerState.fov = cg_fov->integer;
		clamp( frame->playerState.fov, 60, 160 );
	}

	if( cgs.tv )
	{	// FIXME?
		if( frame->playerState.POVnum != cgs.playerNum + 1 )
			frame->playerState.stats[STAT_CHASING] = frame->playerState.POVnum;
		frame->playerState.stats[STAT_REALTEAM] = TEAM_SPECTATOR;
	}
}

static void CG_UpdatePlayerState( void )
{
	int POVnum = -1, i, num;
	int curPlayer = 0;

	// just one POV
	if( !cg.frame.multipov )
	{
		POVnum = cg.frame.playerStates[0].POVnum;
	}
	else
	{
		// find the playerState containing our current POV, then cycle playerStates
		curPlayer = -1;
		for( i = 0; i < cg.frame.numplayers; i++ )
		{
			if( cg.frame.playerStates[i].POVnum > 0 && cg.frame.playerStates[i].POVnum == cg.multiviewPOVnum )
			{
				curPlayer = i;
				break;
			}
		}

		// the POV was lost, find the closer one (may go up or down, but who cares)
		if( curPlayer == -1 )
			curPlayer = CG_LostMultiviewPOV();

		POVnum = cg.frame.playerStates[curPlayer].POVnum;
	}

	cg.multiviewPOVnum = POVnum;

	// set up the playerstates

	// current
	num = curPlayer;
	CG_SetFramePlayerState( &cg.frame, num );

	// old
	num = -1;
	for( i = 0; i < cg.oldFrame.numplayers; i++ )
	{
		if( (int) cg.oldFrame.playerStates[i].POVnum == POVnum )
		{
			num = i;
			break;
		}
	}

	// use the current one for old frame too, if correct POV wasn't found
	if( num == -1 )
		cg.oldFrame.playerState = cg.frame.playerState;
	else
		CG_SetFramePlayerState( &cg.oldFrame, num );

	if( cg.frame.playerState.pmove.pm_type == PM_SPECTATOR || // force clear if spectator
	    cg.frame.playerState.pmove.pm_type == PM_CHASECAM )
	{
		if( !( cg.oldFrame.playerState.pmove.pm_type == PM_SPECTATOR || cg.oldFrame.playerState.pmove.pm_type == PM_CHASECAM )
			|| cg.oldFrame.serverFrame == cg.frame.serverFrame )
			trap_Cvar_ForceSet( "scr_forceclear", "1" );
	}
	else
	{
		if( ( cg.oldFrame.playerState.pmove.pm_type == PM_SPECTATOR || cg.oldFrame.playerState.pmove.pm_type == PM_CHASECAM )
			|| cg.oldFrame.serverFrame == cg.frame.serverFrame )
			trap_Cvar_ForceSet( "scr_forceclear", "0" );
	}
}

//==================
//CG_NewFrameSnap
// a new frame snap has been received from the server
//==================
void CG_NewFrameSnap( frame_t *frame, frame_t *lerpframe )
{
	int i;

	assert( frame );

	if( lerpframe )
		cg.oldFrame = *lerpframe;
	else
		cg.oldFrame = *frame;

	cg.frame = *frame;

	cg.portalInView = qfalse;

	for( i = 0; i < frame->numEntities; i++ )
		CG_NewPacketEntityState( &frame->parsedEntities[i & ( MAX_PARSE_ENTITIES-1 )] );

	if( lerpframe && ( memcmp( cg.oldFrame.areabits, cg.frame.areabits, sizeof( cg.frame.areabits ) ) == 0 ) )
		cg.oldAreabits = qtrue;
	else
		cg.oldAreabits = qfalse;

	CG_UpdatePlayerState();

	// request TV-channels if we haven't done so already
	if( !cgs.tvRequested )
	{
		if( !cgs.configStrings[CS_MODELS+1][0] )
		{
			trap_Cmd_ExecuteText( EXEC_APPEND, "cmd channels\n" );
			trap_Cmd_ExecuteText( EXEC_APPEND, "menu_tv\n" ); // bring up the channels menu if in lobby
		}
		cgs.tvRequested = qtrue;
	}

	// a new server frame begins now
	CG_FixVolumeCvars();

	CG_BuildSolidList();
	CG_UpdateEntities();
	CG_FireEvents();
	CG_CheckWeaponState();
	CG_FirePlayerStateEvents();
	CG_CheckPredictionError();

	for( i = 0; i < cg.frame.numsounds; i++ )
		CG_GlobalSound( &cg.frame.sounds[i] );

	for( i = 0; i < cg.frame.numgamecommands; i++ )
	{
		if( cg.frame.gamecommands[i].all || cg.frame.gamecommands[i].targets[cg.frame.playerState.POVnum-1] )
			CG_GameCommand( cg.frame.gamecommandsData + cg.frame.gamecommands[i].commandOffset );
	}
}


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


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

   ADD INTERPOLATED ENTITIES TO RENDERING LIST

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

//===============
//CG_EntAddBobEffect
//===============
static void CG_EntAddBobEffect( centity_t *cent )
{
	static float scale;
	static float bob;

	scale = 0.005f + cent->current.number * 0.00001f;
	bob = 4 + cos( ( cg.time + 1000 ) * scale ) * 4;
	cent->ent.origin2[2] += bob;
	cent->ent.origin[2] += bob;
	cent->ent.lightingOrigin[2] += bob;
}

//=================
//CG_CModelForEntity
// get the collision model for the given entity, no matter if box or brush-model.
//=================
struct cmodel_s *CG_CModelForEntity( int entNum )
{
	int x, zd, zu;
	centity_t *cent;
	struct cmodel_s	*cmodel;
	vec3_t bmins, bmaxs;

	if( entNum < 0 || entNum >= MAX_EDICTS )
		return NULL;

	cent = &cg_entities[entNum];

	if( cent->serverFrame != cg.frame.serverFrame )  // not present in current frame
		return NULL;

	// find the cmodel
	if( cent->current.solid == SOLID_BMODEL )
	{                                       // special value for bmodel
		cmodel = trap_CM_InlineModel( cent->current.modelindex );
	}
	else
	{                               // encoded bbox
		x = 8 * ( cent->current.solid & 31 );
		zd = 8 * ( ( cent->current.solid>>5 ) & 31 );
		zu = 8 * ( ( cent->current.solid>>10 ) & 63 ) - 32;

		bmins[0] = bmins[1] = -x;
		bmaxs[0] = bmaxs[1] = x;
		bmins[2] = -zd;
		bmaxs[2] = zu;
		cmodel = trap_CM_ModelForBBox( bmins, bmaxs );
	}

	return cmodel;
}

//=================
//CG_DrawEntityBox
// draw the bounding box (in brush models case the box containing the model)
//=================
void CG_DrawEntityBox( centity_t *cent )
{
#ifndef WSW_RELEASE
	struct cmodel_s *cmodel;
	vec3_t mins, maxs;

	if( cent->ent.renderfx & RF_VIEWERMODEL )
		return;

	cmodel = CG_CModelForEntity( cent->current.number );
	if( cmodel )
	{
		trap_CM_InlineModelBounds( cmodel, mins, maxs );
		if( cg_drawEntityBoxes->integer < 2 && cent->current.solid == SOLID_BMODEL )
			return;

		// push triggers don't move so aren't interpolated
		if( cent->current.type == ET_PUSH_TRIGGER )
			CG_DrawTestBox( cent->current.origin, mins, maxs, vec3_origin );
		else
		{
			vec3_t origin;
			VectorLerp( cent->prev.origin, cg.lerpfrac, cent->current.origin, origin );
			CG_DrawTestBox( origin, mins, maxs, vec3_origin );
		}
	}
#endif
}

//==========================================================================
//		ET_GENERIC
//==========================================================================

//===============
//CG_UpdateGenericEnt
//===============
static void CG_UpdateGenericEnt( centity_t *cent )
{
	// start from clean
	memset( &cent->ent, 0, sizeof( cent->ent ) );
	cent->ent.scale = 1.0f;
	cent->ent.renderfx = cent->renderfx;

	// make all of white color by default
	Vector4Set( cent->ent.shaderRGBA, 255, 255, 255, 255 );
	if( cent->effects & EF_OUTLINE )
		Vector4Set( cent->outlineColor, 0, 0, 0, 255 );

	// set frame
	cent->ent.frame = cent->current.frame;
	cent->ent.oldframe = cent->prev.frame;

	// set up the model
	cent->ent.rtype = RT_MODEL;
	if( cent->current.solid == SOLID_BMODEL )
	{
		cent->ent.model = cgs.inlineModelDraw[cent->current.modelindex];
	}
	else
	{
#ifdef QUAKE2_JUNK
		cent->ent.skinNum = cent->current.skinnum;
#endif
		cent->ent.model = cgs.modelDraw[cent->current.modelindex];
	}
	cent->skel = CG_SkeletonForModel( cent->ent.model );

	if( cent->current.linearProjectile )
		return;

	// copy, not interpolated, starting positions (oldframe ones)
	cent->ent.backlerp = 1.0f;
	VectorCopy( cent->prev.origin, cent->ent.origin );
	VectorCopy( cent->prev.origin, cent->ent.origin2 );
	VectorCopy( cent->prev.origin, cent->ent.lightingOrigin );
	if( cent->prev.angles[0] || cent->prev.angles[1] || cent->prev.angles[2] )
		AnglesToAxis( cent->prev.angles, cent->ent.axis );
	else
		Matrix_Copy( axis_identity, cent->ent.axis );
}

//===============
//CG_ExtrapolateLinearProjectile
//===============
void CG_ExtrapolateLinearProjectile( centity_t *cent )
{
	entity_state_t *state = &cent->current;
	unsigned int serverTime = cg.time + cgs.baseServerTime;
	int i;
	vec3_t angles;

	if( cg.frame.match.paused )
		serverTime = cg.frame.serverTime  + cgs.baseServerTime;

	serverTime += state->modelindex2; // add a time offset to counter antilag visualization

	cent->ent.backlerp = 1.0f;

	// linear projectiles do not have a real origin, it is extrapolated from static data
	if( serverTime > state->linearProjectileTimeStamp )
	{
		float flyTime = (float)( serverTime - state->linearProjectileTimeStamp ) * 0.001f;
		VectorMA( state->origin2, flyTime, state->linearProjectileVelocity, state->origin );
	}
	else
		VectorCopy( state->origin2, state->origin );

	for( i = 0; i < 3; i++ )
		cent->ent.origin[i] = cent->ent.origin2[i] = cent->ent.lightingOrigin[i] = state->origin[i];

	// interpolate angles
	for( i = 0; i < 3; i++ )
		angles[i] = LerpAngle( cent->prev.angles[i], cent->current.angles[i], cg.lerpfrac );

	if( angles[0] || angles[1] || angles[2] )
		AnglesToAxis( angles, cent->ent.axis );
	else
		Matrix_Copy( axis_identity, cent->ent.axis );
}

//===============
//CG_LerpGenericEnt
//===============
void CG_LerpGenericEnt( centity_t *cent )
{
	int i;
	vec3_t ent_angles = { 0, 0, 0 };

	cent->ent.backlerp = 1.0f - cg.lerpfrac;

	// interpolate angles
	for( i = 0; i < 3; i++ )
		ent_angles[i] = LerpAngle( cent->prev.angles[i], cent->current.angles[i], cg.lerpfrac );

	if( ent_angles[0] || ent_angles[1] || ent_angles[2] )
		AnglesToAxis( ent_angles, cent->ent.axis );
	else
		Matrix_Copy( axis_identity, cent->ent.axis );

	if( cent->renderfx & RF_FRAMELERP )
	{
		// step origin discretely, because the frames
		// do the animation properly
		vec3_t delta, move;

		VectorSubtract( cent->current.old_origin, cent->current.origin, move );
		Matrix_TransformVector( cent->ent.axis, move, delta );
		VectorMA( cent->current.origin, cent->ent.backlerp, delta, cent->ent.origin );
	}
	else
	{
		// interpolate origin
		for( i = 0; i < 3; i++ )
			cent->ent.origin[i] = cent->ent.origin2[i] = cent->prev.origin[i] + cg.lerpfrac *
			                                             ( cent->current.origin[i] - cent->prev.origin[i] );
	}

	VectorCopy( cent->ent.origin, cent->ent.lightingOrigin );
}

//===============
//CG_AddGenericEnt
//===============
static void CG_AddGenericEnt( centity_t *cent )
{
	if( !cent->ent.scale )
		return;

	// if set to invisible, skip
	if( !cent->current.modelindex )
		return;

	// bobbing & auto-rotation
	if( cent->effects & EF_ROTATE_AND_BOB )
	{
		CG_EntAddBobEffect( cent );
		Matrix_Copy( cg.autorotateAxis, cent->ent.axis );
	}

	// render effects
	if( cent->renderfx & ( RF_COLORSHELL_GREEN | RF_COLORSHELL_RED | RF_COLORSHELL_BLUE ) )
	{
		cent->ent.renderfx = cent->renderfx & RF_MINLIGHT; // renderfx go on color shell entity
	}
	else
	{
		cent->ent.renderfx = cent->renderfx;
	}
	if( !( cent->ent.renderfx & RF_NOSHADOW ) )
		cent->ent.renderfx |= RF_PLANARSHADOW;

	if( cent->item )
	{
		cent->ent.renderfx |= cent->item->renderfx;

		if( cent->effects & EF_AMMOBOX )
		{
#ifdef WSW_RELEASE // Ugly hack for 0.4 release. Armor models are way too big
			if( cgs.gameProtocol == 10 )
				cent->ent.scale *= 0.90f; // UGLY HACK: fix the model sizes.
#endif

			// find out the ammo box color
			if( cent->item->color && strlen( cent->item->color ) > 1 )
			{
				vec4_t scolor;
				Vector4Copy( color_table[ColorIndex( cent->item->color[1] )], scolor );
				cent->ent.shaderRGBA[0] = ( qbyte )( 255 * scolor[0] );
				cent->ent.shaderRGBA[1] = ( qbyte )( 255 * scolor[1] );
				cent->ent.shaderRGBA[2] = ( qbyte )( 255 * scolor[2] );
				cent->ent.shaderRGBA[3] = ( qbyte )( 255 * scolor[3] );
			}
			else  // set white
				Vector4Set( cent->ent.shaderRGBA, 255, 255, 255, 255 );
		}

#ifdef CGAMEGETLIGHTORIGIN
		// add shadows for items (do it before offseting for weapons)
		if( !( cent->ent.renderfx & RF_NOSHADOW ) && ( cg_shadows->integer == 1 ) )
			CG_AllocShadeBox( cent->current.number, cent->ent.origin, item_box_mins, item_box_maxs, NULL );
#endif

		// offset weapon items by their special tag
		if( cent->item->type & IT_WEAPON )
		{
			cent->ent.renderfx |= RF_OCCLUSIONTEST;
			CG_PlaceModelOnTag( &cent->ent, &cent->ent, &cgs.weaponItemTag );
		}
	}

	// add to refresh list
	CG_SetBoneposesForCGEntity( cent );
	CG_AddCentityOutLineEffect( cent );
	CG_AddEntityToScene( &cent->ent );

	// shells generate a separate entity for the main model
	CG_AddColorShell( &cent->ent, cent->renderfx );

	cent->ent.customSkin = NULL;
	cent->ent.customShader = NULL;  // never use a custom skin on others
	Vector4Set( cent->ent.shaderRGBA, 255, 255, 255, 255 );

	// linear projectiles can never have extra model. Modelindex2 is used for a different purpose
	if( cent->current.linearProjectile )
		return;

	// duplicate for linked models
	if( cent->current.modelindex2 )
	{
		struct model_s *model1 = cent->ent.model;
		if( cent->item )
		{
			if( cent->item->type & IT_WEAPON )
			{                        // at this point ent still has the first modelindex model
				orientation_t tag;
				if( CG_GrabTag( &tag, &cent->ent, "tag_barrel" ) )
					CG_PlaceModelOnTag( &cent->ent, &cent->ent, &tag );
			}

			if( cent->effects & EF_AMMOBOX )  // special ammobox icon
				cent->ent.customShader = trap_R_RegisterPic( cent->item->icon );
		}

		cent->ent.model = cgs.modelDraw[cent->current.modelindex2];
		CG_AddEntityToScene( &cent->ent );
		//recover modelindex1 data.
		cent->ent.customShader = NULL;
		cent->ent.model = model1;
	}
}

//==========================================================================
//		ET_FLAG_BASE
//==========================================================================

//===============
//CG_AddFlagModelOnTag
//===============
void CG_AddFlagModelOnTag( centity_t *cent, int flag_team, char *tagname )
{
	static entity_t	flag;
	static vec4_t teamcolor;
	orientation_t tag;

	if( !( cent->effects & EF_ENEMY_FLAG ) )
		return;

	// fixme?: only 2 teams, alpha and beta, are considered
	if( cent->current.team != TEAM_BETA && cent->current.team != TEAM_ALPHA )
		return;

	CG_TeamColor( flag_team, teamcolor );

	memset( &flag, 0, sizeof( entity_t ) );
	flag.model = trap_R_RegisterModel( PATH_FLAG_MODEL );
	if( !flag.model )
		return;

	flag.rtype = RT_MODEL;
	flag.scale = 1.0f;
	flag.renderfx = cent->ent.renderfx & ~RF_PLANARSHADOW;
	flag.customShader = NULL;
	flag.customSkin = NULL;
	flag.shaderRGBA[0] = ( qbyte )( 255 * teamcolor[0] );
	flag.shaderRGBA[1] = ( qbyte )( 255 * teamcolor[1] );
	flag.shaderRGBA[2] = ( qbyte )( 255 * teamcolor[2] );
	flag.shaderRGBA[3] = ( qbyte )( 255 * teamcolor[3] );

	VectorCopy( cent->ent.origin, flag.origin );
	VectorCopy( cent->ent.origin, flag.origin2 );
	VectorCopy( cent->ent.lightingOrigin, flag.lightingOrigin );
	Matrix_Copy( cent->ent.axis, flag.axis );

	// place the flag on the tag if available
	if( tagname && CG_GrabTag( &tag, &cent->ent, tagname ) )
	{
		CG_PlaceModelOnTag( &flag, &cent->ent, &tag );
	}
	else
	{
		// Flag dropped
		cent->current.angles[0] = -75; // Let it stand up 75 degrees

		// quick & dirty client-side rotation animation, rotate once every 2 seconds
		if( !cent->fly_stoptime )
			cent->fly_stoptime = cg.time;

		cent->current.angles[1] = ( 360.0 * ( ( cent->fly_stoptime - cg.time ) % 2000 ) ) / 2000.0;
		AnglesToAxis( cent->current.angles, flag.axis );
		VectorMA( flag.origin, 16, flag.axis[0], flag.origin ); // Move the flag up a bit
	}

	CG_AddColoredOutLineEffect( &flag, EF_OUTLINE,
		(qbyte)( 255*( teamcolor[0]*0.3f ) ),
		(qbyte)( 255*( teamcolor[1]*0.3f ) ),
		(qbyte)( 255*( teamcolor[2]*0.3f ) ),
		255 );

	CG_AddEntityToScene( &flag );

	// add the light & energy effects
	if( CG_GrabTag( &tag, &flag, "tag_color" ) )
		CG_PlaceModelOnTag( &flag, &flag, &tag );

	// FIXME: convert this to an autosprite mesh in the flag model
	if( !( cent->ent.renderfx & RF_VIEWERMODEL ) )
	{
		flag.rtype = RT_SPRITE;
		flag.model = NULL;
		flag.renderfx = RF_NOSHADOW|RF_FULLBRIGHT;
		flag.frame = flag.oldframe = 0;
		flag.radius = 32.0f;
		flag.customShader = CG_MediaShader( cgs.media.shaderFlagFlare );

		CG_AddEntityToScene( &flag );
	}

	// if on a player, flag drops colored particles and lights up
	if( cent->current.type == ET_PLAYER )
	{
		CG_AddLightToScene( flag.origin, 350, teamcolor[0], teamcolor[1], teamcolor[2], NULL );

		if( cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] + FLAG_TRAIL_DROP_DELAY < cg.time )
		{
			cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] = cg.time;
			CG_FlagTrail( flag.origin, cent->trailOrigin, cent->ent.origin, teamcolor[0], teamcolor[1], teamcolor[2] );
		}
	}
}

//===============
//CG_UpdateFlagBaseEnt
//===============
static void CG_UpdateFlagBaseEnt( centity_t *cent )
{
	// start from clean

	memset( &cent->ent, 0, sizeof( cent->ent ) );
	if( cent->current.team <= TEAM_PLAYERS || cent->current.team >= GS_MAX_TEAMS )
	{
		Vector4Set( cent->ent.shaderRGBA, 255, 255, 255, 255 );
	}
	else
	{
		vec4_t teamcolor;
		CG_TeamColor( cent->current.team, teamcolor );
		cent->ent.shaderRGBA[0] = ( qbyte )( 255 * teamcolor[0] );
		cent->ent.shaderRGBA[1] = ( qbyte )( 255 * teamcolor[1] );
		cent->ent.shaderRGBA[2] = ( qbyte )( 255 * teamcolor[2] );
		cent->ent.shaderRGBA[3] = ( qbyte )( 255 * teamcolor[3] );
	}
	cent->ent.scale = 1.0f;
	cent->ent.renderfx = cent->renderfx;

	cent->item = GS_FindItemByTag( cent->current.skinnum );
	if( cent->item )
	{
		cent->effects |= cent->item->effects;
	}

	cent->ent.rtype = RT_MODEL;
	cent->ent.frame = cent->current.frame;
	cent->ent.oldframe = cent->prev.frame;

	// set up the model
	if( cent->current.solid == SOLID_BMODEL )
	{
		cent->ent.model = cgs.inlineModelDraw[cent->current.modelindex];
	}
	else
	{
		cent->ent.model = cgs.modelDraw[cent->current.modelindex];
	}
	cent->skel = CG_SkeletonForModel( cent->ent.model );

	if( cent->effects & EF_OUTLINE )
	{
		vec4_t teamcolor;
		CG_TeamColor( cent->current.team, teamcolor );
		CG_SetOutlineColor( cent->outlineColor, teamcolor );
	}

	// copy, not interpolated, starting positions (oldframe ones)
	cent->ent.backlerp = 1.0f;
	VectorCopy( cent->prev.origin, cent->ent.origin );
	VectorCopy( cent->prev.origin, cent->ent.origin2 );
	VectorCopy( cent->prev.origin, cent->ent.lightingOrigin );
	if( cent->prev.angles[0] || cent->prev.angles[1] || cent->prev.angles[2] )
		AnglesToAxis( cent->prev.angles, cent->ent.axis );
	else
		Matrix_Copy( axis_identity, cent->ent.axis );
}

//===============
//CG_AddFlagBaseEnt
//===============
static void CG_AddFlagBaseEnt( centity_t *cent )
{
	if( !cent->ent.scale )
		return;

	// if set to invisible, skip
	if( !cent->current.modelindex )
		return;

	// bobbing & auto-rotation
	if( cent->effects & EF_ROTATE_AND_BOB )
	{
		CG_EntAddBobEffect( cent );
		Matrix_Copy( cg.autorotateAxis, cent->ent.axis );
	}

	// render effects
	if( cent->renderfx & ( RF_COLORSHELL_GREEN | RF_COLORSHELL_RED | RF_COLORSHELL_BLUE ) )
	{
		cent->ent.renderfx = cent->renderfx & RF_MINLIGHT; // renderfx go on color shell entity
	}
	else
	{
		cent->ent.renderfx = cent->renderfx;
	}
	cent->ent.renderfx |= RF_PLANARSHADOW;
	if( cent->item )
		cent->ent.renderfx |= cent->item->renderfx;


	// let's see: We add first the modelindex 1 (the base)

	// add to refresh list
	CG_SetBoneposesForCGEntity( cent ); // skelmod
	CG_AddCentityOutLineEffect( cent );
	CG_AddEntityToScene( &cent->ent );          // skelmod

	//CG_DrawTestBox( cent->ent.origin, item_box_mins, item_box_maxs, vec3_origin );

	// shells generate a separate entity for the main model
	CG_AddColorShell( &cent->ent, cent->renderfx );

	cent->ent.customSkin = NULL;
	cent->ent.customShader = NULL;  // never use a custom skin on others

	// see if we have to add a flag
	if( cent->effects & EF_ENEMY_FLAG )
		CG_AddFlagModelOnTag( cent, cent->current.team, "tag_flag1" );
	else if( cent->current.modelindex2 <= 9 )
	{
		// add countdown value as a sprite
		if( ( cent->current.modelindex2 >= 0 ) && ( cent->current.modelindex2 <= 9 ) )
		{
			// we only draw from 9 down
			static entity_t number;
			number = cent->ent;
			Vector4Set( number.shaderRGBA, 255, 255, 255, 255 );
			number.rtype = RT_SPRITE;
			number.origin[2] += 24;
			number.origin2[2] += 24;
			number.model = NULL;
			number.radius = 12;
			number.customShader = CG_MediaShader( cgs.media.sbNums[cent->current.modelindex2] );
			CG_AddEntityToScene( &number );
		}
	}
}

//==========================================================================
//		ET_PLAYER
//==========================================================================

//===============
//CG_AddPlayerEnt
//ET_PLAYER entities can only draw as player models
//===============
static void CG_AddPlayerEnt( centity_t *cent )
{
	// render effects
	if( cent->renderfx & ( RF_COLORSHELL_GREEN | RF_COLORSHELL_RED | RF_COLORSHELL_BLUE ) )
		cent->ent.renderfx = RF_MINLIGHT; // renderfx go on color shell entity
	else
		cent->ent.renderfx = cent->renderfx | RF_MINLIGHT;
	cent->ent.renderfx |= RF_OCCLUSIONTEST;

	if( ISVIEWERENTITY( cent->current.number ) )
	{
		cg.effects = cent->effects;
		VectorCopy( cent->ent.lightingOrigin, cg.lightingOrigin );
		if( !cg.view.thirdperson && cent->current.modelindex )
			cent->ent.renderfx |= RF_VIEWERMODEL; // only draw from mirrors
		if( ( cent->ent.renderfx & RF_VIEWERMODEL ) && !cg_showSelfShadow->integer )
			cent->ent.renderfx |= RF_NOSHADOW;
	}

	// if set to invisible, skip
	if( !cent->current.modelindex || cent->current.team == TEAM_SPECTATOR )
		return;

	CG_AddPModel( cent );

	cent->ent.customSkin = NULL;
	cent->ent.customShader = NULL;  // never use a custom skin on others

#ifdef QUAKE2_JUNK
	cent->ent.skinNum = 0;
#endif
	cent->ent.renderfx = cent->ent.renderfx & RF_VIEWERMODEL;   // only draw from mirrors
	Vector4Set( cent->ent.shaderRGBA, 255, 255, 255, 255 );

	// corpses can never have a model in modelindex2
	if( cent->current.type == ET_CORPSE )
		return;

	// duplicate for linked models
	if( cent->current.modelindex2 )
	{
		cent->ent.model = cgs.modelDraw[cent->current.modelindex2];
		CG_AddEntityToScene( &cent->ent );

		CG_AddShellEffects( &cent->ent, cent->effects );
		CG_AddColorShell( &cent->ent, cent->renderfx );
	}
}



//==========================================================================
//		ET_ITEM
//==========================================================================

//===============
//CG_UpdateItemEnt
//===============
static void CG_UpdateItemEnt( centity_t *cent )
{
	memset( &cent->ent, 0, sizeof( cent->ent ) );
	Vector4Set( cent->ent.shaderRGBA, 255, 255, 255, 255 );

	cent->item = GS_FindItemByTag( cent->current.skinnum );
	if( !cent->item )
		return;

	cent->effects |= cent->item->effects;

	if( cg_simpleItems->integer && cent->item->simpleitem )
	{

		cent->ent.rtype = RT_SPRITE;
		cent->ent.model = NULL;
		cent->skel = NULL;
		cent->ent.renderfx = RF_NOSHADOW|RF_FULLBRIGHT;
		cent->ent.frame = cent->ent.oldframe = 0;

		cent->ent.radius = cg_simpleItemsSize->value <= 32 ? cg_simpleItemsSize->value : 32;
		if( cent->ent.radius < 1.0f )
			cent->ent.radius = 1.0f;

		if( cg_simpleItems->integer == 2 )
			cent->effects &= ~EF_ROTATE_AND_BOB;

		cent->ent.customShader = NULL;
		cent->ent.customShader = trap_R_RegisterPic( cent->item->simpleitem );

	}
	else
	{

		cent->ent.rtype = RT_MODEL;
		cent->ent.frame = cent->current.frame;
		cent->ent.oldframe = cent->prev.frame;

		if( cent->effects & EF_OUTLINE )
		{
			if( !cent->item->color || strlen( cent->item->color ) < 2 )
			{
				Vector4Set( cent->outlineColor, 0, 0, 0, 255 ); // black
			}
			else if( !cg_outlineItemsBlack->integer )
			{
				CG_SetOutlineColor( cent->outlineColor, color_table[ColorIndex( cent->item->color[1] )] );
			}
			else  // black
				Vector4Set( cent->outlineColor, 0, 0, 0, 255 );
		}

		// set up the model
		if( cent->current.solid == SOLID_BMODEL )
		{
			cent->ent.model = cgs.inlineModelDraw[cent->current.modelindex];
		}
		else
		{
			cent->ent.model = cgs.modelDraw[cent->current.modelindex];
		}
		cent->skel = CG_SkeletonForModel( cent->ent.model );
	}

	// copy, not interpolated, starting positions (oldframe ones)
	cent->ent.backlerp = 1.0f;
	VectorCopy( cent->prev.origin, cent->ent.origin );
	VectorCopy( cent->prev.origin, cent->ent.origin2 );
	VectorCopy( cent->prev.origin, cent->ent.lightingOrigin );
	if( cent->prev.angles[0] || cent->prev.angles[1] || cent->prev.angles[2] )
		AnglesToAxis( cent->prev.angles, cent->ent.axis );
	else
		Matrix_Copy( axis_identity, cent->ent.axis );
}

#ifndef WSW_RELEASE
//===============
//CG_AddRespawnIndicatorOnTag
//===============
static void CG_AddRespawnIndicatorForEnt( centity_t *cent )
{
	static entity_t	indicator;
	vec4_t color;

	Vector4Set( color, 1.0f, 1.0f, 1.0f, 1.0f );

	//TODO: make the color an effect...
	//if( !(cent->effects & EF_ENEMY_FLAG) )
	//	return;

	// bobbing & auto-rotation
	if( cent->effects & EF_ROTATE_AND_BOB )
	{
		CG_EntAddBobEffect( cent );
		Matrix_Copy( cg.autorotateAxis, cent->ent.axis );
	}

	memset( &indicator, 0, sizeof( entity_t ) );
	indicator.model = trap_R_RegisterModel( PATH_TESTPOD_MODEL );
	if( !indicator.model )
		return;

	indicator.rtype = RT_MODEL;
	indicator.scale = 1.0f;
	indicator.renderfx = cent->ent.renderfx | RF_NOSHADOW;
	indicator.shaderRGBA[0] = ( qbyte )( 255 * color[0] );
	indicator.shaderRGBA[1] = ( qbyte )( 255 * color[1] );
	indicator.shaderRGBA[2] = ( qbyte )( 255 * color[2] );
	indicator.shaderRGBA[3] = ( qbyte )( 255 * color[3] );

	// this should do...
	VectorCopy( cent->ent.origin, indicator.origin );
	VectorCopy( cent->ent.origin, indicator.origin2 );
	VectorCopy( cent->ent.lightingOrigin, indicator.lightingOrigin );
	Matrix_Copy( cent->ent.axis, indicator.axis );

	CG_AddEntityToScene( &indicator );
}
#endif

//===============
//CG_AddItemEnt
//===============
static void CG_AddItemEnt( centity_t *cent )
{
	int msec;

	if( !cent->item )
		return;

#if 0
	if( cent->effects & EF_RESPAWN_INDICATOR )
	{
		// add a respawn indicator
		CG_AddRespawnIndicatorForEnt( cent );
		return;
	}
#endif

	// respawning items
	if( cent->respawnTime )
		msec = cg.time - cent->respawnTime;
	else
		msec = ITEM_RESPAWN_TIME;

	if( msec >= 0 && msec < ITEM_RESPAWN_TIME )
		cent->ent.scale = (float)msec / ITEM_RESPAWN_TIME;
	else
		cent->ent.scale = 1.0f;

	if( cent->ent.rtype != RT_SPRITE )
	{
		// weapons are special
		if( cent->item && cent->item->type & IT_WEAPON )
			cent->ent.scale *= 1.40f;

#ifdef WSW_RELEASE // Ugly hack for 0.4 release. Armor models are way too big
		if( cent->item && cgs.gameProtocol == 10 )
		{
			if( cent->item->type & IT_ARMOR )
				cent->ent.scale *= 0.85f;
			if( cent->item->tag == HEALTH_SMALL )
				cent->ent.scale *= 0.85f;
		}
#endif

		//flags are special
		if( cent->effects & EF_ENEMY_FLAG )
		{
			CG_AddFlagModelOnTag( cent, cent->current.team, NULL );
			return;
		}
		CG_AddGenericEnt( cent );
		return;
	}

	//offset the item origin up
	cent->ent.origin[2] += cent->ent.radius + 2;
	cent->ent.origin2[2] += cent->ent.radius + 2;
	if( cent->effects & EF_ROTATE_AND_BOB )
		CG_EntAddBobEffect( cent );

	Matrix_Identity( cent->ent.axis );
	CG_AddEntityToScene( &cent->ent );
}

//==========================================================================
//		ET_BEAM
//==========================================================================

//===============
//CG_AddBeamEnt
//===============
static void CG_AddBeamEnt( centity_t *cent )
{
	CG_QuickPolyBeam( cent->current.origin, cent->current.origin2, cent->current.frame * 0.5f, CG_MediaShader( cgs.media.shaderLaser ) ); // wsw : jalfixme: missing the color (comes inside cent->current.colorRGBA)
}

//==========================================================================
//		ET_LASERBEAM
//==========================================================================

//===============
//CG_UpdateLaserbeamEnt - lasegun's continuous beam
//===============
static void CG_UpdateLaserbeamEnt( centity_t *cent )
{
	memset( &cent->ent, 0, sizeof( cent->ent ) );
	Vector4Set( cent->ent.shaderRGBA, 255, 255, 255, 255 );
	cent->ent.model = NULL;
}

//===============
//CG_LerpLaserbeamEnt - lasegun's continuous beam
//===============
static void CG_LerpLaserbeamEnt( centity_t *cent )
{
}

//===============
//CG_AddLaserbeamEnt - lasegun's continuous beam
//===============
static void CG_AddLaserbeamEnt( centity_t *cent )
{
	int i;
	centity_t *owner;
	orientation_t projectsource;
	vec3_t dir;
	vec3_t viewoffset;
	vec4_t color;

	VectorSet( viewoffset, 0, 0, cg.interpolatedPlayerState.viewheight );

	// was player updated this frame?
	owner = &cg_entities[cent->current.ownerNum];

	if( owner->current.team == TEAM_SPECTATOR || !owner->current.solid )
		return;

	if( ISVIEWERENTITY( cent->current.ownerNum ) && !cg.view.thirdperson && ( owner->serverFrame == cg.frame.serverFrame ) )
	{
		vec3_t fireorg, end, front;
		trace_t	trace;

		if( cg_predict->integer && cg_predictLaserBeam->value )
		{
			vec3_t front_refdef;
			if( cg_predictLaserBeam->value < 0 || cg_predictLaserBeam->value > 1 )
				trap_Cvar_Set( "cg_predictLaserBeam", "1" );
			for( i = 0; i < 3; i++ )
			{
				fireorg[i] = cg_predictLaserBeam->value * cg.view.refdef.vieworg[i] +
				             ( 1 - cg_predictLaserBeam->value ) * ( cg.interpolatedPlayerState.pmove.origin[i] + viewoffset[i] );
			}
			AngleVectors( cg.view.refdef.viewangles, front_refdef, NULL, NULL );
			AngleVectors( cg.interpolatedPlayerState.viewangles, front, NULL, NULL );
			for( i = 0; i < 3; i++ )
			{
				front[i] = cg_predictLaserBeam->value * front_refdef[i] + ( 1 - cg_predictLaserBeam->value ) * front[i];
			}
		}
		else
		{
			// use only playerstate values
			VectorAdd( cg.interpolatedPlayerState.pmove.origin, viewoffset, fireorg );
			AngleVectors( cg.interpolatedPlayerState.viewangles, front, NULL, NULL );
		}
		VectorNormalizeFast( front );
		VectorMA( fireorg, cent->current.range, front, end );
		CG_Trace( &trace, fireorg, vec3_origin, vec3_origin, end, cg.view.POVent, MASK_SHOT );
		VectorCopy( trace.endpos, cent->ent.origin );

		// for the actual drawing, we use weapon projection source if available
		if( !CG_PModel_GetProjectionSource( cent->current.ownerNum, &projectsource ) )
			VectorCopy( fireorg, projectsource.origin );

	}
	else
	{
		// interpolate both origins from old states
		for( i = 0; i < 3; i++ )
		{
			cent->ent.origin[i] = cent->prev.origin[i] + cg.lerpfrac *
			                      ( cent->current.origin[i] - cent->prev.origin[i] );
		}
		// for the actual drawing, we use weapon projection source if available
		if( !CG_PModel_GetProjectionSource( cent->current.ownerNum, &projectsource ) )
		{
			for( i = 0; i < 3; i++ )
			{
				projectsource.origin[i] = cent->prev.origin2[i] + cg.lerpfrac *
				                          ( cent->current.origin2[i] - cent->prev.origin2[i] );
			}
		}
	}

	// find dir from final beam for particle effects
	VectorSubtract( projectsource.origin, cent->ent.origin, dir );
	VectorNormalizeFast( dir );

	if( DistanceFast( cent->current.origin, cent->current.origin2 ) < ( cent->current.range-1 ) )
	{
		CG_NewElectroBeamPuff( cent, cent->ent.origin, dir );
		//light on impact
		CG_AddLightToScene( cent->ent.origin, 100, 1.0f, 1.0f, 0.5f, NULL );
	}

	if( !( CG_PointContents( projectsource.origin ) & MASK_SOLID ) )
	{
		CG_LaserGunPolyBeam( projectsource.origin, cent->ent.origin, CG_TeamBeamColor( owner->current.team, cent->current.ownerNum, color ) );
	}

	//enable continuous flash on the weapon
	if( cg_weaponFlashes->integer )
	{
		pmodel_t *pmodel = &cg_entPModels[cent->current.ownerNum];
		pmodel->pweapon.flashtime = cg.time + cgs.snapFrameTime;
		if( ISVIEWERENTITY( cent->current.ownerNum ) )
			vweap.pweapon.flashtime = cg.time + cg.frameTime;
	}

	//light on weapon
	CG_AddLightToScene( projectsource.origin, 150, 1.0f, 1.0f, 0.5f, NULL );

	if( cent->current.sound )
	{
		if( ISVIEWERENTITY( owner->current.number ) )
		{
			trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], owner->current.number,
			                     cg_volume_effects->value, ATTN_NONE );
		}
		else
		{
			trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], owner->current.number,
			                     cg_volume_effects->value, ATTN_LOOP );
		}
	}
}


//===============
//CG_AddCurveLaserbeamEnt - lasegun's continuous curved beam
//===============
static void CG_AddCurveLaserbeamEnt( centity_t *cent )
{
	centity_t *owner;
	orientation_t projectsource;
	int i;
	vec3_t viewangles;
	vec3_t fireorg, front, dir, hit, hitdir;
	qboolean hitwall;
	vec4_t color;
	vec_t *teamcolor;
	vec3_t viewoffset;

	VectorSet( viewoffset, 0, 0, cg.frame.playerState.viewheight );

	// was player updated this frame?
	owner = &cg_entities[cent->current.ownerNum];
	if( owner->current.team == TEAM_SPECTATOR || !owner->current.solid )
		return;

	teamcolor = CG_TeamBeamColor( owner->current.team, cent->current.ownerNum, color );

	if( ISVIEWERENTITY( cent->current.ownerNum ) && !cg.view.thirdperson && ( owner->serverFrame == cg.frame.serverFrame ) )
	{
		if( cg_predict->integer && cg_predictLaserBeam->value )
		{
			vec3_t front_refdef;
			if( cg_predictLaserBeam->value < 0 || cg_predictLaserBeam->value > 1 )
				trap_Cvar_Set( "cg_predictLaserBeam", "1" );

			for( i = 0; i < 3; i++ )
			{
				fireorg[i] = cg_predictLaserBeam->value * cg.view.refdef.vieworg[i] +
				             ( 1 - cg_predictLaserBeam->value ) * ( cg.frame.playerState.pmove.origin[i] + viewoffset[i] );
			}
			AngleVectors( cg.view.refdef.viewangles, front_refdef, NULL, NULL );
			AngleVectors( cg.frame.playerState.viewangles, front, NULL, NULL );
			for( i = 0; i < 3; i++ )
			{
				viewangles[i] = cg.view.refdef.viewangles[i];
			}
		}
		else
		{
			// use playerstate values
			VectorAdd( cg.frame.playerState.pmove.origin, viewoffset, fireorg );
			VectorCopy( cg.frame.playerState.viewangles, viewangles );
		}

		// for the actual drawing, we use weapon projection source if available
		if( !CG_PModel_GetProjectionSource( cent->current.ownerNum, &projectsource ) )
			VectorCopy( fireorg, projectsource.origin );

	}
	else
	{

		// for the actual drawing, we use weapon projection source if available
		if( !CG_PModel_GetProjectionSource( cent->current.ownerNum, &projectsource ) )
		{
			for( i = 0; i < 3; i++ )
			{
				projectsource.origin[i] = cent->prev.origin[i] + cg.lerpfrac *
				                          ( cent->current.origin[i] - cent->prev.origin[i] );
			}
		}

		// use the angles value in the entity
		for( i = 0; i < 3; i++ )
			viewangles[i] = LerpAngle( cent->prev.angles[i], cent->current.angles[i], cg.lerpfrac );
	}

	AngleVectors( viewangles, front, NULL, NULL );
	VectorNormalizeFast( front );

	// interpolate both origins from old states
	for( i = 0; i < 3; i++ )
	{
		cent->ent.origin[i] = cent->prev.origin2[i] + cg.lerpfrac *
		                      ( cent->current.origin2[i] - cent->prev.origin2[i] );
	}

	// find dir from final beam for particle effects
	VectorSubtract( cent->ent.origin, projectsource.origin, dir );
	VectorNormalizeFast( dir );

	// ok, we have a final start and end points, find out the curve
	{
		int j;
		float frac;
		trace_t	trace;
		vec3_t impactangles, tmpangles;
		vec3_t segmentStart, segmentEnd;

		//VectorSubtract( end, start, dir );
		VecToAngles( dir, impactangles );

		if( cg_laserBeamSubdivisions->integer < 3 )
			trap_Cvar_SetValue( "cg_laserBeamSubdivisions", 3 );

		hitwall = qfalse;

		VectorCopy( projectsource.origin, segmentStart );
		for( i = 1; i <= cg_laserBeamSubdivisions->integer; i++ )
		{
			frac = ( ( (float)cent->current.range/cg_laserBeamSubdivisions->value )*(float)i ) /
			       (float)cent->current.range;
			for( j = 0; j < 3; j++ ) tmpangles[j] = LerpAngle( viewangles[j], impactangles[j], frac );
			AngleVectors( tmpangles, dir, NULL, NULL );
			VectorMA( projectsource.origin, cent->current.range*frac, dir, segmentEnd );

			CG_Trace( &trace, segmentStart, vec3_origin, vec3_origin, segmentEnd, cent->current.number, MASK_SOLID );
			if( trace.fraction < 1.0 )
			{
				VectorCopy( trace.endpos, segmentEnd );
			}

			//segment is ready here
			CG_LaserGunPolyBeam( segmentStart, segmentEnd, teamcolor );

			VectorCopy( segmentEnd, segmentStart );

			if( trace.fraction < 1.0 )
			{
				VectorSubtract( segmentEnd, segmentStart, hitdir );
				VectorNormalizeFast( hitdir );
				VectorCopy( segmentEnd, hit );
				hitwall = qtrue;
				break;
			}
		}
	}

	if( hitwall )
	{
		CG_NewElectroBeamPuff( cent, hit, hitdir );
		CG_AddLightToScene( hit, 100, 1.0f, 1.0f, 0.5f, NULL ); //light on impact
	}

	//enable continuous flash on the weapon
	if( cg_weaponFlashes->integer )
	{
		pmodel_t *pmodel = &cg_entPModels[cent->current.ownerNum];
		pmodel->pweapon.flashtime = cg.time + cgs.snapFrameTime;
		if( ISVIEWERENTITY( cent->current.ownerNum ) )
			vweap.pweapon.flashtime = cg.time + cg.frameTime;
	}

	//light on weapon
	CG_AddLightToScene( projectsource.origin, 150, 1.0f, 1.0f, 0.5f, NULL );

	if( cent->current.sound )
	{
		if( ISVIEWERENTITY( owner->current.number ) )
		{
			trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], owner->current.number,
			                     cg_volume_effects->value, ATTN_NONE );
		}
		else
		{
			trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], owner->current.number,
			                     cg_volume_effects->value, ATTN_LOOP );
		}
	}
}

//==========================================================================
//		ET_PORTALSURFACE
//==========================================================================

//===============
//CG_UpdatePortalSurfaceEnt
//===============
static void CG_UpdatePortalSurfaceEnt( centity_t *cent )
{
	// start from clean
	memset( &cent->ent, 0, sizeof( cent->ent ) );

	cent->ent.rtype = RT_PORTALSURFACE;
	Matrix_Identity( cent->ent.axis );
	VectorCopy( cent->current.origin, cent->ent.origin );
	VectorCopy( cent->current.origin2, cent->ent.origin2 );

	if( !VectorCompare( cent->ent.origin, cent->ent.origin2 ) )
	{
		cg.portalInView = qtrue;
		cent->ent.frame = cent->current.skinnum;
	}
}

//===============
//CG_AddPortalSurfaceEnt
//===============
static void CG_AddPortalSurfaceEnt( centity_t *cent )
{
	if( !VectorCompare( cent->ent.origin, cent->ent.origin2 ) )
	{                                                           // construct the view matrix for portal view
		if( cent->current.effects & EF_ROTATE_AND_BOB )
		{
			float phase = cent->current.frame / 256.0f;
			float speed = cent->current.modelindex2 ? cent->current.modelindex2 : 50;

			Matrix_Identity( cent->ent.axis );
			Matrix_Rotate( cent->ent.axis, 5 * sin( ( phase + cg.time * 0.001 * speed * 0.01 ) * M_TWOPI ), 1, 0, 0 );
		}
	}

	CG_AddEntityToScene( &cent->ent );
}

//==========================================================================
//		ET_CAPTURE_AREA_*
//==========================================================================

// helper macro
#define CG_CaptureAreaNo( type ) ( ( type ) - ET_CAPTURE_AREA_A )

//===============
//CG_SetCaptureAreaColor
//===============
static void CG_SetCaptureAreaColor( centity_t *cent )
{
	vec4_t teamcolor;
	int areano;
	int areastate = 0, team = 0, cteam = 0;
	float percentage = 0.0f;

	areano = CG_CaptureAreaNo( cent->type );
	if( areano < 0 || areano >= MAX_CAPTURE_AREAS )
		CG_Error( "Invalid cent->type in CG_CaptureAreaEnt" );

	Vector4Copy( TDM_FreeCAColour, teamcolor );

	CG_GetCaptureAreaInfo( areano, &areastate, &team, &cteam, &percentage );

	if( areastate )
	{
		if( areastate == AREA_STATE_CAPTURED )
			CG_TeamColor( team, teamcolor );
	}
	else
		CG_Error( "Capture area indicator linked to unused capture area" );

	// slowly change the colour of the indicator if it is being captured
	if( cteam )
	{
		vec4_t cteamcolor;

		CG_TeamColor( cteam, cteamcolor );
		VectorLerp( teamcolor, percentage, cteamcolor, teamcolor );
	}

	Vector4Scale( teamcolor, 255, cent->ent.shaderRGBA );
}

//===============
//CG_AddCaptureAreaEnt
//===============
static void CG_AddCaptureAreaEnt( centity_t *cent )
{
	int areano;

	areano = CG_CaptureAreaNo( cent->type );
	if( areano < 0 || areano >= MAX_CAPTURE_AREAS )
		CG_Error( "Invalid cent->type in CG_CaptureAreaEnt" );

	if( cg_simpleItems->integer )
	{	
		cent->ent.rtype = RT_SPRITE;
		cent->ent.model = NULL;
		cent->ent.renderfx = RF_NOSHADOW|RF_FULLBRIGHT;
		cent->ent.frame = cent->ent.oldframe = 0;
		cent->ent.radius = 32;
		cent->ent.customShader = trap_R_RegisterPic( capture_indicators_simple[areano] );
	}
	else
	{
		cent->ent.rtype = RT_MODEL;
		cent->ent.model = cgs.modelDraw[cent->current.modelindex];
		cent->ent.renderfx = 0;
		cent->ent.customShader = trap_R_RegisterPic( capture_indicators[areano] );
	}

	CG_SetCaptureAreaColor( cent );

	CG_AddEntityToScene( &cent->ent );
}

#undef CG_CaptureAreaNo

//==========================================================================
//		PACKET ENTITIES
//==========================================================================

//===============
//CG_AddPacketEntities
// Add the entities to the rendering list
//===============
static void CG_AddPacketEntities( void )
{
	entity_state_t *state;
	vec3_t autorotate;
	int pnum;
	centity_t *cent;
	vec4_t teamcolor = { 1.0f, 1.0f, 1.0f, 1.0f };

	// bonus items rotate at a fixed rate
	VectorSet( autorotate, 0, ( cg.time%3600 )*0.1, 0 );
	AnglesToAxis( autorotate, cg.autorotateAxis );

	for( pnum = 0; pnum < cg.frame.numEntities; pnum++ )
	{
		state = &cg.frame.parsedEntities[pnum & ( MAX_PARSE_ENTITIES-1 )];
		cent = &cg_entities[state->number];

		// the snapshot might be drawing before linear projectiles
		// were spawned in the server
		if( state->linearProjectile )
		{
			unsigned int serverTime = cg.time + cgs.baseServerTime;
			if( state->linearProjectileTimeStamp > serverTime )
				continue;
		}

		switch( cent->type )
		{
		case ET_GENERIC:
			CG_AddGenericEnt( cent );
			if( cg_drawEntityBoxes->integer ) CG_DrawEntityBox( cent );
			break;
		case ET_GIB:
			if( cg_gibs->integer )
			{
				CG_AddGenericEnt( cent );
				CG_NewBloodTrail( cent );
			}
			break;
		case ET_BLASTER:
			CG_AddGenericEnt( cent );
			CG_BlasterTrail( cent->trailOrigin, cent->ent.origin );
			CG_AddLightToScene( cent->ent.origin, 300, 1, 1, 0, NULL );
			break;
		case ET_SHOCKWAVE:
			cent->ent.scale = 5.0; // temporary hax
			CG_AddGenericEnt( cent );
			break;
		case ET_ELECTRO_WEAK:
			if( !CG_TeamBeamColor( cent->current.team, cent->current.ownerNum, teamcolor ) )
			{
				teamcolor[0] = 1.0f;
				teamcolor[1] = 1.0f;
				teamcolor[2] = 1.0f;
			}

			// Oops - ownerNum and frame are a union, reset frame, I hope this hasn't got any side-effects...
			// Reset them to 0
			cent->current.frame = cent->prev.frame = 0;
			cent->ent.frame =  cent->ent.oldframe = 0;

			CG_AddGenericEnt( cent );
			CG_AddLightToScene( cent->ent.origin, 300, teamcolor[0], teamcolor[1], teamcolor[2], NULL );
			CG_ElectroWeakTrail( cent->trailOrigin, cent->ent.origin, teamcolor );
			break;
		case ET_ROCKET:
			CG_AddGenericEnt( cent );
			CG_NewRocketTrail( cent );
			CG_RocketFireTrail( cent );
			CG_AddLightToScene( cent->ent.origin, 300, 1, 1, 0, NULL );
			break;
		case ET_GRENADE:
			CG_AddGenericEnt( cent );
			CG_NewGrenadeTrail( cent );
			break;
		case ET_PLASMA:
			CG_AddGenericEnt( cent );
			break;

		case ET_ITEM:
			CG_AddItemEnt( cent );
			if( cg_drawEntityBoxes->integer ) CG_DrawEntityBox( cent );
			break;

		case ET_PLAYER:
		case ET_CORPSE:
			CG_AddPlayerEnt( cent );
			if( cg_drawEntityBoxes->integer ) CG_DrawEntityBox( cent );
			break;

		case ET_BEAM:
			CG_AddBeamEnt( cent );
			break;

		case ET_LASERBEAM:
			CG_AddLaserbeamEnt( cent );
			break;

		case ET_CURVELASERBEAM:
			CG_AddCurveLaserbeamEnt( cent );
			break;

		case ET_PORTALSURFACE:
			CG_AddPortalSurfaceEnt( cent );
			break;

		case ET_FLAG_BASE:
			CG_AddFlagBaseEnt( cent );
			break;

		case ET_EVENT:
			break;

		case ET_PUSH_TRIGGER:
			if( cg_drawEntityBoxes->integer ) CG_DrawEntityBox( cent );
			break;

		case ET_CAPTURE_AREA_A:
		case ET_CAPTURE_AREA_B:
		case ET_CAPTURE_AREA_C:
		case ET_CAPTURE_AREA_D:
			CG_AddCaptureAreaEnt( cent );
			break;

		default:
			CG_Error( "CG_AddPacketEntities: unknown entity type" );
			break;
		}

		// add loop sound, laserbeam does it by itself
		if( cent->current.sound && cent->type != ET_LASERBEAM && cent->type != ET_CURVELASERBEAM )
		{
			if( ISVIEWERENTITY( cent->current.number ) )
				trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], cent->current.number, 1.0, ATTN_NONE );
			else
				trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], cent->current.number, 1.0, ATTN_LOOP );
		}

		// glow if light is set
		if( state->light && !state->linearProjectile )
		{
			CG_AddLightToScene( cent->ent.origin,
			                    COLOR_A( state->light ) * 4.0,
			                    COLOR_R( state->light ) * ( 1.0/255.0 ),
			                    COLOR_G( state->light ) * ( 1.0/255.0 ),
			                    COLOR_B( state->light ) * ( 1.0/255.0 ), NULL );
		}

		VectorCopy( cent->ent.origin, cent->trailOrigin );
	}
}

//==============
//CG_LerpEntities
// Interpolate the entity states positions into the entity_t structs
//==============
void CG_LerpEntities( void )
{
	entity_state_t *state;
	int pnum;
	centity_t *cent;

	for( pnum = 0; pnum < cg.frame.numEntities; pnum++ )
	{
		state = &cg.frame.parsedEntities[pnum & ( MAX_PARSE_ENTITIES-1 )];
		cent = &cg_entities[state->number];

		switch( cent->type )
		{
		case ET_GENERIC:
		case ET_GIB:
		case ET_BLASTER:
		case ET_SHOCKWAVE:
		case ET_ELECTRO_WEAK:
		case ET_ROCKET:
		case ET_PLASMA:
		case ET_GRENADE:
		case ET_ITEM:
		case ET_PLAYER:
		case ET_CORPSE:
		case ET_FLAG_BASE:
			if( state->linearProjectile )
				CG_ExtrapolateLinearProjectile( cent );
			else
				CG_LerpGenericEnt( cent );
			break;

		case ET_CAPTURE_AREA_A:
		case ET_CAPTURE_AREA_B:
		case ET_CAPTURE_AREA_C:
		case ET_CAPTURE_AREA_D:
			//cent->ent.customShader = trap_R_RegisterPic( "models/objects/capture_a/capture_a" );
			cent->ent.frame = ( cg.time / 80 ) % 59;
			cent->ent.oldframe = cent->ent.frame ? cent->ent.frame-1 : 58;

			cent->ent.backlerp = 1.0 - (float)( cg.time % 80 ) / 80.0;
			break;

		case ET_BEAM:
			// beams aren't interpolated
			break;

		case ET_LASERBEAM:
		case ET_CURVELASERBEAM:
			CG_LerpLaserbeamEnt( cent );
			break;

		case ET_PORTALSURFACE:
			//portals aren't interpolated
			break;

		case ET_EVENT:
			break;

		case ET_PUSH_TRIGGER:
			break;

		default:
			CG_Error( "CG_LerpEntities: unknown entity type" );
			break;
		}
	}
}

//==============
//CG_UpdateEntities
// Called at receiving a new serverframe. Sets up the model, type, etc to be drawn later on
//==============
void CG_UpdateEntities( void )
{
	entity_state_t *state;
	int pnum;
	centity_t *cent;

	for( pnum = 0; pnum < cg.frame.numEntities; pnum++ )
	{
		state = &cg.frame.parsedEntities[pnum & ( MAX_PARSE_ENTITIES-1 )];
		cent = &cg_entities[state->number];
		cent->renderfx = state->renderfx & ~RF_CULLHACK; // cullhack is never set from the server
		cent->type = state->type;
		cent->effects = state->effects;
		cent->item = NULL;

		switch( cent->type )
		{
		case ET_GENERIC:
			CG_UpdateGenericEnt( cent );
			break;
		case ET_GIB:
		case ET_GRENADE:
			cent->renderfx |= RF_NOSHADOW;
			CG_UpdateGenericEnt( cent );
			break;

			// projectiles with linear trajectories
		case ET_BLASTER:
		case ET_SHOCKWAVE:
		case ET_ELECTRO_WEAK:
		case ET_ROCKET:
		case ET_PLASMA:
			cent->renderfx |= ( RF_NOSHADOW|RF_FULLBRIGHT );
			CG_UpdateGenericEnt( cent );
			break;

		case ET_ITEM:
			CG_UpdateItemEnt( cent );
			break;
		case ET_PLAYER:
		case ET_CORPSE:
			CG_UpdatePlayerModelEnt( cent );
			break;

		case ET_BEAM:
			break;

		case ET_LASERBEAM:
		case ET_CURVELASERBEAM:
			CG_UpdateLaserbeamEnt( cent );
			break;

		case ET_FLAG_BASE:
			CG_UpdateFlagBaseEnt( cent );
			break;

		case ET_PORTALSURFACE:
			CG_UpdatePortalSurfaceEnt( cent );
			break;

		case ET_EVENT:
			break;

		case ET_PUSH_TRIGGER:
			break;

		case ET_CAPTURE_AREA_A:
		case ET_CAPTURE_AREA_B:
		case ET_CAPTURE_AREA_C:
		case ET_CAPTURE_AREA_D:
			CG_UpdateGenericEnt( cent );
			break;

		default:
			if( cent->type & ET_INVERSE )
				CG_Printf( "CG_UpdateEntities: entity type with ET_INVERSE. Stripped type %i", cent->type & 127 );

			CG_Error( "CG_UpdateEntities: unknown entity type %i", cent->type );
			break;
		}
	}
}

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

//==============
//CG_StartKickAnglesEffect
//==============
void CG_StartKickAnglesEffect( vec3_t source, float knockback, float radius, int time )
{
	float kick;
	float side;
	float dist;
	float delta;
	float ftime;
	vec3_t forward, right, v;
	int i, kicknum = -1;
	vec3_t playerorigin;

	if( knockback <= 0 || time <= 0 || radius <= 0.0f )
		return;

	// if spectator but not in chasecam, don't get any kick
	if( cg.frame.playerState.pmove.pm_type == PM_SPECTATOR )
		return;

	// not if dead
	if( cg_entities[cg.view.POVent].current.type == ET_CORPSE || cg_entities[cg.view.POVent].current.type == ET_GIB )
		return;

	// unpredicted if it's in chasecam (fixme: they aren't interpolated and should be)
	if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM )
		VectorCopy( cg.frame.playerState.pmove.origin, playerorigin );
	else
		VectorCopy( cg.predictedOrigin, playerorigin );

	VectorSubtract( source, playerorigin, v );
	dist = VectorNormalize( v );
	if( dist > radius )
		return;

	delta = 1.0f - ( dist / radius );
	if( delta > 1.0f )
		delta = 1.0f;
	if( delta <= 0.0f )
		return;

	kick = abs( knockback ) * delta;
	if( kick ) // kick of 0 means no view adjust at all
	{
		//find first free kick spot, or the one closer to be finished
		for( i = 0; i < MAX_ANGLES_KICKS; i++ )
		{
			if( cg.time > cg.kickangles[i].timestamp + cg.kickangles[i].kicktime )
			{
				kicknum = i;
				break;
			}
		}

		// all in use. Choose the closer to be finished
		if( kicknum == -1 )
		{
			int remaintime;
			int best = ( cg.kickangles[0].timestamp + cg.kickangles[0].kicktime ) - cg.time;
			kicknum = 0;
			for( i = 1; i < MAX_ANGLES_KICKS; i++ )
			{
				remaintime = ( cg.kickangles[i].timestamp + cg.kickangles[i].kicktime ) - cg.time;
				if( remaintime < best )
				{
					best = remaintime;
					kicknum = i;
				}
			}
		}

		AngleVectors( cg.frame.playerState.viewangles, forward, right, NULL );

		if( kick < 1.0f )
			kick = 1.0f;

		side = DotProduct( v, right );
		cg.kickangles[kicknum].v_roll = kick*side*0.3;
		clamp( cg.kickangles[kicknum].v_roll, -20, 20 );

		side = -DotProduct( v, forward );
		cg.kickangles[kicknum].v_pitch = kick*side*0.3;
		clamp( cg.kickangles[kicknum].v_pitch, -20, 20 );

		cg.kickangles[kicknum].timestamp = cg.time;
		ftime = (float)time * delta;
		if( ftime < 100 )
			ftime = 100;
		cg.kickangles[kicknum].kicktime = ftime;
	}
}



//==============
//CG_StartColorBlendEffect
//==============
void CG_StartColorBlendEffect( float r, float g, float b, float a, int time )
{
	int i, bnum = -1;

	if( a <= 0.0f || time <= 0 )
		return;

	//find first free colorblend spot, or the one closer to be finished
	for( i = 0; i < MAX_COLORBLENDS; i++ )
	{
		if( cg.time > cg.colorblends[i].timestamp + cg.colorblends[i].blendtime )
		{
			bnum = i;
			break;
		}
	}

	// all in use. Choose the closer to be finished
	if( bnum == -1 )
	{
		int remaintime;
		int best = ( cg.colorblends[0].timestamp + cg.colorblends[0].blendtime ) - cg.time;
		bnum = 0;
		for( i = 1; i < MAX_COLORBLENDS; i++ )
		{
			remaintime = ( cg.colorblends[i].timestamp + cg.colorblends[i].blendtime ) - cg.time;
			if( remaintime < best )
			{
				best = remaintime;
				bnum = i;
			}
		}
	}

	// assign the color blend
	cg.colorblends[bnum].blend[0] = r;
	cg.colorblends[bnum].blend[1] = g;
	cg.colorblends[bnum].blend[2] = b;
	cg.colorblends[bnum].blend[3] = a;

	cg.colorblends[bnum].timestamp = cg.time;
	cg.colorblends[bnum].blendtime = time;
}

/*
   ===============
   CG_AddEntities

   Emits all entities, particles, and lights to the refresh
   ===============
 */
void CG_AddEntities( void )
{
	CG_AddPacketEntities();
	CG_AddViewWeapon();
	CG_AddLocalEntities();
	CG_AddParticles();
	CG_AddDlights();
#ifdef CGAMEGETLIGHTORIGIN
	CG_AddShadeBoxes();
#endif
	CG_AddDecals();
	CG_AddPolys();
}

/*
   ===============
   CG_GlobalSound
   ===============
 */
void CG_GlobalSound( const sound_t *sound )
{
	if( sound->num < 0 || sound->num >= MAX_SOUNDS )
		CG_Error( "CG_GlobalSound: bad sound num" );
	if( sound->entnum < 0 || sound->entnum >= MAX_EDICTS )
		CG_Error( "CG_GlobalSound: bad entnum" );

	if( cgs.soundPrecache[sound->num] )
	{
		if( ISVIEWERENTITY( sound->entnum ) )
		{
			trap_S_StartGlobalSound( cgs.soundPrecache[sound->num], sound->channel, sound->volume );
		}
		else
		{
			trap_S_StartFixedSound( cgs.soundPrecache[sound->num], sound->pos, sound->channel, sound->volume,
			                        sound->attenuation );
		}
	}
	else if( cgs.configStrings[CS_SOUNDS + sound->num][0] == '*' )
	{
		CG_SexedSound( sound->entnum, sound->channel, cgs.configStrings[CS_SOUNDS + sound->num], sound->volume );
	}
}

/*
   ===============
   CG_GetEntitySpatilization

   Called to get the sound spatialization origin and velocity
   ===============
 */
void CG_GetEntitySpatilization( int entNum, vec3_t origin, vec3_t velocity )
{
	centity_t *cent;
	struct cmodel_s *cmodel;
	vec3_t mins, maxs;

	if( entNum < -1 || entNum >= MAX_EDICTS )
		CG_Error( "CG_GetEntitySoundOrigin: bad entnum" );

	// hack for client side floatcam
	if( entNum == -1 )
	{
		if( origin != NULL )
			VectorCopy( cg.frame.playerState.pmove.origin, origin );
		if( velocity != NULL )
			VectorCopy( cg.frame.playerState.pmove.velocity, velocity );
		return;
	}

	cent = &cg_entities[entNum];

	// normal
	if( cent->current.solid != SOLID_BMODEL )
	{
		if( origin != NULL )
			VectorCopy( cent->ent.origin, origin );
		if( velocity != NULL )
			VectorCopy( cent->velocity, velocity );
		return;
	}

	// bmodel
	if( origin != NULL )
	{
		cmodel = trap_CM_InlineModel( cent->current.modelindex );
		trap_CM_InlineModelBounds( cmodel, mins, maxs );
		VectorAdd( maxs, mins, origin );
		VectorMA( cent->ent.origin, 0.5f, origin, origin );
	}
	if( velocity != NULL )
		VectorCopy( cent->velocity, velocity );
}
