// Copyright (C) 1999-2000 Id Software, Inc.
//
// cg_ents.c -- present snapshot entities, happens every single frame

#include "cg_local.h"
#include "fx_local.h"

static void CG_LaserSight( centity_t *cent ); // Laser
static void CG_Turbolift( centity_t *cent );

/*
======================
CG_PositionEntityOnTag

Modifies the entities position and axis by the given
tag location
======================
*/
void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 
							qhandle_t parentModel, char *tagName ) {
	int				i;
	orientation_t	lerped;
	
	// lerp the tag
	trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
		1.0 - parent->backlerp, tagName );

	// FIXME: allow origin offsets along tag?
	VectorCopy( parent->origin, entity->origin );
	for ( i = 0 ; i < 3 ; i++ ) {
		VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
	}

	// had to cast away the const to avoid compiler problems...
	MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis );
	entity->backlerp = parent->backlerp;

}


/*
======================
CG_PositionRotatedEntityOnTag

Modifies the entities position and axis by the given
tag location
======================
*/
void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, 
							qhandle_t parentModel, char *tagName ) {
	int				i;
	orientation_t	lerped;
	vec3_t			tempAxis[3];

//AxisClear( entity->axis );
	// lerp the tag
	trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
		1.0 - parent->backlerp, tagName );

	// FIXME: allow origin offsets along tag?
	VectorCopy( parent->origin, entity->origin );
	for ( i = 0 ; i < 3 ; i++ ) {
		VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
	}

	// had to cast away the const to avoid compiler problems...
	MatrixMultiply( entity->axis, lerped.axis, tempAxis );
	MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis );
}



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

FUNCTIONS CALLED EACH FRAME

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

/*
======================
CG_SetEntitySoundPosition

Also called by event processing code
======================
*/
void CG_SetEntitySoundPosition( centity_t *cent ) {
	if ( cent->currentState.solid == SOLID_BMODEL ) {
		vec3_t	origin;
		float	*v;

		v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ];
		VectorAdd( cent->lerpOrigin, v, origin );
		trap_S_UpdateEntityPosition( cent->currentState.number, origin );
	} else {
		trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin );
	}
}

/*
==================
CG_EntityEffects

Add continuous entity effects, like local entity emission and lighting
==================
*/
static void CG_EntityEffects( centity_t *cent ) {

	// update sound origins
	CG_SetEntitySoundPosition( cent );

	// add loop sound
	if ( cent->currentState.loopSound && cent->currentState.loopSound < 256 ) {
		trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, 
			cgs.gameSounds[ cent->currentState.loopSound ] );
	}


	// constant light glow
	if ( cent->currentState.constantLight ) {
		int		cl;
		int		i, r, g, b;

		cl = cent->currentState.constantLight;
		r = cl & 255;
		g = ( cl >> 8 ) & 255;
		b = ( cl >> 16 ) & 255;
		i = ( ( cl >> 24 ) & 255 ) * 4;
		trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b );
	}

}


/*
==================
CG_Useable
==================
*/
static void CG_Useable( centity_t *cent ) {
	refEntity_t			ent;
	entityState_t		*s1;

	s1 = &cent->currentState;

	// if set to invisible, skip
	if (!s1->modelindex) {
		return;
	}

	if (s1->modelindex == HI_SHIELD) 
	{	// The portable shield should go through a different rendering function.
		FX_DrawPortableShield(cent);
		return;
	}


	memset (&ent, 0, sizeof(ent));

	// set frame

	if (s1->eFlags & EF_ANIM_ALLFAST)
	{
		//ent.frame = (cg.time / 100);
		ent.frame = (cg.time * 0.01);
		ent.renderfx|=RF_WRAP_FRAMES;
	}
	else
	{
		ent.frame = s1->frame;
	}
	ent.oldframe = ent.frame;
	ent.backlerp = 0;

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

	ent.hModel = cg_items[s1->modelindex2].model;//cgs.useableModels[s1->modelindex];

	// player model
	if (s1->number == cg.snap->ps.clientNum) {
		ent.renderfx |= RF_THIRD_PERSON;	// only draw from mirrors
	}

	// convert angles to axis
//	AnglesToAxis( cent->lerpAngles, ent.axis );
	{
		// hack to keep dropped detpacks from rotating 
		vec3_t vecs[3];
		AngleVectors(s1->angles, vecs[0], vecs[1], vecs[2]);
		VectorNegate(vecs[1], vecs[1]);
		if (s1->modelindex == HI_DETPACK) // as stated, HACK for detpack
		{
			VectorScale(vecs[0], .5, vecs[0]);
			VectorScale(vecs[1], .5, vecs[1]);
			VectorScale(vecs[2], .5, vecs[2]);
		}
		AxisCopy( vecs, ent.axis );
	}

	// add to refresh list
	trap_R_AddRefEntityToScene (&ent);
}

