



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

class rvMonsterHeavyHoverTank : public idAI {
public:

	CLASS_PROTOTYPE( rvMonsterHeavyHoverTank );

	rvMonsterHeavyHoverTank ( void );

	void					InitSpawnArgsVariables( void );
	void					Spawn				( void );
	void					Save				( idSaveGame *savefile ) const;
	void					Restore				( idRestoreGame *savefile );
		
	virtual void			Think				( void );
	
	virtual	void			Damage				( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
	virtual void			OnDeath				( void );
	
protected:

	enum weaponState_t {
		WEAPONSTATE_BLASTER,
		WEAPONSTATE_ROCKET,
		WEAPONSTATE_MAX
	};

	int					blasterAnimIndex;
	int					shots;
	weaponState_t		weaponStateCurrent;
	weaponState_t		weaponStateIdeal;
	
	rvAIAction			actionRocketAttack;
	rvAIAction			actionBlasterAttack;
	rvAIAction			actionStrafe;
	
	jointHandle_t		jointHoverEffect;
	
	rvClientEffectPtr	effectDust;
	rvClientEffectPtr	effectHover;

	virtual bool			CheckActions				( void );
	virtual void			OnEnemyChange				( idEntity* oldEnemy );

//	virtual const char*		GetIdleAnimName				( void );

private:

	float					strafeSpeed;

	bool					CheckAction_Strafe				( 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_ChangeWeaponState	( 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 );

	CLASS_STATES_PROTOTYPE ( rvMonsterHeavyHoverTank );
};

CLASS_DECLARATION( idAI, rvMonsterHeavyHoverTank )
END_CLASS

/*
================
rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank
================
*/
rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank ( ) {
	weaponStateIdeal   = WEAPONSTATE_BLASTER;
	weaponStateCurrent = weaponStateIdeal;
	
	effectDust  = NULL;
	effectHover = NULL;
	
	blasterAnimIndex = 0;
	shots			 = 0;

	strafeSpeed		= 0;
}

void rvMonsterHeavyHoverTank::InitSpawnArgsVariables( void )
{
	jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover") );
	strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "300" );
}
/*
================
rvMonsterHeavyHoverTank::Spawn
================
*/
void rvMonsterHeavyHoverTank::Spawn ( void ) {
	actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK );
	actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK );
	actionStrafe.Init  ( spawnArgs, "action_strafe",	"Torso_Strafe",	0 );
	
	InitSpawnArgsVariables();

	if ( jointHoverEffect != INVALID_JOINT ) {
		effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true );
	}
}

/*
================
rvMonsterHeavyHoverTank::Think
================
*/
void rvMonsterHeavyHoverTank::Think ( void ) {
	idAI::Think ( );
	
	// If thinking we should play an effect on the ground under us
	if ( jointHoverEffect != INVALID_JOINT ) {
		if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) {
			trace_t tr;
			idVec3	origin;
			idMat3	axis;
			
			// Project the effect 128 units down from the hover effect joint
			GetJointWorldTransform ( jointHoverEffect, gameLocal.time, origin, axis );
			
	// RAVEN BEGIN
	// ddynerman: multiple clip worlds
			gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * 128.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;
		}
	}
}

/*
================
rvMonsterHeavyHoverTank::Save
================
*/
void rvMonsterHeavyHoverTank::Save ( idSaveGame *savefile ) const {
	savefile->WriteInt ( blasterAnimIndex );
	savefile->WriteInt ( shots );
	savefile->WriteInt ( (int)weaponStateCurrent );
	savefile->WriteInt ( (int)weaponStateIdeal );
	
	actionRocketAttack.Save ( savefile );
	actionBlasterAttack.Save ( savefile );
	actionStrafe.Save ( savefile );
	
	effectDust.Save ( savefile );
	effectHover.Save ( savefile );
}

/*
================
rvMonsterHeavyHoverTank::Restore
================
*/
void rvMonsterHeavyHoverTank::Restore ( idRestoreGame *savefile ) {
	savefile->ReadInt ( blasterAnimIndex );
	savefile->ReadInt ( shots );
	savefile->ReadInt ( (int&)weaponStateCurrent );
	savefile->ReadInt ( (int&)weaponStateIdeal );
	
	actionRocketAttack.Restore ( savefile );
	actionBlasterAttack.Restore ( savefile );
	actionStrafe.Restore ( savefile );
	
	effectDust.Restore ( savefile );
	effectHover.Restore ( savefile );

	InitSpawnArgsVariables();
}

void rvMonsterHeavyHoverTank::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location )
{
	if ( damageScale > 0.0f ) {
		const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false );
		if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) {
			//push me hard!
			float push = idMath::ClampFloat( 250.0f, 500.0f, damageScale*1000.0f );
			idVec3 vel = GetPhysics()->GetLinearVelocity();
			vel += dir * push;
			physicsObj.UseVelocityMove( true );
			GetPhysics()->SetLinearVelocity( vel );
		}
	}

	idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
}

/*
================
rvMonsterHeavyHoverTank::OnDeath
================
*/
void rvMonsterHeavyHoverTank::OnDeath ( void ) {
	// Stop the dust effect
	if ( effectDust ) {
		effectDust->Stop ( );
		effectDust = NULL;
	}

	// Stop the hover effect
	if ( effectHover ) {
		effectHover->Stop ( );
		effectHover = NULL;
	}
	
	idAI::OnDeath ( );
}

