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

void CG_UpdateEntities( void );


/*
==================
CG_FixVolumeCvars
Don't let the user go too far away with volumes
==================
*/
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 );

#ifdef VSAYS
	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 );
#endif

	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
==================
*/
void CG_FireEvents( void )
{
	int					pnum;
	entity_state_t		*state;

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

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

	cent = &cg_entities[state->number];

	// some data changes will force no lerping
	if( state->modelindex != cent->current.modelindex
		|| state->modelindex2 != cent->current.modelindex2
		|| abs(state->origin[0] - cent->current.origin[0]) > 512
		|| abs(state->origin[1] - cent->current.origin[1]) > 512
		|| abs(state->origin[2] - cent->current.origin[2]) > 512
		|| state->events[0] == EV_TELEPORT 
		|| state->events[1] == EV_TELEPORT
		)
	{
		cent->serverFrame = -99;
	}

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

		if ( state->events[0] == EV_TELEPORT || state->events[1] == EV_TELEPORT ) {
			VectorCopy( state->origin, cent->prev.origin );
			VectorCopy( state->origin, cent->trailOrigin );
		} else {
			VectorCopy( state->old_origin, cent->prev.origin );
			VectorCopy( state->old_origin, cent->trailOrigin );
		}

		//splitmodels:(jalPVSfix) Init the animation when new into PVS
		if ( cg.frame.valid && state->type == ET_PLAYER ) {
			CG_ClearEventAnimations( state->number );
			CG_AddPModelAnimation( state->number, (state->frame)&0x3F, (state->frame>>6)&0x3F, (state->frame>>12)&0xF, BASIC_CHANNEL);
		}
	}
	else
	{	// shuffle the last state to previous
		cent->prev = cent->current;
	}

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

//==================
//CG_NewFrameSnap
// a new frame snap has been received from the server
//==================
void CG_NewFrameSnap( frame_t *frame, frame_t *deltaframe ) {
	int i;
	if( deltaframe ) {
		cg.oldFrame = *deltaframe;
	} else {
		cg.oldFrame = *frame;
	}

	cg.frame = *frame;
	cg.time = cg.frame.serverTime;
	for( i = 0; i < frame->numEntities; i++ ) {
		CG_NewPacketEntityState( &frame->parsedEntities[i & (MAX_PARSE_ENTITIES-1)] );
	}

	// a new server frame begins now
	CG_FixVolumeCvars();		// wsw : jal

	CG_BuildSolidList();
	CG_UpdateEntities();		//wsw : jal
	CG_vWeapUpdateState();		//splitmodels
	CG_FireEvents();
	CG_CheckWeaponState();		// wsw : mdr
	CG_FirePlayerStateEvents();	// wsw : jal
	CG_CheckPredictionError();
}


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


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

ADD INTERPOLATED ENTITIES TO RENDERING LIST

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

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

	// bobbing items
	//if( !(cent->effects & EF_ROTATE_AND_BOB) ) 
	//	return;

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

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

//===============
//CG_UpdateGenericEnt
//===============
void CG_UpdateGenericEnt( centity_t *cent )
{
	// start from clean
	memset( &cent->ent, 0, sizeof( cent->ent ) );
	cent->ent.scale = 1.0f;
	cent->ent.flags = cent->renderfx;
	
	// make all of white color by default
	Vector4Set( cent->ent.color, 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 {
		cent->ent.skinnum = cent->current.skinnum;
		cent->ent.model = cgs.modelDraw[cent->current.modelindex];
	}

	// 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.oldorigin );
	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 );

	//relink entity boneposes to cg_entity ones
	CG_RegisterBoneposesForCGEntity( cent, cent->ent.model );
	cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes;
}

//===============
//CG_LerpGenericEnt
//===============
void CG_LerpGenericEnt( centity_t *cent )
{
	int				i;
	vec3_t			ent_angles = { 0, 0, 0 };
	
	cent->ent.backlerp = 1.0f - cg.lerpfrac;

	if( cent->renderfx & RF_FRAMELERP ) {
		// let the renderer do the origin interpolation 
		VectorCopy( cent->current.origin, cent->ent.origin );
		VectorCopy( cent->current.old_origin, cent->ent.oldorigin );
	} else { 
		// interpolate origin from old state 
		for( i = 0; i < 3; i++ ) {
			cent->ent.origin[i] = cent->ent.oldorigin[i] = cent->prev.origin[i] + cg.lerpfrac * 
				(cent->current.origin[i] - cent->prev.origin[i]);
		}
	}

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

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

//===============
//CG_AddGenericEnt
//===============
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_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE) ) {
		cent->ent.flags = cent->renderfx & RF_MINLIGHT;	// renderfx go on color shell entity
	} else {
		cent->ent.flags = cent->renderfx;
	}

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

		if( cent->effects & EF_AMMOBOX ) {
			// 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.color[0] = ( qbyte )( 255 * scolor[0] );
				cent->ent.color[1] = ( qbyte )( 255 * scolor[1] );
				cent->ent.color[2] = ( qbyte )( 255 * scolor[2] );
				cent->ent.color[3] = ( qbyte )( 255 * scolor[3] );
			}
			else // set white
				Vector4Set( cent->ent.color, 255, 255, 255, 255 );
		}