/*
==================
CG_General
==================
*/
#define ITEM_SCALEUP_DIV 1.0/(float)ITEM_SCALEUP_TIME
static void CG_General( centity_t *cent ) {
	refEntity_t			ent;
	entityState_t		*s1;

	s1 = &cent->currentState;

	// if set to invisible, skip
	if (!s1->modelindex) {
		return;
	}

	memset (&ent, 0, sizeof(ent));

	// set frame

	if ( s1->eFlags & EF_ANIM_ONCE )
	{
		ent.frame = s1->frame;
		ent.renderfx|=RF_CAP_FRAMES;
	}
	else if (s1->eFlags & EF_ANIM_ALLFAST)
	{
		//ent.frame = (cg.time / 100);
		ent.frame = (cg.time * 0.01);
		ent.renderfx|=RF_WRAP_FRAMES;
	}
	else
	{
		ent.frame = s1->frame;
		ent.renderfx|=RF_CAP_FRAMES;
	}

	ent.oldframe = ent.frame;
	ent.backlerp = 0;

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

	ent.hModel = cgs.gameModels[s1->modelindex];

	// player model
	if (s1->number == cg.snap->ps.clientNum) {
		ent.renderfx |= RF_THIRD_PERSON;	// only draw from mirrors
	}

	// convert angles to axis
	AnglesToAxis( cent->lerpAngles, ent.axis );

	// add to refresh list
	if ( s1->eFlags & EF_ITEMPLACEHOLDER )		// object is "spawning" in
	{
		int	msec;

		if ( !cent->miscTime )
		{
			cent->miscTime = cg.time;
		}

		msec = cg.time - cent->miscTime;
		if ( msec < ITEM_SCALEUP_TIME )
		{
			float alpha;
			int a;
			alpha = (float)msec * ITEM_SCALEUP_DIV;
			if ( s1->eventParm == 255 )
			{
				alpha = 1.0f-alpha;
			}
			a = alpha * 255.0;
			if (a <= 0)
				a=1;
			ent.shaderRGBA[0] =
			ent.shaderRGBA[1] =
			ent.shaderRGBA[2] = 255;
			ent.shaderRGBA[3] = a;
			ent.renderfx |= RF_FORCE_ENT_ALPHA;
			trap_R_AddRefEntityToScene(&ent);
			ent.renderfx &= ~RF_FORCE_ENT_ALPHA;
		
			// Now draw the static shader over it.
			// Alpha in over half the time, out over half.
			alpha = sin(M_PI*alpha);
			a = alpha * 255.0;
			if (a <= 0)
				a=1;
			ent.customShader = cgs.media.rezOutShader;
			ent.shaderRGBA[0] =
			ent.shaderRGBA[1] =
			ent.shaderRGBA[2] = a;
			trap_R_AddRefEntityToScene( &ent );
		}
		else
		{
			trap_R_AddRefEntityToScene (&ent);
		}
	}
	else
	{
		cent->miscTime = 0;
		trap_R_AddRefEntityToScene (&ent);
	}
}


/*
==================
CG_Speaker

Speaker entities can automatically play sounds
==================
*/
static void CG_Speaker( centity_t *cent ) {
	if ( ! cent->currentState.clientNum ) {	// FIXME: use something other than clientNum...
		return;		// not auto triggering
	}

	if ( cg.time < cent->miscTime ) {
		return;
	}

	trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] );

	//	ent->s.frame = ent->wait * 10;
	//	ent->s.clientNum = ent->random * 10;
	cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom();
}

