
#include "../../idlib/precompiled.h"
#pragma hdrstop

#include "../Game_local.h"
#include "../vehicle/Vehicle.h"

#define MAX_MISSILE_JOINTS 4
#define MAX_HOVER_JOINTS 4
class rvMonsterStroggHover : public idAI {
public:

	CLASS_PROTOTYPE( rvMonsterStroggHover );

	rvMonsterStroggHover ( void );
	~rvMonsterStroggHover ( void );

	void					InitSpawnArgsVariables( void );
	void					Spawn				( void );
	void					Save				( idSaveGame *savefile ) const;
	void					Restore				( idRestoreGame *savefile );
		
	virtual void			Think				( void );
	
	virtual bool			Collide				( const trace_t &collision, const idVec3 &velocity );
	virtual	void			Damage				( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
	virtual void			OnDeath				( void );
	virtual	void			DeadMove			( void );

	virtual bool			SkipImpulse			( idEntity *ent, int id );
	
	virtual int				FilterTactical		( int availableTactical );

	virtual void			GetDebugInfo		( debugInfoProc_t proc, void* userData );

	virtual void			Hide( void );
	virtual void			Show( void );

	void					StartHeadlight					( void );
	void					StopHeadlight					( void );

protected:

//	rvAIAction				actionRocketAttack;
//	rvAIAction				actionBlasterAttack;
	rvAIAction				actionMGunAttack;
	rvAIAction				actionMissileAttack;
	rvAIAction				actionBombAttack;
	rvAIAction				actionStrafe;
	rvAIAction				actionCircleStrafe;
	
	
	virtual bool			CheckActions				( void );
	virtual void			OnEnemyChange				( idEntity* oldEnemy );
	virtual void			OnStartMoving				( void );

	virtual const char*		GetIdleAnimName				( void );

private:

	idEntityPtr<idEntity>	marker;
	idVec3					attackPosOffset;
	bool					inPursuit;
	int						holdPosTime;

	int						strafeTime;
	bool					strafeRight;
	bool					circleStrafing;
	float					deathPitch;
	float					deathRoll;
	float					deathPitchRate;
	float					deathYawRate;
	float					deathRollRate;
	float					deathSpeed;
	float					deathGrav;
	
	int						markerCheckTime;

	bool					MarkerPosValid					( void );
	void					TryStartPursuit					( void );
	void					Pursue							( void );
	void					CircleStrafe					( void );
	void					Evade							( bool left );

	int						mGunFireRate;
	int						missileFireRate;
	int						bombFireRate;
	int						nextMGunFireTime;
	int						nextMissileFireTime;
	int						nextBombFireTime;

	int						mGunMinShots;
	int						mGunMaxShots;
	int						missileMinShots;
	int						missileMaxShots;
	int						bombMinShots;
	int						bombMaxShots;

	int						shots;

	int						evadeDebounce;
	int						evadeDebounceRate;
	float					evadeChance;
	float					evadeSpeed;
	float					strafeSpeed;
	float					circleStrafeSpeed;

	rvClientEffectPtr		effectDust;
	rvClientEffectPtr		effectHover[MAX_HOVER_JOINTS];
	rvClientEffectPtr		effectHeadlight;

	jointHandle_t			jointDust;
	int						numHoverJoints;
	jointHandle_t			jointHover[MAX_HOVER_JOINTS];
	jointHandle_t			jointBomb;
	jointHandle_t			jointMGun;
	int						numMissileJoints;
	jointHandle_t			jointMissile[MAX_MISSILE_JOINTS];
	jointHandle_t			jointHeadlight;
	jointHandle_t			jointHeadlightControl;

	renderLight_t			renderLight;
	int						lightHandle;	
//	bool					lightOn;

	void					DoNamedAttack					( const char* attackName, jointHandle_t joint );

	void					UpdateLightDef					( void );

	bool					CheckAction_Strafe				( rvAIAction* action, int animNum );
	bool					CheckAction_CircleStrafe		( rvAIAction* action, int animNum );
	bool					CheckAction_BombAttack			( rvAIAction* action, int animNum );
	//virtual bool			CheckAction_EvadeLeft			( rvAIAction* action, int animNum );
	//virtual bool			CheckAction_EvadeRight			( rvAIAction* action, int animNum );

//	stateResult_t			State_Torso_BlasterAttack		( const stateParms_t& parms );
//	stateResult_t			State_Torso_RocketAttack		( const stateParms_t& parms );
	stateResult_t			State_Torso_MGunAttack			( const stateParms_t& parms );
	stateResult_t			State_Torso_MissileAttack		( const stateParms_t& parms );
	stateResult_t			State_Torso_BombAttack			( const stateParms_t& parms );
//	stateResult_t			State_Torso_EvadeLeft			( const stateParms_t& parms );
//	stateResult_t			State_Torso_EvadeRight			( const stateParms_t& parms );
	stateResult_t			State_Torso_Strafe				( const stateParms_t& parms );
	stateResult_t			State_Torso_CircleStrafe		( const stateParms_t& parms );
	stateResult_t			State_CircleStrafe				( const stateParms_t& parms );
	stateResult_t			State_DeathSpiral				( const stateParms_t& parms );
	stateResult_t			State_Pursue					( const stateParms_t& parms );

	CLASS_STATES_PROTOTYPE ( rvMonsterStroggHover );
};

CLASS_DECLARATION( idAI, rvMonsterStroggHover )
END_CLASS

/*
================
rvMonsterStroggHover::rvMonsterStroggHover
================
*/
rvMonsterStroggHover::rvMonsterStroggHover ( ) {
	effectDust  = NULL;
	for ( int i = 0; i < MAX_HOVER_JOINTS; i++ ) {
		effectHover[i] = NULL;
	}
	effectHeadlight = NULL;
	
	shots		= 0;
	strafeTime	= 0;
	strafeRight = false;
	circleStrafing = false;
	evadeDebounce = 0;
	deathPitch	= 0;
	deathRoll	= 0;
	deathPitchRate	= 0;
	deathYawRate	= 0;
	deathRollRate	= 0;
	deathSpeed	= 0;
	deathGrav	= 0;

	markerCheckTime = 0;

	marker		= NULL;
	attackPosOffset.Zero();
	inPursuit	= false;
	holdPosTime = 0;

	nextMGunFireTime	= 0;
	nextMissileFireTime = 0;
	nextBombFireTime	= 0;

	lightHandle	= -1;
}

/*
================
rvMonsterStroggHover::~rvMonsterStroggHover
================
*/
rvMonsterStroggHover::~rvMonsterStroggHover ( ) {
	if ( lightHandle != -1 ) {
		gameRenderWorld->FreeLightDef( lightHandle );
		lightHandle = -1;
	}
}

void rvMonsterStroggHover::InitSpawnArgsVariables( void )
{
	numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" ));
	for ( int i = 0; i < numHoverJoints; i++ ) {
		jointHover[i]	= animator.GetJointHandle ( spawnArgs.GetString( va("joint_hover%d",i+1) ) );
	}
	
	jointDust = animator.GetJointHandle ( spawnArgs.GetString( "joint_dust" ) );

	jointMGun		= animator.GetJointHandle ( spawnArgs.GetString( "joint_mgun" ) );
	numMissileJoints = idMath::ClampInt(0,MAX_MISSILE_JOINTS,spawnArgs.GetInt( "num_missile_joints", "1" ));
	for ( int i = 0; i < numMissileJoints; i++ ) {
		jointMissile[i]	= animator.GetJointHandle ( spawnArgs.GetString( va("joint_missile%d",i+1) ) );
	}
	jointBomb		= animator.GetJointHandle ( spawnArgs.GetString( "joint_bomb" ) );

	mGunFireRate = SEC2MS(spawnArgs.GetFloat( "mgun_fire_rate", "0.1" ));
	missileFireRate = SEC2MS(spawnArgs.GetFloat( "missile_fire_rate", "0.25" ));
	bombFireRate = SEC2MS(spawnArgs.GetFloat( "bomb_fire_rate", "0.5" ));

	mGunMinShots = spawnArgs.GetInt( "mgun_minShots", "20" );
	mGunMaxShots = spawnArgs.GetInt( "mgun_maxShots", "40" );
	missileMinShots = spawnArgs.GetInt( "missile_minShots", "4" );
	missileMaxShots = spawnArgs.GetInt( "missile_maxShots", "12" );
	bombMinShots = spawnArgs.GetInt( "bomb_minShots", "5" );
	bombMaxShots = spawnArgs.GetInt( "bomb_maxShots", "20" );

	evadeDebounceRate = SEC2MS(spawnArgs.GetFloat( "evade_rate", "0" ));
	evadeChance = spawnArgs.GetFloat( "evade_chance", "0.6" );
	evadeSpeed = spawnArgs.GetFloat( "evade_speed", "400" );
	strafeSpeed = spawnArgs.GetFloat( "strafe_speed", "500" );
	circleStrafeSpeed = spawnArgs.GetFloat( "circle_strafe_speed", "200" );

	//LIGHT
	jointHeadlight	= animator.GetJointHandle ( spawnArgs.GetString( "joint_light" ) );
	jointHeadlightControl = animator.GetJointHandle ( spawnArgs.GetString( "joint_light_control" ) );
}

void rvMonsterStroggHover::Hide( void )
{
	StopHeadlight();
	idAI::Hide();
}

void rvMonsterStroggHover::Show( void )
{
	idAI::Show();
	StartHeadlight();
}

void rvMonsterStroggHover::StartHeadlight( void )
{
	if ( jointHeadlight != INVALID_JOINT )
	{
		lightHandle = -1;
		if ( cvarSystem->GetCVarInteger( "com_machineSpec"	) > 1 && spawnArgs.GetString("mtr_light") ) {
			idVec3		color;
			//const char* temp;

			const idMaterial *headLightMaterial = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false );
			if ( headLightMaterial )
			{
				renderLight.shader	  = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false );
				renderLight.pointLight  = spawnArgs.GetBool( "light_pointlight", "1" );
// RAVEN BEGIN
// dluetscher: added detail levels to render lights
				renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL;
// RAVEN END
				spawnArgs.GetVector( "light_color", "0 0 0", color );
				renderLight.shaderParms[ SHADERPARM_RED ]		= color[0];
				renderLight.shaderParms[ SHADERPARM_GREEN ]		= color[1];
				renderLight.shaderParms[ SHADERPARM_BLUE ]		= color[2];
				renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;

				renderLight.lightRadius[0] = renderLight.lightRadius[1] = 
					renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "light_radius" );

				if ( !renderLight.pointLight ) {
					renderLight.target = spawnArgs.GetVector( "light_target" );
					renderLight.up	   = spawnArgs.GetVector( "light_up" );
					renderLight.right  = spawnArgs.GetVector( "light_right" );
					renderLight.end	   = spawnArgs.GetVector( "light_target" );;
					rvNormalizeProjectedRenderLight( renderLight, GetName(), "strogg-hover-headlight" );
				}

				//lightOn = spawnArgs.GetBool( "start_on", "1" );

				lightHandle = gameRenderWorld->AddLightDef( &renderLight );
			}
		}
		// Hide flare surface if there is one
		/*
		temp = spawnArgs.GetString ( "light_flaresurface", "" );
		if ( temp && *temp ) {
			parent->ProcessEvent ( &EV_HideSurface, temp );
		}
		*/

		// Sounds shader when turning light
		//spawnArgs.GetString ( "snd_on", "", soundOn );
		
		// Sound shader when turning light off
		//spawnArgs.GetString ( "snd_off", "", soundOff);
		
		UpdateLightDef ( );
	}
}