#ifdef CGAMEGETLIGHTORIGIN
		// add shadows for items (do it before offseting for weapons)
		if( !(cent->ent.flags & RF_NOSHADOW) )
			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 ) {
			CG_PlaceModelOnTag( &cent->ent, &cent->ent, &cgs.weaponItemTag );
		}
	}

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

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

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

	// if it's a rocket, add a second model with the flare
	if( cent->type == ET_ROCKET ) {
		struct model_s	*model1 = cent->ent.model;
		cent->ent.model = CG_MediaModel( cgs.media.modRocketFlare );
		CG_SetBoneposesForCGEntity( &cent->ent, cent );	// skelmod
		CG_AddEntityToScene( &cent->ent );
		cent->ent.model = model1;
	}

	// 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 );	// skelmod
		cent->ent.customShader = NULL;
		//recover the model
		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, red and blue, are considered
	if( cent->current.team != TEAM_BLUE && cent->current.team != TEAM_RED )
		return;

	GS_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.flags = cent->ent.flags;
	flag.customShader = NULL;
	flag.customSkin = NULL;
	if( cent->ent.flags & RF_VIEWERMODEL ) {
		VectorCopy( cg.refdef.vieworg, flag.origin );
		VectorCopy( cg.refdef.vieworg, flag.oldorigin );
		VectorCopy( cg.refdef.vieworg, flag.lightingOrigin );
		Matrix_Copy( axis_identity, flag.axis );
		VectorMA( flag.origin, -24, flag.axis[0], flag.origin ); // move it some backwards
		VectorMA( flag.origin, 64, flag.axis[2], flag.origin ); // move it some upwards
	} else {
		VectorCopy( cent->ent.origin, flag.origin );
		VectorCopy( cent->ent.origin, flag.oldorigin );
		VectorCopy( cent->ent.origin, 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 );
		}

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

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

		if( !(cent->ent.flags & RF_VIEWERMODEL) ) {
			flag.rtype = RT_SPRITE;
			flag.model = NULL;
			flag.flags = RF_NOSHADOW|RF_FULLBRIGHT;
			flag.frame = flag.oldframe = 0;
			flag.radius = 32.0f;
			flag.customShader = CG_MediaShader( cgs.media.shaderFlagFlare );

			flag.color[0] = ( qbyte )( 255 * teamcolor[0] );
			flag.color[1] = ( qbyte )( 255 * teamcolor[1] );
			flag.color[2] = ( qbyte )( 255 * teamcolor[2] );
			flag.color[3] = ( qbyte )( 255 * teamcolor[3] );

			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
//===============
void CG_UpdateFlagBaseEnt( centity_t *cent )
{
	// start from clean
	
	memset( &cent->ent, 0, sizeof( cent->ent ) );
	Vector4Set( cent->ent.color, 255, 255, 255, 255 );
	cent->ent.scale = 1.0f;
	cent->ent.flags = 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];
	}

	if( cent->effects & EF_OUTLINE ) {
		vec4_t teamcolor;
		GS_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.oldorigin );
	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 );

	//relink entity boneposes to cg_entity ones
	CG_RegisterBoneposesForCGEntity( cent, cent->ent.model );
	cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes;
}

//===============
//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_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE) ) {
		cent->ent.flags = cent->renderfx & RF_MINLIGHT;	// renderfx go on color shell entity
	} else {
		cent->ent.flags = cent->renderfx;
	}
	if( cent->item )
		cent->ent.flags |= cent->item->renderfx;


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

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

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

	// shells generate a separate entity for the main model
	CG_AddCentityOutLineEffect( cent );
	CG_AddColorShell( &cent->ent, cent->renderfx );