/*
==================
CG_Item
==================
*/
static void CG_Item( centity_t *cent ) {
	refEntity_t			ent;
	entityState_t		*es;
	gitem_t				*item;
	int					msec;
//	float				scale;
    // RPG-X: Marcin: Custom angles for each weapon so they lie on the ground correctly. - 06/12/2008
    const vec3_t weaponangles[WP_NUM_WEAPONS] = {
        { 0,   0,   0   },  // WP_NONE
        { 0,   0,   0   },  // WP_NULL_HAND
        { 52,  280, 18  },  // WP_TRICORDER
        { 48,  26,  33  },  // WP_PADD
        { 335, 210, 347 },  // WP_COFFEE
        { 15,  160, 65  },  // WP_PHASER
        { 5,   10,  70  },  // WP_COMPRESSION_RIFLE
        { 5,   6,   70  },  // WP_TR116
        { 5,   17,  70  },  // WP_GRENADE_LAUNCHER
        { 350, 23,  70  },  // WP_QUANTUM_BURST
        { 15,  187, 80  },  // WP_DISRUPTOR
        { 0,   270, 86  },  // WP_MEDKIT
        { 0,   247, 90  },  // WP_VOYAGER_HYPO
        { 36,  190, 40  },  // WP_DERMAL_REGEN
        { 0,   0,   105 },  // WP_TOOLKIT
        { 0,   210, 90  }   // WP_HYPERSPANNER
    };

	es = &cent->currentState;
	if ( es->modelindex >= bg_numItems ) {
		CG_Error( "Bad item index %i on entity", es->modelindex );
	}

	// if set to invisible, skip
	if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) {
		return;
	}

	item = &bg_itemlist[ es->modelindex ];
	if ( cg_simpleItems.integer && item->giType != IT_TEAM ) {
		memset( &ent, 0, sizeof( ent ) );
		ent.reType = RT_SPRITE;
		VectorCopy( cent->lerpOrigin, ent.origin );
		ent.data.sprite.radius = 14;
	
		ent.customShader = cg_items[es->modelindex].icon;
		ent.shaderRGBA[0] = 255;
		ent.shaderRGBA[1] = 255;
		ent.shaderRGBA[2] = 255;

		if ( es->eFlags & EF_ITEMPLACEHOLDER )
		{
			ent.renderfx |= RF_FORCE_ENT_ALPHA;
			ent.shaderRGBA[3] = 50 + sin(cg.time*0.01)*30;
		}
		else
		{
			ent.shaderRGBA[3] = 255;
		}

		trap_R_AddRefEntityToScene(&ent);

		return;
	}

	// items bob up and down continuously