void rvMonsterStroggHover::StopHeadlight( void )
{
	if ( lightHandle != -1 ) {
		gameRenderWorld->FreeLightDef ( lightHandle );
		lightHandle = -1;
	}
	memset ( &renderLight, 0, sizeof(renderLight) );
}
/*
================
rvMonsterStroggHover::Spawn
================
*/
void rvMonsterStroggHover::Spawn ( void ) {
//	actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK );
//	actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK );
	actionMGunAttack.Init ( spawnArgs, "action_mGunAttack", "Torso_MGunAttack", AIACTIONF_ATTACK );
	actionMissileAttack.Init ( spawnArgs, "action_missileAttack", "Torso_MissileAttack", AIACTIONF_ATTACK );
	actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK );

	actionStrafe.Init  ( spawnArgs, "action_strafe", "Torso_Strafe",	0 );
	actionCircleStrafe.Init  ( spawnArgs, "action_circleStrafe", "Torso_CircleStrafe",	AIACTIONF_ATTACK );
	
	InitSpawnArgsVariables();

	evadeDebounce = 0;

	numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" ));
	for ( int i = 0; i < numHoverJoints; i++ ) {
		if ( jointHover[i] != INVALID_JOINT ) {
			effectHover[i] = PlayEffect ( "fx_hover", jointHover[i], true );
		}
	}
	
	if ( !marker ) {
		marker = gameLocal.SpawnEntityDef( "target_null" );
	}

	//LIGHT
	StopHeadlight();
	StartHeadlight();

	if ( jointHeadlight != INVALID_JOINT )
	{
		effectHeadlight = PlayEffect( "fx_headlight", jointHeadlight, true );
	}
}