#ifdef CGAMEGETLIGHTORIGIN
	// no light for flag bases
	//if( !(cent->ent.flags & RF_NOSHADOW) )
	//	CG_AllocShadeBox( cent->current.number, cent->ent.origin, item_box_mins, item_box_maxs, NULL );
#endif

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

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

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

//===============
//CG_UpdatePlayerModelEnt
//===============
void CG_UpdatePlayerModelEnt( centity_t *cent );

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

	if( cent->current.number == cg.chasedNum + 1 ) { 
		cg.effects = cent->effects;
		if( !cg.thirdPerson && cent->current.modelindex )
			cent->ent.flags |= RF_VIEWERMODEL;	// only draw from mirrors
	}

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

	CG_AddPModel( cent );

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

	cent->ent.skinnum = 0;
	cent->ent.flags = cent->ent.flags & RF_VIEWERMODEL;		// only draw from mirrors
	Vector4Set( cent->ent.color, 255, 255, 255, 255 );

	// corpses can never have a model in modelindex2
	if( cent->effects & EF_CORPSE )
		return;
	
	// duplicate for linked models
	if( cent->current.modelindex2 )
	{
		cent->ent.model = cgs.modelDraw[cent->current.modelindex2];
		CG_AddEntityToScene( &cent->ent );	// skelmod
		
		CG_AddShellEffects( &cent->ent, cent->effects );
		CG_AddColorShell( &cent->ent, cent->renderfx );
	}
}



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

//===============
//CG_UpdateItemEnt
//===============
void CG_UpdateItemEnt( centity_t *cent )
{
	memset( &cent->ent, 0, sizeof( cent->ent ) );
	Vector4Set( cent->ent.color, 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->tag != FLAG_RED && 
		cent->item->tag != FLAG_BLUE && cent->item->tag != FLAG_YELLOW &&
		cent->item->tag != FLAG_GREEN ) {

		cent->ent.rtype = RT_SPRITE;
		cent->ent.model = NULL;
		cent->ent.flags = 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->icon );

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

	// 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.oldorigin );
	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 );

	//relink entity boneposes to cg_entity ones
	CG_RegisterBoneposesForCGEntity( cent, cent->ent.model );
	cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes;
}

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

	if( !cent->item )
		return;

	// 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.5f;

		//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.oldorigin[2] += cent->ent.radius + 2;
	if( cent->effects & EF_ROTATE_AND_BOB ) 
		CG_EntAddBobEffect( cent );

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

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

//===============
//CG_AddBeamEnt
//===============
void CG_AddBeamEnt( centity_t *cent )
{
	CG_AddLaser( cent->current.origin, cent->current.origin2, cent->current.frame * 0.5f, cent->current.colorRGBA, CG_MediaShader( cgs.media.shaderLaser ) );
}

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

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

	//cent->ent.model = cgs.modelDraw[cent->current.modelindex];
	cent->ent.model = NULL;
}

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

//===============
//CG_AddLaserbeamEnt - lasegun's continuous beam
//===============
void CG_AddLaserbeamEnt( centity_t *cent )
{
	centity_t	*owner;
	orientation_t	projectsource;
	vec3_t dir;
	int			i;
	int			range = cent->current.skinnum;

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

	if( (cent->current.ownerNum == cg.chasedNum+1) &&
		(owner->serverFrame == cg.frame.serverFrame) )
	{
		vec3_t	fireorg, end, front;
		trace_t	trace;
		
		if( !cg.thirdPerson && 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.refdef.vieworg[i] +
					(1 - cg_predictLaserBeam->value) * (cg.player.origin[i] + cg.player.viewoffset[i]);
			}
			AngleVectors( cg.refdef.viewangles, front_refdef, NULL, NULL );
			AngleVectors( cg.player.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
			for( i = 0; i < 3; i++ ) {
				fireorg[i] = cg.player.origin[i] + cg.player.viewoffset[i];
			}
			AngleVectors( cg.player.viewangles, front, NULL, NULL );
		}
		VectorNormalizeFast( front );
		VectorMA( fireorg, range, front, end );
		CG_Trace( &trace, fireorg, vec3_origin, vec3_origin, end, cg.chasedNum+1, 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) < (range-1) ) {
		CG_NewElectroBeamPuff( cent, cent->ent.origin, dir );
		//CG_ImpactPufParticles( cent->ent.origin, dir, 1, 1.5f, 0.9f, 0.9f, 0.5f, 1.0f, NULL );
		//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 );

	//enable continuous flash on the weapon 
	if( cg_weaponFlashes->integer ) {
		pmodel_t *pmodel = &cg_entPModels[cent->current.ownerNum];
		pmodel->pweapon.flashtime = cg.time + (cg.frame.serverTime - cg.oldFrame.serverTime);
		if( cent->current.ownerNum == cg.chasedNum + 1 )
			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 ) {
		trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], owner->current.origin,
			cg_volume_effects->value, (owner->current.number != cg.chasedNum + 1) );
	}
}