//	scale = 0.005 + cent->currentState.number * 0.00001;
//	cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) *  scale ) * 4;

	memset (&ent, 0, sizeof(ent));


	// autorotate at one of two speeds
	if ( item->giType == IT_HEALTH ) 
	{
		VectorCopy( cg.autoAnglesFast, cent->lerpAngles );
		AxisCopy( cg.autoAxisFast, ent.axis );
	} 
	else if (item->giType != IT_TEAM) // RPG-X | Marcin | 05/12/2008
    {
		//VectorCopy( cg.autoAngles, cent->lerpAngles );
        //AxisCopy( cg.autoAxis, ent.axis );
        VectorCopy( weaponangles[item->giTag], cent->lerpAngles );
		AnglesToAxis( weaponangles[item->giTag], ent.axis);
	}
	else
	{	// Flags don't rotate at all...
		float frame;
		vec3_t vecs[3];


		// ...but they do animate.
		//frame = (cg.time / 100.0);
		frame = (cg.time * 0.01);
		ent.renderfx|=RF_WRAP_FRAMES;

		ent.oldframe = (int)frame;
		ent.frame = (int)frame+1;
		ent.backlerp = (float)(ent.frame) - frame;

		// and they are scaled too
		if (1)
		{
			AngleVectors(es->angles, vecs[0], vecs[1], vecs[2]);
			VectorScale( vecs[0], 1.6, vecs[0] );
			VectorScale( vecs[1], 1.6, vecs[1] );
			VectorScale( vecs[2], 1.6, vecs[2] );
			AxisCopy( vecs, ent.axis );
		}
		else
		{
			AnglesToAxis(cent->lerpAngles, ent.axis);
			VectorScale( ent.axis[0], 1.6, ent.axis[0] );
			VectorScale( ent.axis[1], 1.6, ent.axis[1] );
			VectorScale( ent.axis[2], 1.6, ent.axis[2] );
			ent.nonNormalizedAxes = qtrue;
		}
		if (item->giTag == PW_BORG_ADAPT)
		{ 
			//ent.customShader = cgs.media.blueFlagShader[3];
		}
		else
		{
			ent.customShader = cgs.media.redFlagShader[3];
		}
	}

	// the weapons have their origin where they attatch to player
	// models, so we need to offset them or they will rotate
	// eccentricly
	if ( item->giType == IT_WEAPON ) {
		weaponInfo_t	*wi;

		wi = &cg_weapons[item->giTag];
		cent->lerpOrigin[0] -= 
			wi->weaponMidpoint[0] * ent.axis[0][0] +
			wi->weaponMidpoint[1] * ent.axis[1][0] +
			wi->weaponMidpoint[2] * ent.axis[2][0];
		cent->lerpOrigin[1] -= 
			wi->weaponMidpoint[0] * ent.axis[0][1] +
			wi->weaponMidpoint[1] * ent.axis[1][1] +
			wi->weaponMidpoint[2] * ent.axis[2][1];
		cent->lerpOrigin[2] -= 
			wi->weaponMidpoint[0] * ent.axis[0][2] +
			wi->weaponMidpoint[1] * ent.axis[1][2] +
			wi->weaponMidpoint[2] * ent.axis[2][2];

		cent->lerpOrigin[2] -= 14;	// an extra height boost
                                    // RPG-X | Marcin | 03/12/2008
                                    // (Was += 8)
	}

	ent.hModel = cg_items[es->modelindex].model;

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

	ent.nonNormalizedAxes = qfalse;

	if ( es->eFlags & EF_ITEMPLACEHOLDER )		// item has been picked up
	{
		if ( es->eFlags & EF_DEAD )				// if item had been droped, don't show at all
			return;

		ent.customShader = cgs.media.weaponPlaceholderShader;
	}

    // RPG-X | Marcin | 06/12/2008
	// increase the size of the weapons when they are presented as items - DON'T!
	/*if ( item->giType == IT_WEAPON ) {
		VectorScale( ent.axis[0], 1.5, ent.axis[0] );
		VectorScale( ent.axis[1], 1.5, ent.axis[1] );
		VectorScale( ent.axis[2], 1.5, ent.axis[2] );
		ent.nonNormalizedAxes = qtrue;
	}*/

	msec = cg.time - cent->miscTime;	// Count from last respawn.
	/*if (cg.predictedPlayerState.introTime > cg.time)
	{	// The stuff is "holodecking in".
		int dtime;

		dtime = cg.predictedPlayerState.introTime - cg.time;
		if (dtime < TIME_FADE_DUR)
		{	// "rez" in.
			float alpha;
			int a;
			
			alpha = 1.0 - ((float)dtime / (float)TIME_FADE_DUR);
			a = alpha * 255.0;
			if (a <= 0)
				a=1;
			ent.shaderRGBA[3] = a;
			ent.renderfx |= RF_FORCE_ENT_ALPHA;
			trap_R_AddRefEntityToScene(&ent);
			ent.renderfx &= ~RF_FORCE_ENT_ALPHA;
		
			// Now draw the static shader over it.
			// Alpha in over half the time, out over half.
			alpha = sin(M_PI*alpha);
			a = alpha * 255.0;
			if (a <= 0)
				a=1;
			ent.customShader = cgs.media.rezOutShader;
			ent.shaderRGBA[0] =
			ent.shaderRGBA[1] =
			ent.shaderRGBA[2] = a;
			trap_R_AddRefEntityToScene( &ent );
			ent.shaderRGBA[0] =
			ent.shaderRGBA[1] =
			ent.shaderRGBA[2] = 255;
		}
	}
	else*/ if (item->giType != IT_TEAM && msec >= 0 && msec < ITEM_SCALEUP_TIME && !(es->eFlags & EF_ITEMPLACEHOLDER)) 
	{	// if just respawned, fade in, but don't do this for flags.
		float alpha;
		int a;
		
		alpha = (float)msec * ITEM_SCALEUP_DIV;
		a = alpha * 255.0;
		if (a <= 0)
			a=1;
		ent.shaderRGBA[3] = a;
		ent.renderfx |= RF_FORCE_ENT_ALPHA;
		trap_R_AddRefEntityToScene(&ent);
		ent.renderfx &= ~RF_FORCE_ENT_ALPHA;
	
		// Now draw the static shader over it.
		// Alpha in over half the time, out over half.
		alpha = sin(M_PI*alpha);
		a = alpha * 255.0;
		if (a <= 0)
			a=1;
		ent.customShader = cgs.media.rezOutShader;
		ent.shaderRGBA[0] =
		ent.shaderRGBA[1] =
		ent.shaderRGBA[2] = a;
		trap_R_AddRefEntityToScene( &ent );
		//what is the point of this next bit???
		ent.shaderRGBA[0] =
		ent.shaderRGBA[1] =
		ent.shaderRGBA[2] = 255;
	}
	else
	{	// add to refresh list  -- normal item
		trap_R_AddRefEntityToScene(&ent);
	}	
}

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