/*
================
rvMonsterStroggHover::GetDebugInfo
================
*/
void rvMonsterStroggHover::GetDebugInfo( debugInfoProc_t proc, void* userData ) {
	// Base class first
	idAI::GetDebugInfo ( proc, userData );
	
	proc ( "rvMonsterStroggHover", "action_mGunAttack",	aiActionStatusString[actionMGunAttack.status], userData );
	proc ( "rvMonsterStroggHover", "action_missileAttack",aiActionStatusString[actionMissileAttack.status], userData );
	proc ( "rvMonsterStroggHover", "action_bombAttack",	aiActionStatusString[actionBombAttack.status], userData );
	proc ( "rvMonsterStroggHover", "action_strafe",		aiActionStatusString[actionStrafe.status], userData );
	proc ( "rvMonsterStroggHover", "action_circleStrafe",aiActionStatusString[actionCircleStrafe.status], userData );

	proc ( "rvMonsterStroggHover", "inPursuit",			inPursuit?"true":"false", userData );
	proc ( "rvMonsterStroggHover", "marker",			(!inPursuit||marker==NULL)?"0 0 0":va("%f %f %f",marker->GetPhysics()->GetOrigin().x,marker->GetPhysics()->GetOrigin().y,marker->GetPhysics()->GetOrigin().z), userData );
	proc ( "rvMonsterStroggHover", "holdPosTime",		va("%d",holdPosTime), userData );
	
	proc ( "rvMonsterStroggHover", "circleStrafing",	circleStrafing?"true":"false", userData );
	proc ( "rvMonsterStroggHover", "strafeRight",		strafeRight?"true":"false", userData );
	proc ( "rvMonsterStroggHover", "strafeTime",		va("%d",strafeTime), userData );

	proc ( "rvMonsterStroggHover", "mGunFireRate",		va("%d",mGunFireRate), userData );
	proc ( "rvMonsterStroggHover", "missileFireRate",	va("%d",missileFireRate), userData );
	proc ( "rvMonsterStroggHover", "bombFireRate",		va("%d",bombFireRate), userData );

	proc ( "rvMonsterStroggHover", "mGunMinShots",		va("%d",mGunMinShots), userData );
	proc ( "rvMonsterStroggHover", "mGunMaxShots",		va("%d",mGunMaxShots), userData );
	proc ( "rvMonsterStroggHover", "missileMinShots",	va("%d",missileMinShots), userData );
	proc ( "rvMonsterStroggHover", "missileMaxShots",	va("%d",missileMaxShots), userData );
	proc ( "rvMonsterStroggHover", "bombMinShots",		va("%d",bombMinShots), userData );
	proc ( "rvMonsterStroggHover", "bombMaxShots",		va("%d",bombMaxShots), userData );
	proc ( "rvMonsterStroggHover", "nextMGunFireTime",	va("%d",nextMGunFireTime), userData );
	proc ( "rvMonsterStroggHover", "nextMissileFireTime",va("%d",nextMissileFireTime), userData );
	proc ( "rvMonsterStroggHover", "nextBombFireTime",	va("%d",nextBombFireTime), userData );
	proc ( "rvMonsterStroggHover", "shots",				va("%d",shots), userData );

	proc ( "rvMonsterStroggHover", "evadeDebounce",		va("%d",evadeDebounce), userData );
	proc ( "rvMonsterStroggHover", "evadeDebounceRate",	va("%d",evadeDebounceRate), userData );
	proc ( "rvMonsterStroggHover", "evadeChance",		va("%g",evadeChance), userData );
	proc ( "rvMonsterStroggHover", "evadeSpeed",		va("%g",evadeSpeed), userData );
	proc ( "rvMonsterStroggHover", "strafeSpeed",		va("%g",strafeSpeed), userData );
	proc ( "rvMonsterStroggHover", "circleStrafeSpeed",	va("%g",circleStrafeSpeed), userData );
}

/*
=====================
rvMonsterStroggHover::UpdateLightDef
=====================
*/
void rvMonsterStroggHover::UpdateLightDef ( void ) {
	if ( jointHeadlight != INVALID_JOINT )
	{
		idVec3 origin;
		idMat3 axis;

		if ( jointHeadlightControl != INVALID_JOINT ) {
			idAngles jointAng;
			jointAng.Zero();
			jointAng.yaw = 10.0f * sin( ( (gameLocal.GetTime()%2000)-1000 ) / 1000.0f * idMath::PI );
			jointAng.pitch = 7.5f * sin( ( (gameLocal.GetTime()%4000)-2000 ) / 2000.0f * idMath::PI );

			animator.SetJointAxis( jointHeadlightControl, JOINTMOD_WORLD, jointAng.ToMat3() );
		}

		GetJointWorldTransform ( jointHeadlight, gameLocal.time, origin, axis );
	
		//origin += (localOffset * axis);
		
		// Include this part in the total bounds 
		// FIXME: bounds are local
		//parent->AddToBounds ( worldOrigin );
		//UpdateOrigin ( );
		
		if ( lightHandle != -1 ) {
			renderLight.origin = origin;
			renderLight.axis   = axis;	
			
			gameRenderWorld->UpdateLightDef( lightHandle, &renderLight );	
		}
	}
}