/*
================
rvMonsterHeavyHoverTank::CheckAction_Strafe
================
*/
bool rvMonsterHeavyHoverTank::CheckAction_Strafe ( rvAIAction* action, int animNum ) {
	if ( !enemy.fl.visible ) {
		return false;
	}

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

	if ( !move.fl.done ) {
		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;
}

/*
================
rvMonsterHeavyHoverTank::Spawn
================
*/
bool rvMonsterHeavyHoverTank::CheckActions ( void ) {
	if ( weaponStateIdeal != weaponStateCurrent ) {
		PerformAction ( "Torso_ChangeWeaponState", 4 );
		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 ( idAI::CheckActions( ) ) {
		return true;
	}

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

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

	if ( enemy.ent->IsType ( rvVehicle::GetClassType() ) ) {
		weaponStateIdeal = WEAPONSTATE_ROCKET;
	}
}

/*
================
rvMonsterHeavyHoverTank::GetIdleAnimName
================
*/
/*
const char* rvMonsterHeavyHoverTank::GetIdleAnimName ( void ) {
	if ( weaponStateCurrent == WEAPONSTATE_ROCKET ) {
		return "rocket_idle";
	}
	
	return idAI::GetIdleAnimName ( );
}	
*/

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

	States 

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

CLASS_STATES_DECLARATION ( rvMonsterHeavyHoverTank )
	STATE ( "Torso_BlasterAttack",		rvMonsterHeavyHoverTank::State_Torso_BlasterAttack )
	STATE ( "Torso_RocketAttack",		rvMonsterHeavyHoverTank::State_Torso_RocketAttack )
	STATE ( "Torso_ChangeWeaponState",	rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState )
	STATE ( "Torso_EvadeLeft",			rvMonsterHeavyHoverTank::State_Torso_EvadeLeft )
	STATE ( "Torso_EvadeRight",			rvMonsterHeavyHoverTank::State_Torso_EvadeRight )
	STATE ( "Torso_Strafe",				rvMonsterHeavyHoverTank::State_Torso_Strafe )
END_CLASS_STATES

/*
================
rvMonsterHeavyHoverTank::State_Torso_BlasterAttack
================
*/
stateResult_t rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_WAITSTART,
		STAGE_LOOP,
		STAGE_WAITLOOP,
		STAGE_WAITEND
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			weaponStateIdeal = WEAPONSTATE_BLASTER;
			if ( weaponStateIdeal != weaponStateCurrent  ) {
				PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames );				
				PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BlasterAttack", parms.blendFrames );				
				return SRESULT_DONE;
			}
			shots = gameLocal.random.RandomInt ( 8 ) + 4;
			blasterAnimIndex = (blasterAnimIndex + 1) % 2;
			PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_preshoot", blasterAnimIndex + 1 ), 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, va("blaster_%d_fire", blasterAnimIndex + 1 ), 0 );
			return SRESULT_STAGE ( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				if ( --shots <= 0 ) {
					PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_postshoot", blasterAnimIndex + 1 ), 0 );
					return SRESULT_STAGE ( STAGE_WAITEND );
				}
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_WAITEND:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;				
	}
	return SRESULT_ERROR; 
}

/*
================
rvMonsterHeavyHoverTank::State_Torso_RocketAttack
================
*/
stateResult_t rvMonsterHeavyHoverTank::State_Torso_RocketAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_WAITSTART,
		STAGE_LOOP,
		STAGE_WAITLOOP,
		STAGE_WAITEND
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			weaponStateIdeal = WEAPONSTATE_ROCKET;
			if ( weaponStateIdeal != weaponStateCurrent  ) {
				PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames );				
				PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RocketAttack", parms.blendFrames );				
				return SRESULT_DONE;
			}
			DisableAnimState ( ANIMCHANNEL_LEGS );
			PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_WAITSTART );
			
		case STAGE_WAITSTART:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}		

	return SRESULT_ERROR;
}

/*
================
rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState
================
*/
stateResult_t rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ( const stateParms_t& parms ) {
	static const char* stateAnims [ WEAPONSTATE_MAX ] [ WEAPONSTATE_MAX ] = {
		{ NULL, "blaster_to_rocket" },			// WEAPONSTATE_BLASTER
		{ "rocket_to_blaster", NULL },			// WEAPONSTATE_ROCKET
	};
		
	enum { 
		STAGE_INIT,
		STAGE_WAIT
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			// No anim for that transition?
			if ( stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ] == NULL ) {
				return SRESULT_DONE;
			}	
			DisableAnimState ( ANIMCHANNEL_LEGS );
			PlayAnim ( ANIMCHANNEL_TORSO, stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ], parms.blendFrames );
			return SRESULT_STAGE ( STAGE_WAIT );
		
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
				weaponStateCurrent = weaponStateIdeal;
				switch ( weaponStateCurrent ) {
					case WEAPONSTATE_ROCKET:
						animPrefix = "rocket";
						break;
					
					default:
						animPrefix = "";
						break;
				}
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_WAIT
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			{
				idVec3 vel = GetPhysics()->GetLinearVelocity();
				vel += viewAxis[1] * strafeSpeed;
				physicsObj.UseVelocityMove( true );
				GetPhysics()->SetLinearVelocity( vel );
				PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames );
			}
			return SRESULT_STAGE ( STAGE_WAIT );
		
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeRight ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_WAIT
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			{
				idVec3 vel = GetPhysics()->GetLinearVelocity();
				vel += viewAxis[1] * -strafeSpeed;
				physicsObj.UseVelocityMove( true );
				GetPhysics()->SetLinearVelocity( vel );
				PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames );
			}
			return SRESULT_STAGE ( STAGE_WAIT );
		
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

stateResult_t rvMonsterHeavyHoverTank::State_Torso_Strafe ( const stateParms_t& parms ) {
	//fixme: trace first for visibility & obstruction?
	if ( gameLocal.random.RandomFloat() > 0.5f ) {
		SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeRight", parms.blendFrames );
	} else {
		SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeLeft", parms.blendFrames );
	}
	return SRESULT_DONE;
}