/*
===============
CG_Missile
===============
*/
static void CG_Missile( centity_t *cent, qboolean altfire ) {
	refEntity_t			ent;
	entityState_t		*s1;
	qhandle_t			missile = 0;
	const weaponInfo_t		*weapon;
	int     rpg_tripmines;
	const char	*info;
	
	s1 = &cent->currentState;
	if ( s1->weapon > WP_NUM_WEAPONS ) {
		s1->weapon = 0;
	}
	weapon = &cg_weapons[s1->weapon];

	// calculate the axis
	VectorCopy( s1->angles, cent->lerpAngles);

//	if (cent->currentState.eFlags & EF_ALT_FIRING)
	if (altfire)
	{
		// add trails
		if ( weapon->alt_missileTrailFunc ) 
		{
			weapon->alt_missileTrailFunc( cent, weapon );
		}

		// add dynamic light
		if ( weapon->alt_missileDlight ) {
			trap_R_AddLightToScene(cent->lerpOrigin, weapon->alt_missileDlight, 
				weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] );
		}

		// add missile sound
		if ( weapon->alt_missileSound ) 
		{
			vec3_t	velocity;

			BG_EvaluateTrajectoryDelta( &cent->currentState.pos, cg.time, velocity );
			if (velocity[0] || velocity[1] || velocity[2])
			{
				trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->alt_missileSound );
			}
		}
		//RPG-X: RedTechie - non-admin see no tripmines! But first by popular demand check CVAR
		info = CG_ConfigString( CS_SERVERINFO );
		rpg_tripmines = atoi( Info_ValueForKey( info, "rpg_invisibletripmines" ) );
		if (!weapon->alt_missileModel || (!cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN*/ && rpg_tripmines == 1)) {
			//if there is no missile then we're done
			return;
		}
		missile = weapon->alt_missileModel;
	}
	else
	{
		if (cent->thinkFlag)
		{	// we already grabbed info that was stored on the game side, so use what's already in cent->rawAngles
			//and cent->rawOrigin
		}
		else
		{
			// kef -- get out some info we stored in a very unfortunate manner on the game side
			VectorCopy(cent->currentState.angles2,cent->rawAngles);
			VectorCopy(cent->currentState.angles2,cent->rawOrigin);
			cent->thinkFlag = 1;
		}
		// add trails
		if ( weapon->missileTrailFunc ) 
		{
			weapon->missileTrailFunc( cent, weapon );
		}

		// add dynamic light
		if ( weapon->missileDlight ) {
			trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, 
				weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] );
		}

		// add missile sound
/*		if ( weapon->missileSound ) {
			vec3_t	velocity;

			BG_EvaluateTrajectoryDelta( &cent->currentState.pos, cg.time, velocity );

			trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound );
		}
*/
		if (!weapon->missileModel) {	//if ther is no missile then we're done
			return;
		}
		missile = weapon->missileModel;
	}

	// create the render entity
	memset (&ent, 0, sizeof(ent));
	VectorCopy( cent->lerpOrigin, ent.origin);
	VectorCopy( cent->lerpOrigin, ent.oldorigin);

	if ( cent->currentState.weapon == WP_QUANTUM_BURST ) {
		ent.reType = RT_SPRITE;
		ent.data.sprite.radius = 16;
		ent.data.sprite.rotation = 0;
		trap_R_AddRefEntityToScene( &ent );
		return;
	}

	// flicker between two skins
	ent.skinNum = cg.clientFrame & 1;
	ent.hModel = missile;
	ent.renderfx = RF_NOSHADOW;

	if ( s1->pos.trType == TR_STATIONARY )
	{
		AnglesToAxis( s1->angles, ent.axis );
	}
	else
	{
		// convert direction of travel into axis
		if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) {
			ent.axis[0][2] = 1;
		}

		// spin as it moves
		if ( s1->pos.trType != TR_STATIONARY && (cent->currentState.weapon != WP_DISRUPTOR) ) { //RPG-X-TiM: Stop from spinning O_o  I got dizzy
			//RotateAroundDirection( ent.axis, cg.time / 4 );
			RotateAroundDirection( ent.axis, cg.time * 0.25);
		} else {
			RotateAroundDirection( ent.axis, s1->time );
		}
	}

	// add to refresh list, possibly with quad glow
	CG_AddRefEntityWithPowerups( &ent, s1->powerups, s1->eFlags, &cent->beamData, cent->cloakTime, cent->decloakTime, qfalse );
	if ( s1->eFlags & EF_FIRING )
	{//special code for adding the beam to the attached tripwire mine
		trace_t	trace;
		vec3_t	beamOrg, beamEnd, rgb;
		float	alpha;
		qhandle_t	flareShader;
		
		if ( s1->otherEntityNum2 == TEAM_BLUE )
		{
			VectorSet( rgb, 0.0f, 0.3f, 1.0f );
			alpha = 1.0f - (random() * 0.2);
			flareShader = cgs.media.blueParticleShader;
		}
		else
		{
			VectorSet( rgb, 1.0f, 0.0f, 0.0f );
			alpha = 1.0f - (random() * 0.5);
			flareShader = cgs.media.borgEyeFlareShader;
		}
		VectorCopy( ent.origin, beamOrg );
		VectorMA( beamOrg, -2, ent.axis[0], beamOrg );//forward
		VectorMA( beamOrg, -1022, ent.axis[0], beamEnd);//forward to end, have to reverse it because it actually faces other way

		trap_CM_BoxTrace( &trace, beamOrg, beamEnd, NULL, NULL, 0, MASK_SHOT );
		VectorCopy(trace.endpos, beamEnd);
		FX_AddLine2( beamOrg, beamEnd, 1.0f, 0.35f + ( crandom() * 0.1 ), 0.0f, 0.35f + ( crandom() * 0.1 ), 0.0f, alpha, alpha, rgb, rgb,1.0f, cgs.media.whiteLaserShader );
		//FX_AddSprite( beamOrg, NULL, qfalse, 1.0f + (random() * 2.0f), 0.0f, 0.9f, 0.9f, 0.0f, 0.0f, 0.0f, flareShader );
		FX_AddQuad( beamOrg, ent.axis[0], 1.0f, 1.0f, 2.0f + (crandom() * 1.0f), 0.0f, 0.0f, 1.0f, flareShader );
		FX_AddQuad( beamEnd, trace.plane.normal, 1.0f, 1.0f, 2.0f + (crandom() * 1.0f), 0.0f, 0.0f, 1.0f, flareShader );
	}
}