/*
================
rvMonsterStroggHover::Think
================
*/
void rvMonsterStroggHover::Think ( void ) {
	idAI::Think ( );
	
	if ( !aifl.dead )
	{
		// If thinking we should play an effect on the ground under us
		if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) {
			trace_t tr;
			idVec3	origin;
			idMat3	axis;
			
			// Project the effect 80 units down from the bottom of our bbox
			GetJointWorldTransform ( jointDust, gameLocal.time, origin, axis );
			
	// RAVEN BEGIN
	// ddynerman: multiple clip worlds
			gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * (GetPhysics()->GetBounds()[0][2]+80.0f), CONTENTS_SOLID, this );
	// RAVEN END

			// Start the dust effect if not already started
			if ( !effectDust ) {
				effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true );
			}
			
			// If the effect is playing we should update its attenuation as well as its origin and axis
			if ( effectDust ) {
				effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) );
				effectDust->SetOrigin ( tr.endpos );
				effectDust->SetAxis ( tr.c.normal.ToMat3() );
			}
			
			// If the hover effect is playing we can set its end origin to the ground
			/*
			if ( effectHover ) {
				effectHover->SetEndOrigin ( tr.endpos );
			}
			*/
		} else if ( effectDust ) {
			effectDust->Stop ( );
			effectDust = NULL;
		}

		//Try to circle strafe or pursue
		if ( circleStrafing )
		{
			CircleStrafe();
		}
		else if ( !inPursuit )
		{
			if ( !aifl.action && move.fl.done && !aifl.scripted )
			{
				if ( GetEnemy() )
				{
					if ( DistanceTo( GetEnemy() ) > 2000.0f 
						|| (GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetEnemy()->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin())) > 1000.0f )
					{//enemy is far away or moving away from us at a pretty decent speed
						TryStartPursuit();
					}
				}
			}
		}
		else
		{
			Pursue();
		}

		//Dodge
		if ( !circleStrafing ) {
			if( combat.shotAtTime && gameLocal.GetTime() - combat.shotAtTime < 1000.0f ) {
				if ( nextBombFireTime < gameLocal.GetTime() - 3000 ) {
					if ( gameLocal.random.RandomFloat() > evadeChance ) {
						//40% chance of ignoring it - makes them dodge rockets less often but bullets more often?
						combat.shotAtTime = 0;
					} else if ( evadeDebounce < gameLocal.GetTime() ) {
						//ramps down from 400 to 100 over 1 second
						float speed = evadeSpeed - ((((float)(gameLocal.GetTime()-combat.shotAtTime))/1000.0f)*(evadeSpeed-(evadeSpeed*0.25f)));
						idVec3 evadeVel = viewAxis[1] * ((combat.shotAtAngle >= 0)?-1:1) * speed;
						evadeVel.z *= 0.5f;
						move.addVelocity += evadeVel;
						move.addVelocity.Normalize();
						move.addVelocity *= speed;
						/*
						if ( move.moveCommand < NUM_NONMOVING_COMMANDS ) {
							//just need to do it once?
							combat.shotAtTime = 0;
						}
						*/
						if ( evadeDebounceRate > 1 )
						{
							evadeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt( evadeDebounceRate ) + (ceil(((float)evadeDebounceRate)/2.0f));
						}
					}
				}
			}
		}

		//If using melee rush to nav to him, stop when we're close enough to attack
		if ( combat.tacticalCurrent == AITACTICAL_MELEE 
			&& move.moveCommand == MOVE_TO_ENEMY
			&& !move.fl.done
			&& nextBombFireTime < gameLocal.GetTime() - 3000
			&& enemy.fl.visible && DistanceTo( GetEnemy() ) < 2000.0f ) {
			StopMove( MOVE_STATUS_DONE );
			ForceTacticalUpdate();
		} else {
			//whenever we're not in the middle of something, force an update of our tactical
			if ( !aifl.action ) {
				if ( !aasFind ) {
					if ( move.fl.done ) {
						if ( !inPursuit && !circleStrafing ) {
							ForceTacticalUpdate();
						}
					}
				}
			}
		}
	}

	//update light
//	if ( lightOn ) {
		UpdateLightDef ( );
//	}
}

/*
============
rvMonsterStroggHover::OnStartMoving
============
*/
void rvMonsterStroggHover::OnStartMoving ( void ) {
	idAI::OnStartMoving();
	if ( move.moveCommand == MOVE_TO_ENEMY ) {
		move.range = combat.meleeRange;
	}
}

/*
================
rvMonsterStroggHover::Save
================
*/
void rvMonsterStroggHover::Save ( idSaveGame *savefile ) const {
	savefile->WriteInt ( shots );
	savefile->WriteInt ( strafeTime	);
	savefile->WriteBool ( strafeRight );
	savefile->WriteBool ( circleStrafing );
	savefile->WriteFloat ( deathPitch );
	savefile->WriteFloat ( deathRoll );
	savefile->WriteFloat ( deathPitchRate );
	savefile->WriteFloat ( deathYawRate );
	savefile->WriteFloat ( deathRollRate );
	savefile->WriteFloat ( deathSpeed );
	savefile->WriteFloat ( deathGrav );
	savefile->WriteInt	( markerCheckTime );

	savefile->WriteVec3( attackPosOffset );
	
//	actionRocketAttack.Save ( savefile );
//	actionBlasterAttack.Save ( savefile );
	actionMGunAttack.Save ( savefile );
	actionMissileAttack.Save ( savefile );
	actionBombAttack.Save ( savefile );
	actionStrafe.Save ( savefile );
	actionCircleStrafe.Save ( savefile );
	
	for ( int i = 0; i < numHoverJoints; i++ ) {
		effectHover[i].Save ( savefile );
	}
	
	effectDust.Save ( savefile );
	effectHeadlight.Save ( savefile );

	marker.Save( savefile );
	savefile->WriteBool ( inPursuit );
	savefile->WriteInt ( holdPosTime );
	savefile->WriteInt ( nextMGunFireTime );
	savefile->WriteInt ( nextMissileFireTime );
	savefile->WriteInt ( nextBombFireTime );

	savefile->WriteRenderLight ( renderLight );
	savefile->WriteInt ( lightHandle );

	savefile->WriteInt( evadeDebounce );
}