//===============
//CG_AddCurveLaserbeamEnt - lasegun's continuous curved beam
//===============
void CG_AddCurveLaserbeamEnt( centity_t *cent )
{
	centity_t	*owner;
	orientation_t	projectsource;
	int			i;
	int			range = cent->current.skinnum;
	vec3_t		viewangles;
	vec3_t		fireorg, front, dir;
	vec3_t		aim_end;

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

	if( (cent->current.ownerNum == cg.chasedNum+1) &&
		(owner->serverFrame == cg.frame.serverFrame) )
	{
		if( !cg.thirdPerson && 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.refdef.vieworg[i] +
					(1 - cg_predictLaserBeam->value) * (cg.player.origin[i] + cg.player.viewoffset[i]);
			}
			AngleVectors( cg.refdef.viewangles, front_refdef, NULL, NULL );
			AngleVectors( cg.player.viewangles, front, NULL, NULL );
			for( i = 0; i < 3; i++ ) {
				//viewangles[i] = LerpAngle( cg.player.viewangles[i], cg.refdef.viewangles[i], cg_predictLaserBeam->value );
				//viewangles[i] = LerpAngle( cg.refdef.viewangles[i], cg.player.viewangles[i], cg_predictLaserBeam->value );
				viewangles[i] = cg.refdef.viewangles[i];
				//front[i] = cg_predictLaserBeam->value * front_refdef[i] + (1 - cg_predictLaserBeam->value) * front[i];
			}
		} else {
			// use only playerstate values
			for( i = 0; i < 3; i++ ) {
				fireorg[i] = cg.player.origin[i] + cg.player.viewoffset[i];
			}
			//AngleVectors( cg.player.viewangles, front, NULL, NULL );
			VectorCopy( cg.player.viewangles, viewangles );
		}
		
		//VectorMA( fireorg, range, front, end );
		//CG_Trace( &trace, fireorg, vec3_origin, vec3_origin, end, cg.chasedNum+1, 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 {
		
		// 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]);
			}
		}

		// 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 );
	VectorMA( projectsource.origin, range, front, aim_end );

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

	// 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
	// let's draw it server side
	{
		int		j;
		float	frac;
		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 );

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

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

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

	if( cent->current.sound ) {
		trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], owner->current.origin,
			cg_volume_effects->value, (owner->current.number != cg.chasedNum + 1) );
	}
}

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

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

	VectorCopy( cent->current.origin, cent->ent.origin );
	VectorCopy( cent->current.origin2, cent->ent.oldorigin );

	cent->ent.rtype = RT_PORTALSURFACE;
	cent->ent.scale = cent->current.frame / 256.0f;

	if( cent->current.effects & EF_ROTATE_AND_BOB )
		cent->ent.frame = cent->current.modelindex2 ? cent->current.modelindex2 : 50;

	cent->ent.skinnum = cent->current.skinnum;
}

//===============
//CG_AddPortalSurfaceEnt
//===============
void CG_AddPortalSurfaceEnt( centity_t *cent )
{
	CG_AddEntityToScene( &cent->ent );
}