/*
===============
CG_Mover
===============
*/
static void CG_Mover( centity_t *cent ) {
	refEntity_t			ent;
	entityState_t		*s1;

	s1 = &cent->currentState;

	// create the render entity
	memset (&ent, 0, sizeof(ent));
	VectorCopy( cent->lerpOrigin, ent.origin);
	VectorCopy( cent->lerpOrigin, ent.oldorigin);
	AnglesToAxis( cent->lerpAngles, ent.axis );

	ent.renderfx = RF_NOSHADOW;

	// flicker between two skins (FIXME?)
	ent.skinNum = ( cg.time >> 6 ) & 1;

	// get the model, either as a bmodel or a modelindex
	if ( s1->solid == SOLID_BMODEL ) {
		ent.hModel = cgs.inlineDrawModel[s1->modelindex];
	} else {
		ent.hModel = cgs.gameModels[s1->modelindex];
	}

	// add to refresh list
	trap_R_AddRefEntityToScene(&ent);

	// add the secondary model
	if ( s1->modelindex2 ) {
		ent.skinNum = 0;
		ent.hModel = cgs.gameModels[s1->modelindex2];
		trap_R_AddRefEntityToScene(&ent);
	}

}

/*
===============
CG_Beam

Also called as an event
===============
*/
void CG_Beam( centity_t *cent ) {
	refEntity_t			ent;
	entityState_t		*s1;

	s1 = &cent->currentState;

	// create the render entity
	memset (&ent, 0, sizeof(ent));
	VectorCopy( s1->pos.trBase, ent.origin );
	VectorCopy( s1->origin2, ent.oldorigin );
	AxisClear( ent.axis );
	ent.reType = RT_BEAM;

	ent.renderfx = RF_NOSHADOW;

	// add to refresh list
	trap_R_AddRefEntityToScene(&ent);
}


/*
RPG-X: RedTechie - FIXME: STILL FRICKEN SWAYS its not a sailing ship its a fricken star ship!
===============
CG_Portal
===============
*/
static void CG_Portal( centity_t *cent ) {
	refEntity_t			ent;
	entityState_t		*s1;

	s1 = &cent->currentState;

	// create the render entity
	memset (&ent, 0, sizeof(ent));
	VectorCopy( cent->lerpOrigin, ent.origin );
	VectorCopy( s1->origin2, ent.oldorigin );
	ByteToDir( s1->eventParm, ent.axis[0] );
	PerpendicularVector( ent.axis[1], ent.axis[0] );

	// negating this tends to get the directions like they want
	// we really should have a camera roll value
	VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] );

	CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] );
	ent.reType = RT_PORTALSURFACE;
	
	ent.frame = s1->frame;		// rotation speed - s1->frame
	ent.skinNum = s1->clientNum / 256 * 360;	// roll offset //RPG-X: RedTechie - ent.skinNum = s1->clientNum/256.0 * 360;
	//ent.oldframe = 0;

	// add to refresh list
	trap_R_AddRefEntityToScene(&ent);
}