/*
================
rvMonsterStroggHover::Restore
================
*/
void rvMonsterStroggHover::Restore ( idRestoreGame *savefile ) {
	savefile->ReadInt ( shots );
	savefile->ReadInt ( strafeTime	);
	savefile->ReadBool ( strafeRight );
	savefile->ReadBool ( circleStrafing );
	savefile->ReadFloat ( deathPitch );
	savefile->ReadFloat ( deathRoll );
	savefile->ReadFloat ( deathPitchRate );
	savefile->ReadFloat ( deathYawRate );
	savefile->ReadFloat ( deathRollRate );
	savefile->ReadFloat ( deathSpeed );
	savefile->ReadFloat ( deathGrav );
	savefile->ReadInt	( markerCheckTime );

	savefile->ReadVec3( attackPosOffset );
	
//	actionRocketAttack.Restore ( savefile );
//	actionBlasterAttack.Restore ( savefile );
	actionMGunAttack.Restore ( savefile );
	actionMissileAttack.Restore ( savefile );
	actionBombAttack.Restore ( savefile );
	actionStrafe.Restore ( savefile );
	actionCircleStrafe.Restore ( savefile );
	
	InitSpawnArgsVariables();
	//NOTE: if the def file changes the the number of numHoverJoints, this will be BAD...
	for ( int i = 0; i < numHoverJoints; i++ ) {
		effectHover[i].Restore ( savefile );
	}
	
	effectDust.Restore ( savefile );
	effectHeadlight.Restore ( savefile );

	marker.Restore( savefile );
	savefile->ReadBool ( inPursuit );
	savefile->ReadInt ( holdPosTime );
	savefile->ReadInt ( nextMGunFireTime );
	savefile->ReadInt ( nextMissileFireTime );
	savefile->ReadInt ( nextBombFireTime );

	savefile->ReadRenderLight ( renderLight );
	savefile->ReadInt ( lightHandle );
	if ( lightHandle != -1 ) {
		//get the handle again as it's out of date after a restore!
		rvNormalizeProjectedRenderLight( renderLight, GetName(), "strogg-hover-headlight" );
		lightHandle = gameRenderWorld->AddLightDef( &renderLight );
	}

	savefile->ReadInt ( evadeDebounce );
}

/*
================
rvMonsterStroggHover::Collide
================
*/
bool rvMonsterStroggHover::Collide( const trace_t &collision, const idVec3 &velocity ) {
	if ( aifl.dead ) { 
		StopHeadlight();
		//stop headlight
		if ( effectHeadlight ) {
			effectHeadlight->Stop ( );
			effectHeadlight = NULL;
		}
		// Stop the crash & burn effect
		for ( int i = 0; i < numHoverJoints; i++ ) {
			if ( effectHover[i] ) {
				effectHover[i]->Stop ( );
				effectHover[i] = NULL;
			}
		}
		gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
		SetState ( "State_Remove" );
		return false;
	}
	return idAI::Collide( collision, velocity );
}

/*
================
rvMonsterStroggHover::Damage
================
*/
void rvMonsterStroggHover::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, 
								  const char *damageDefName, const float damageScale, const int location ) {
	if ( attacker == this ) {
		return;
	}
	bool wasDead = aifl.dead;
	idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );

	if ( !wasDead && aifl.dead ) {
		SetState( "State_DeathSpiral" );
	}
}
/*
================
rvMonsterStroggHover::OnDeath
================
*/
void rvMonsterStroggHover::OnDeath ( void ) {
	// Stop the dust effect
	if ( effectDust ) {
		effectDust->Stop ( );
		effectDust = NULL;
	}

	// Stop the hover effect
	for ( int i = 0; i < numHoverJoints; i++ ) {
		if ( effectHover[i] ) {
			effectHover[i]->Stop ( );
			effectHover[i] = NULL;
		}
	}

	idAI::OnDeath ( );
}

/*
=====================
rvMonsterStroggHover::DeadMove
=====================
*/
void rvMonsterStroggHover::DeadMove( void ) {
	DeathPush ( );
	physicsObj.UseVelocityMove( true );
	RunPhysics();
}

/*
=====================
rvMonsterStroggHover::SkipImpulse
=====================
*/
bool rvMonsterStroggHover::SkipImpulse( idEntity* ent, int id ) {	
	return ((ent==this) || (move.moveCommand==MOVE_RV_PLAYBACK));
}

/*
================
rvMonsterStroggHover::CheckAction_Strafe
================
*/
bool rvMonsterStroggHover::CheckAction_Strafe ( rvAIAction* action, int animNum ) {
	if ( inPursuit && !holdPosTime ) {
		return false;
	}

	if ( !enemy.fl.visible ) {
		return false;
	}

	if ( !enemy.fl.inFov ) {
		return false;
	}

	if ( !move.fl.done ) {
		return false;
	}
	if ( evadeDebounce >= gameLocal.GetTime() ) {
		return false;
	}

	if ( animNum != -1 && !TestAnimMove ( animNum ) ) {
		//well, at least try a new attack position
		if ( combat.tacticalCurrent == AITACTICAL_RANGED ) {
			combat.tacticalUpdateTime = 0;
		}
		return false;
	}
	return true;
}

/*
================
rvMonsterStroggHover::CheckAction_CircleStrafe
================
*/
bool rvMonsterStroggHover::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) {
	if ( inPursuit ) {
		return false;
	}

	if ( !enemy.fl.visible ) {
		return false;
	}

	if ( !enemy.fl.inFov ) {
		return false;
	}

	if ( !move.fl.done ) {
		return false;
	}

	return true;
}

/*
================
rvMonsterStroggHover::CheckAction_CircleStrafe
================
*/
bool rvMonsterStroggHover::CheckAction_BombAttack ( rvAIAction* action, int animNum ) {
	if ( !GetEnemy() || !enemy.fl.visible ) {
		return false;
	}
	/*
	if ( GetPhysics()->GetLinearVelocity().Length() < 200.0f ) {
		//not moving enough
		return false;
	}
	*/
	if ( GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetPhysics()->GetOrigin()-GetEnemy()->GetPhysics()->GetOrigin()) >= 250.0f ) {
		//enemy is moving toward me, drop 'em!
		return true;
	}
	return false;
}