//===============
//CG_AddPacketEntities
// Add the entities to the rendering list
//===============
void CG_AddPacketEntities( void )
{
	entity_state_t	*state;
	vec3_t			autorotate;
	vec3_t			sound_origin;
	int				pnum;
	centity_t		*cent;

	// bonus items rotate at a fixed rate
	VectorSet( autorotate, 0, anglemod( cg.time * 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];

		switch( cent->type ) {
			case ET_GENERIC:
				CG_AddGenericEnt( 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:
				CG_AddGenericEnt( cent );
				CG_AddLightToScene( cent->ent.origin, 300, 1, 1, 1, NULL );
				CG_ElectroWeakTrail( cent->trailOrigin, cent->ent.origin );
				break;
			case ET_ROCKET:
				CG_AddGenericEnt( cent );
				CG_NewRocketTrail( 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 );
				//CG_AddLightToScene( cent->ent.origin, 300, 0, 1, 0, NULL );
				break;

			case ET_ITEM:
				CG_AddItemEnt( cent );
				break;

			case ET_PLAYER:
				CG_AddPlayerEnt( 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:
				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 ) {
			CG_GetEntitySoundOrigin( state->number, sound_origin );
			trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], sound_origin, 1.0, qtrue );
		}

		// glow if light is set
		if( state->light ) {
			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_GRENADE:
			case ET_PLASMA:
			case ET_ITEM:
			case ET_PLAYER:	
			case ET_FLAG_BASE:
				CG_LerpGenericEnt( cent );
				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:
			case ET_GIB:
			case ET_BLASTER:
			case ET_SHOCKWAVE:
			case ET_ELECTRO_WEAK:
			case ET_ROCKET:
			case ET_GRENADE:
			case ET_PLASMA:
				CG_UpdateGenericEnt( cent );
				break;
			case ET_ITEM:
				CG_UpdateItemEnt( cent );
				break;
			case ET_PLAYER:	
				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;

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

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


/*
==============
CG_AddViewWeapon - SPLITMODELS
==============
*/
void CG_AddViewWeapon( void );
void CG_CalcViewWeapon( void );


//==============
//CG_StartKickAnglesEffect - wsw
//==============
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;
	player_state_t	*ps;
	ps = &cg.frame.playerState;

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

	// if spectator but not in chasecam, don't get any kick
	if( ps->pmove.pm_type == PM_SPECTATOR )
		return;

	// not if dead
	if( cg_entities[cg.chasedNum+1].current.effects & EF_CORPSE )
		return;

	// unpredicted if it's in chasecam (fixme: they aren't interpolated and should be)
	if( cgs.playerNum != cg.chasedNum )
		VectorScale( cg.frame.playerState.pmove.origin, (1.0/16.0), 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( ps->viewangles, forward, right, NULL );

		if( kick < 1.0f )
			kick = 1.0f;
		
		side = DotProduct( v, right );
		cg.kickangles[kicknum].v_roll = kick*side*0.3;
		
		side = -DotProduct( v, forward );
		cg.kickangles[kicknum].v_pitch = kick*side*0.3;
		
		cg.kickangles[kicknum].timestamp = cg.time;
		ftime = (float)time * delta;
		if( ftime < 100 )
			ftime = 100;
		cg.kickangles[kicknum].kicktime = ftime;
	}
}



//==============
//CG_StartColorBlendEffect - wsw
//==============
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_Printf("New Blend: spot:%i, alpha:%f, cgtime:%i blendtime:%i\n", bnum, a, cg.time, cg.colorblends[bnum].blendtime );
}

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

Emits all entities, particles, and lights to the refresh
===============
*/
void CG_AddEntities( void )
{
	extern int cg_numBeamEnts;

	cg_numBeamEnts = 0;

	CG_CalcViewWeapon();
	CG_AddPacketEntities();
	CG_AddViewWeapon();
	CG_AddBeams();
	CG_AddLocalEntities();
	CG_AddParticles();
	CG_AddDlights();
#ifdef CGAMEGETLIGHTORIGIN
	CG_AddShadeBoxes();
#endif
	CG_AddDecals();
	CG_AddPolys();
}

/*
===============
CG_GlobalSound
===============
*/
void CG_GlobalSound( vec3_t origin, int entNum, int entChannel, int soundNum, float fvol, float attenuation )
{
	if( entNum < 0 || entNum >= MAX_EDICTS )
		CG_Error( "CG_GlobalSound: bad entnum" );

	if( cgs.soundPrecache[soundNum] ) {
		if( entNum == cg.chasedNum + 1 )
			trap_S_StartSound( NULL, entNum, entChannel, cgs.soundPrecache[soundNum], fvol, attenuation, 0.0 ); 
		else
			trap_S_StartSound( origin, 0, entChannel, cgs.soundPrecache[soundNum], fvol, attenuation, 0.0 ); 
	} else if( cgs.configStrings[CS_SOUNDS + soundNum][0] == '*' ) {
		CG_SexedSound( entNum, entChannel, cgs.configStrings[CS_SOUNDS + soundNum], fvol );
	}
}

/*
===============
CG_GetEntitySoundOrigin

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

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

	cent = &cg_entities[entNum];
	if( cent->current.solid != SOLID_BMODEL ) {
		VectorCopy( cent->ent.origin, org );
		return;
	}

	cmodel = trap_CM_InlineModel( cent->current.modelindex );
	trap_CM_InlineModelBounds( cmodel, mins, maxs );
	VectorAdd( maxs, mins, org );
	VectorMA( cent->ent.origin, 0.5f, org, org );
}