/*
=========================
CG_AdjustPositionForMover

Also called by client movement prediction code
=========================
*/
void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) {
	centity_t	*cent;
	vec3_t	oldOrigin, origin, deltaOrigin;
	vec3_t	oldAngles, angles, deltaAngles;

	if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) {
		VectorCopy( in, out );
		return;
	}

	cent = &cg_entities[ moverNum ];
	if ( cent->currentState.eType != ET_MOVER && cent->currentState.eType != ET_MOVER_STR ) { //RPG-X | GSIO01 | 13/05/2009
		VectorCopy( in, out );
		return;
	}

	BG_EvaluateTrajectory( &cent->currentState.pos, fromTime, oldOrigin );
	BG_EvaluateTrajectory( &cent->currentState.apos, fromTime, oldAngles );

	BG_EvaluateTrajectory( &cent->currentState.pos, toTime, origin );
	BG_EvaluateTrajectory( &cent->currentState.apos, toTime, angles );

	VectorSubtract( origin, oldOrigin, deltaOrigin );
	VectorSubtract( angles, oldAngles, deltaAngles );

	VectorAdd( in, deltaOrigin, out );

	// FIXME: origin change when on a rotating object
}


/*
=============================
CG_InterpolateEntityPosition
=============================
*/
static void CG_InterpolateEntityPosition( centity_t *cent ) {
	vec3_t		current, next;
	float		f;

	// it would be an internal error to find an entity that interpolates without
	// a snapshot ahead of the current one
	if ( cg.nextSnap == NULL ) {
		CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" );
	}

	f = cg.frameInterpolation;

	// this will linearize a sine or parabolic curve, but it is important
	// to not extrapolate player positions if more recent data is available
	BG_EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, current );
	BG_EvaluateTrajectory( &cent->nextState.pos, cg.nextSnap->serverTime, next );

	cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] );
	cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] );
	cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] );

	BG_EvaluateTrajectory( &cent->currentState.apos, cg.snap->serverTime, current );
	BG_EvaluateTrajectory( &cent->nextState.apos, cg.nextSnap->serverTime, next );

	cent->lerpAngles[0] = LerpAngle( current[0], next[0], f );
	cent->lerpAngles[1] = LerpAngle( current[1], next[1], f );
	cent->lerpAngles[2] = LerpAngle( current[2], next[2], f );

}

/*
===============
CG_CalcEntityLerpPositions

===============
*/
static void CG_CalcEntityLerpPositions( centity_t *cent ) {
	if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) {
		CG_InterpolateEntityPosition( cent );
		return;
	}
	
	// just use the current frame and evaluate as best we can
	BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, cent->lerpOrigin );
	BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );

	// adjust for riding a mover if it wasn't rolled into the predicted
	// player state
	if ( cent != &cg.predictedPlayerEntity ) {
		CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, 
		cg.snap->serverTime, cg.time, cent->lerpOrigin );
	}
}

/*
===============
CG_AddCEntity

===============
*/
static void CG_AddCEntity( centity_t *cent ) {
	// event-only entities will have been dealt with already
	if ( cent->currentState.eType >= ET_EVENTS ) {
		return;
	}

	// calculate the current origin
	CG_CalcEntityLerpPositions( cent );

	// add automatic effects
	CG_EntityEffects( cent );

	switch ( cent->currentState.eType ) {
	default:
		CG_Error( "Bad entity type: %i\n", cent->currentState.eType );
		break;
	case ET_TRIC_STRING:
	case ET_INVISIBLE:
	case ET_PUSH_TRIGGER:
	case ET_TELEPORT_TRIGGER:
		break;
	case ET_USEABLE:		// e.g. detpacks
		CG_Useable( cent );
		break;
	case ET_GENERAL:
		CG_General( cent );
		break;
	case ET_PLAYER:
		CG_Player( cent );
		break;
	case ET_ITEM:
		CG_Item( cent );
		break;
	case ET_ALT_MISSILE:
		CG_Missile( cent, qtrue );
		break;
	case ET_MISSILE:
		CG_Missile( cent, qfalse );
		break;
	case ET_MOVER:
	case ET_MOVER_STR:
		CG_Mover( cent );
		break;
	case ET_BEAM:
		CG_Beam( cent );
		break;
	case ET_PORTAL:
		CG_Portal( cent );
		break;
	case ET_SPEAKER:
		CG_Speaker( cent );
		break;
	case ET_LASER:
		CG_LaserSight( cent );
		break;
	case ET_TURBOLIFT:
		CG_Turbolift( cent );
		break;
	}
}