/*
================
rvMonsterStroggHover::Spawn
================
*/
bool rvMonsterStroggHover::CheckActions ( void ) {
	if ( PerformAction ( &actionCircleStrafe,  (checkAction_t)&rvMonsterStroggHover::CheckAction_CircleStrafe ) ) {
		return true;
	}

/*
	if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) {
		return true;
	}

	if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
		return true;
	}
*/
	if ( PerformAction ( &actionBombAttack, (checkAction_t)&rvMonsterStroggHover::CheckAction_BombAttack ) ) {
		return true;
	}

	if ( PerformAction ( &actionMissileAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
		return true;
	}

	if ( PerformAction ( &actionMGunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
		return true;
	}

	if ( idAI::CheckActions ( ) ) {
		return true;
	}

	if ( PerformAction ( &actionStrafe,  (checkAction_t)&rvMonsterStroggHover::CheckAction_Strafe ) ) {
		return true;
	}
	
	return false;
}

/*
================
rvMonsterStroggHover::OnEnemyChange
================
*/
void rvMonsterStroggHover::OnEnemyChange ( idEntity* oldEnemy ) {
	idAI::OnEnemyChange ( oldEnemy );
	
	if ( !enemy.ent ) {
		return;
	}
}

/*
================
rvMonsterStroggHover::GetIdleAnimName
================
*/
const char* rvMonsterStroggHover::GetIdleAnimName ( void ) {
	/*
	if ( move.moveType == MOVETYPE_FLY ) {
		return "flying_idle";
	}
	*/
	return idAI::GetIdleAnimName ( );
}	

/*
================
rvMonsterStroggHover::DoNamedAttack
================
*/
void rvMonsterStroggHover::DoNamedAttack ( const char* attackName, jointHandle_t joint ) {
	if ( joint != INVALID_JOINT ) {
		StartSound ( va("snd_%s_fire",attackName), SND_CHANNEL_ANY, 0, false, NULL );
		PlayEffect ( va("fx_%s_flash",attackName), joint );
		Attack ( attackName, joint, GetEnemy() );
	}
}

/*
================
rvMonsterStroggHover::FilterTactical
================
*/
int rvMonsterStroggHover::FilterTactical ( int availableTactical ) {
	availableTactical = idAI::FilterTactical( availableTactical );
	if ( circleStrafing || inPursuit ) {
		return 0;
	}
	if ( nextBombFireTime >= gameLocal.GetTime() ) {
        availableTactical &= ~(AITACTICAL_RANGED_BITS);
	} else if ( enemy.fl.visible ) {
        availableTactical &= ~AITACTICAL_MELEE_BIT;
	}
	return availableTactical;
}

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

	States 

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

CLASS_STATES_DECLARATION ( rvMonsterStroggHover )
//	STATE ( "Torso_BlasterAttack",		rvMonsterStroggHover::State_Torso_BlasterAttack )
//	STATE ( "Torso_RocketAttack",		rvMonsterStroggHover::State_Torso_RocketAttack )
	STATE ( "Torso_MGunAttack",			rvMonsterStroggHover::State_Torso_MGunAttack )
	STATE ( "Torso_MissileAttack",		rvMonsterStroggHover::State_Torso_MissileAttack )
	STATE ( "Torso_BombAttack",			rvMonsterStroggHover::State_Torso_BombAttack )
//	STATE ( "Torso_EvadeLeft",			rvMonsterStroggHover::State_Torso_EvadeLeft )
//	STATE ( "Torso_EvadeRight",			rvMonsterStroggHover::State_Torso_EvadeRight )
	STATE ( "Torso_Strafe",				rvMonsterStroggHover::State_Torso_Strafe )
	STATE ( "Torso_CircleStrafe",		rvMonsterStroggHover::State_Torso_CircleStrafe )
	STATE ( "State_CircleStrafe",		rvMonsterStroggHover::State_CircleStrafe )
	STATE ( "State_DeathSpiral",		rvMonsterStroggHover::State_DeathSpiral )
	STATE ( "State_Pursue",				rvMonsterStroggHover::State_Pursue )
END_CLASS_STATES

/*
================
rvMonsterStroggHover::State_Torso_MGunAttack
================
*/
stateResult_t rvMonsterStroggHover::State_Torso_MGunAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_LOOP,
		STAGE_WAITLOOP,
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			shots = gameLocal.random.RandomInt ( mGunMaxShots-mGunMinShots ) + mGunMinShots;
			return SRESULT_STAGE ( STAGE_LOOP );
			
		case STAGE_LOOP:
			DoNamedAttack( "mgun", jointMGun );
			nextMGunFireTime = gameLocal.GetTime() + mGunFireRate;
			return SRESULT_STAGE ( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( nextMGunFireTime <= gameLocal.GetTime() ) {
				if ( --shots <= 0 ) {
					ForceTacticalUpdate();
					return SRESULT_DONE;
				}
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR; 
}

/*
================
rvMonsterStroggHover::State_Torso_MissileAttack
================
*/
stateResult_t rvMonsterStroggHover::State_Torso_MissileAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_LOOP,
		STAGE_WAITLOOP,
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			shots = gameLocal.random.RandomInt ( missileMaxShots-missileMinShots ) + missileMinShots;
			return SRESULT_STAGE ( STAGE_LOOP );
			
		case STAGE_LOOP:
			DoNamedAttack( "missile", jointMissile[gameLocal.random.RandomInt(numMissileJoints)] );
			nextMissileFireTime = gameLocal.GetTime() + missileFireRate;
			return SRESULT_STAGE ( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( nextMissileFireTime <= gameLocal.GetTime() ) {
				if ( --shots <= 0 || enemy.range < (actionMissileAttack.minRange*0.75f) ) {
					//out of shots or enemy too close to safely keep launching rockets at
					ForceTacticalUpdate();
					return SRESULT_DONE;
				}
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR; 
}

/*
================
rvMonsterStroggHover::State_Torso_BombAttack
================
*/
stateResult_t rvMonsterStroggHover::State_Torso_BombAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_LOOP,
		STAGE_WAITLOOP,
	};
	idVec3 vel = GetPhysics()->GetLinearVelocity();
	if ( vel.z < 150.0f ) {
		vel.z += 20.0f;
		physicsObj.UseVelocityMove( true );
		GetPhysics()->SetLinearVelocity( vel );
	}
	switch ( parms.stage ) {
		case STAGE_INIT:
			move.fly_offset = 800;//go up!
			shots = gameLocal.random.RandomInt ( bombMaxShots-bombMinShots ) + bombMinShots;
			if ( GetEnemy() ) {
				//if I'm not above him, give me a quick boost first
				float zDiff = GetPhysics()->GetOrigin().z-GetEnemy()->GetPhysics()->GetOrigin().z;
				if ( zDiff < 150.0f ) {
					idVec3 vel = GetPhysics()->GetLinearVelocity();
					vel.z += 200.0f;
					if ( zDiff < 0.0f ) {
						//even more if I'm below him!
						vel.z -= zDiff*2.0f;
					}
					physicsObj.UseVelocityMove( true );
					GetPhysics()->SetLinearVelocity( vel );
				}
			}
			if ( move.moveCommand == MOVE_TO_ATTACK ) {
				StopMove( MOVE_STATUS_DONE );
			}
			if ( combat.tacticalCurrent == AITACTICAL_RANGED ) {
				ForceTacticalUpdate();
			}
			if ( move.moveCommand == MOVE_NONE
				&& !inPursuit && !circleStrafing ) {
				MoveToEnemy();
			}
			return SRESULT_STAGE ( STAGE_LOOP );
			
		case STAGE_LOOP:
			DoNamedAttack( "bomb", jointBomb );
			nextBombFireTime = gameLocal.GetTime() + bombFireRate;
			return SRESULT_STAGE ( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( nextBombFireTime <= gameLocal.GetTime() ) {
				if ( --shots <= 0 ) {
					move.fly_offset = spawnArgs.GetFloat("fly_offset","250");
					ForceTacticalUpdate();
					return SRESULT_DONE;
				}
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR; 
}

/*
================
rvMonsterStroggHover::State_Torso_BlasterAttack
================
*/
/*
stateResult_t rvMonsterStroggHover::State_Torso_BlasterAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_WAITSTART,
		STAGE_LOOP,
		STAGE_WAITLOOP,
		STAGE_WAITEND
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			shots = gameLocal.random.RandomInt ( 8 ) + 4;
			PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_preshoot", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_WAITSTART );
			
		case STAGE_WAITSTART:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_LOOP:
			PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_fire", 0 );
			return SRESULT_STAGE ( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				if ( --shots <= 0 ) {
					PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_postshoot", 0 );
					return SRESULT_STAGE ( STAGE_WAITEND );
				}
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_WAITEND:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
				ForceTacticalUpdate();
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;				
	}
	return SRESULT_ERROR; 
}
*/
/*
================
rvMonsterStroggHover::State_Torso_RocketAttack
================
*/
/*
stateResult_t rvMonsterStroggHover::State_Torso_RocketAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_WAITSTART,
		STAGE_LOOP,
		STAGE_WAITLOOP,
		STAGE_WAITEND
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			//DisableAnimState ( ANIMCHANNEL_LEGS );
			PlayAnim ( ANIMCHANNEL_TORSO, "rocket_range_attack", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_WAITSTART );
			
		case STAGE_WAITSTART:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				ForceTacticalUpdate();
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}		

	return SRESULT_ERROR;
}
*/

void rvMonsterStroggHover::Evade ( bool left ) {
	idVec3 vel = GetPhysics()->GetLinearVelocity();
	vel += viewAxis[1] * (left?strafeSpeed:-strafeSpeed);
	physicsObj.UseVelocityMove( true );
	GetPhysics()->SetLinearVelocity( vel );
}

stateResult_t rvMonsterStroggHover::State_Torso_Strafe ( const stateParms_t& parms ) {
	//fixme: trace first for visibility & obstruction?
	if ( gameLocal.random.RandomFloat() > 0.5f ) {
		Evade( false );
	} else {
		Evade( true );
	}
	return SRESULT_DONE;
}

stateResult_t rvMonsterStroggHover::State_Torso_CircleStrafe ( const stateParms_t& parms ) {
	circleStrafing = true;
	return SRESULT_DONE;
}

bool rvMonsterStroggHover::MarkerPosValid ( void )
{
	//debouncer ftw
	if( markerCheckTime > gameLocal.GetTime() )	{
		return true;
	}

	markerCheckTime = gameLocal.GetTime() + 500 + (gameLocal.random.RandomFloat() * 500);

	trace_t trace;
	gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetOrigin(), GetPhysics()->GetClipMask(), NULL );
	if ( !(trace.c.contents&GetPhysics()->GetClipMask()) )
	{//not in solid
		gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), GetEnemy()->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, GetEnemy() );
		idActor* enemyAct = NULL;
		rvVehicle* enemyVeh = NULL;
		if ( GetEnemy()->IsType( rvVehicle::GetClassType() ) ) {
			enemyVeh = static_cast<rvVehicle*>(GetEnemy());
		} else if ( GetEnemy()->IsType( idActor::GetClassType() ) ) {
			enemyAct = static_cast<idActor*>(GetEnemy());
		}
		idEntity* hitEnt = gameLocal.entities[trace.c.entityNum];
		idActor* hitAct = NULL;
		if ( hitEnt && hitEnt->IsType( idActor::GetClassType() ) ) {
			hitAct = static_cast<idActor*>(hitEnt);
		}
		if ( trace.fraction >= 1.0f 
			|| (enemyAct && enemyAct->IsInVehicle() && enemyAct->GetVehicleController().GetVehicle() == gameLocal.entities[trace.c.entityNum])
			|| (enemyVeh && hitAct && hitAct->IsInVehicle() && hitAct->GetVehicleController().GetVehicle() == enemyVeh) )
		{//have a clear LOS to enemy
			if ( PointReachableAreaNum( marker.GetEntity()->GetPhysics()->GetOrigin() ) )
			{//valid AAS there...
				return true;
			}
		}
	}
	return false;
}

void rvMonsterStroggHover::TryStartPursuit ( void )
{
	if ( GetEnemy() )
	{
		inPursuit = false;
		if ( !marker.GetEntity() ) {
			//wtf?!
			assert(0);
			return;
		}
		attackPosOffset.Set( gameLocal.random.CRandomFloat()*500.0f, gameLocal.random.CRandomFloat()*500.0f, 0.0f );
		if ( attackPosOffset.Length() < 150.0f )
		{
			attackPosOffset.Normalize();
			attackPosOffset *= 150.0f;
		}
		attackPosOffset.z = (gameLocal.random.CRandomFloat()*30.0f)+50.0f + move.fly_offset;
		marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset );
		if ( MarkerPosValid() )
		{
			if ( MoveToEntity( marker ) )
			{
				inPursuit = true;
				holdPosTime = 0;
				SetState( "State_Pursue" );
			}
		}
	}
}

void rvMonsterStroggHover::Pursue ( void )
{
	if ( marker.GetEntity() && GetEnemy() )
	{
		marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset );
		if ( DebugFilter(ai_debugMove) ) {
			gameRenderWorld->DebugAxis( marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetAxis() );
		}
		if ( MarkerPosValid() )
		{
			bool breakOff = false;
			if ( move.fl.done )
			{//even once get there, hold that position for a while...
				if ( holdPosTime && holdPosTime > gameLocal.GetTime() )
				{//held this position long enough
					breakOff = true;
				}
				else
				{
					if ( !holdPosTime )
					{//just got there, hold position for a bit
						holdPosTime = gameLocal.random.RandomInt(2000)+3000 + gameLocal.GetTime();
					}
					if ( !MoveToEntity( marker ) )
					{
						breakOff = true;
					}
				}
			}
			if ( !breakOff )
			{
				return;
			}
		}
	}
	if ( !move.fl.done )
	{
		StopMove( MOVE_STATUS_DONE );
	}
	inPursuit = false;
}

stateResult_t rvMonsterStroggHover::State_Pursue ( const stateParms_t& parms ) {
	if ( inPursuit ) {
		// Perform actions along the way
		if ( UpdateAction ( ) ) {
			return SRESULT_WAIT;
		}
		return SRESULT_WAIT;
	}
	SetState( "State_Combat" );
	return SRESULT_DONE;
}

void rvMonsterStroggHover::CircleStrafe ( void )
{
	if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov )
	{
		//FIXME: also stop if I bump into something
		circleStrafing = false;
		strafeTime = 0;
		SetState( "State_Combat" );
		return;
	}
	if ( !strafeTime )
	{
		strafeTime = gameLocal.GetTime() + 8000;
		//FIXME: try to see which side it clear?
		strafeRight = (gameLocal.random.RandomFloat()>0.5f);
	}

	idVec3 vel = GetPhysics()->GetLinearVelocity();
	idVec3 strafeVel = viewAxis[1] * (strafeRight?-circleStrafeSpeed:circleStrafeSpeed);
	strafeVel.z = 0.0f;
	vel += strafeVel;
	vel.Normalize();
	vel *= circleStrafeSpeed;
	physicsObj.UseVelocityMove( true );
	GetPhysics()->SetLinearVelocity( vel );
	TurnToward( GetEnemy()->GetPhysics()->GetOrigin() );
}