/*
===============
CG_AddPacketEntities

===============
*/
void CG_AddPacketEntities( void ) {
	int					num;
	centity_t			*cent;
	playerState_t		*ps;

	// set cg.frameInterpolation
	if ( cg.nextSnap ) {
		int		delta;

		delta = (cg.nextSnap->serverTime - cg.snap->serverTime);
		if ( delta == 0 ) {
			cg.frameInterpolation = 0;
		} else {
			cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta;
		}
	} else {
		cg.frameInterpolation = 0;	// actually, it should never be used, because 
									// no entities should be marked as interpolating
	}


	// the auto-rotating items will all have the same axis

    // RPG-X | Marcin | 03/12/2008
    // We don't want dropped weapons to rotate at all
    // -- NOT USED ANY MORE THOUGH --
    cg.autoAngles[0] = 0;
	cg.autoAngles[1] = 0;
	cg.autoAngles[2] = 68;

	cg.autoAnglesFast[0] = 0;
	cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024;
	cg.autoAnglesFast[2] = 0;

	AnglesToAxis( cg.autoAngles, cg.autoAxis );
	AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast );

	// generate and add the entity from the playerstate
	ps = &cg.predictedPlayerState;
	BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse );
	CG_AddCEntity( &cg.predictedPlayerEntity );

	// lerp the non-predicted value for lightning gun origins
	CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] );

	// add each entity sent over by the server
	for ( num = 0 ; num < cg.snap->numEntities ; num++ ) {
		cent = &cg_entities[ cg.snap->entities[ num ].number ];
		CG_AddCEntity( cent );
	}
}

/*
==================
CG_LaserSight
  Creates the laser
==================
*/

static void CG_LaserSight( centity_t *cent )  {
	refEntity_t			ent;

	// create the render entity
	memset (&ent, 0, sizeof(ent));
	VectorCopy( cent->lerpOrigin, ent.origin);
	VectorCopy( cent->lerpOrigin, ent.oldorigin);

	if (cent->currentState.eventParm == 1)
	{
		ent.reType = RT_SPRITE;
		//ent.radius = 2;
		//ent.rotation = 0;
		ent.data.sprite.radius = 2;
		ent.customShader = cgs.media.laserShader;
		trap_R_AddRefEntityToScene( &ent );
	}
	else	{
		trap_R_AddLightToScene(ent.origin, 200, 1, 1, 1);
	}

	
}

/*
==================
CG_Turbolift
A client complement
to the turbolift ent,
this plays the sound
FX whilst it is in action
==================
*/

static void CG_Turbolift( centity_t* cent )
{
	int i;
	centity_t *player;
	//TiM - find all of the cents inside the lift, and make it so they orient to their view angles.
	//Otherwise, when they teleport, the entire body snaps around, looking weird and/or painful.
	
	if ( cent->currentState.time2 <= 0 )
		return;

	for ( i = 0; i < MAX_CLIENTS; i++ )
	{
		player = &cg_entities[i];

		if ( !player )
			continue;

		if ( ( player->lerpOrigin[0] > cent->currentState.angles2[0] && player->lerpOrigin[0] < cent->currentState.origin2[0] )
			 && ( player->lerpOrigin[1] > cent->currentState.angles2[1] && player->lerpOrigin[1] < cent->currentState.origin2[1] )
			  && ( player->lerpOrigin[2] > cent->currentState.angles2[2] && player->lerpOrigin[2] < cent->currentState.origin2[2] ) )
		{
			//time2 = startTime+waittime
			if ( cent->currentState.time2 > 0 ) 
			{
				cg_liftEnts[player->currentState.clientNum] = cent->currentState.time2;
			}
			else
			{
				cg_liftEnts[player->currentState.clientNum] = 0;
			}
		}
	}

	trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, //cent->lerpOrigin
			cgs.gameSounds[ cent->currentState.loopSound ] );
}

/*static void CG_Turbolift( centity_t* cent )
{
	if ( cent->currentState.eventParm <= 0 )
	{
		cent->deathTime = 0;
		cent->miscTime = 0;
		return;
	}

	//Init sound
	if ( cent->miscTime == 0 )
	{
		cent->miscTime = cg.time + cent->currentState.modelindex2; //set the end of the wait time
	}

	if ( cg.time < cent->miscTime )
	{
		trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, //cent->lerpOrigin
			cgs.gameSounds[ cent->currentState.loopSound ] );

		return;
	}
	
	if ( cent->deathTime == 0 )
	{
		trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.otherEntityNum2] );
		cent->deathTime = 1;
	}
}*/