stateResult_t rvMonsterStroggHover::State_CircleStrafe ( const stateParms_t& parms ) {
	if ( circleStrafing ) {
		// Perform actions along the way
		if ( UpdateAction ( ) ) {
			return SRESULT_WAIT;
		}
		return SRESULT_WAIT;
	}
	SetState( "State_Combat" );
	return SRESULT_DONE;
}

stateResult_t rvMonsterStroggHover::State_DeathSpiral ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_SPIRAL
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			{
				disablePain = true;

				// Make sure all animation and attack states stop
				StopAnimState ( ANIMCHANNEL_TORSO );
				StopAnimState ( ANIMCHANNEL_LEGS );

				// Start the crash & burn effects
				for ( int i = 0; i < numHoverJoints; i++ ) {
					if ( jointHover[i] != INVALID_JOINT ) {
						PlayEffect ( "fx_hurt", jointHover[i], false );
						effectHover[i] = PlayEffect ( "fx_crash", jointHover[i], true );
					}
				}
				deathPitch = viewAxis.ToAngles()[0];
				deathRoll = viewAxis.ToAngles()[2];

				deathPitchRate = gameLocal.random.RandomFloat()*0.3f + 0.1f;
				deathYawRate = gameLocal.random.RandomFloat()*2.0f + 1.5f;
				deathRollRate = gameLocal.random.RandomFloat()*3.0f + 1.0f;
				deathSpeed = gameLocal.random.RandomFloat()*300.0f + 500.0f;
				deathGrav = gameLocal.random.RandomFloat()*6.0f + 6.0f;

				strafeRight = (gameLocal.random.RandomFloat()>0.5f);
				StopSound( SND_CHANNEL_HEART, false ); 
				StartSound ( "snd_crash", SND_CHANNEL_HEART, 0, false, NULL );
			}
			return SRESULT_STAGE ( STAGE_SPIRAL );
		case STAGE_SPIRAL:
			{
				move.current_yaw += (strafeRight?-deathYawRate:deathYawRate);
				deathPitch = idMath::ClampFloat( -90.0f, 90.0f, deathPitch+deathPitchRate );
				deathRoll += (strafeRight?deathRollRate:-deathRollRate);
				viewAxis = idAngles( deathPitch, move.current_yaw, deathRoll ).ToMat3();

				idVec3 vel = GetPhysics()->GetLinearVelocity();
				idVec3 strafeVel = viewAxis[0] * deathSpeed;
				strafeVel.z = 0;
				vel += strafeVel;
				vel.Normalize();
				vel *= deathSpeed;
				vel += GetPhysics()->GetGravity()/deathGrav;

				physicsObj.UseVelocityMove( true );

				physicsObj.SetLinearVelocity( vel );

				idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle );
				if( emitter ) {
					refSound.parms.frequencyShift += 0.025f;
					emitter->ModifySound ( SND_CHANNEL_HEART, &refSound.parms );
				}
			}
			return SRESULT_WAIT;
	}

	return SRESULT_ERROR;
}
