/*
================
Monster_Fatguy.cpp

AI for the fat guy on the putra level
================
*/




#include "../Game_local.h"
#include "../Projectile.h"

typedef struct monsterFattyChain_s {
	jointHandle_t			orientationJoint;
	jointHandle_t			attackJoint;
	bool					out;
	idEntityPtr<idEntity>	projectile;
} monsterFattyChain_t;

class rvMonsterFatty : public idAI {
public:

	CLASS_PROTOTYPE( rvMonsterFatty );

	rvMonsterFatty ( void ) {}
	~rvMonsterFatty ( void );
	
	void				InitSpawnArgsVariables			( void );
	void				Spawn							( void );
	void				Save							( idSaveGame *savefile ) const;
	void				Restore							( idRestoreGame *savefile );

	virtual bool		UpdateAnimationControllers		( void );
	virtual bool		CanPlayImpactEffect				( idEntity* attacker, idEntity* target ) { return false; };
	virtual void		AddDamageEffect					( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor );

protected:

	rvAIAction			actionWhipAttack;

	enum {
		CHAIN_LEFT,
		CHAIN_RIGHT,
		CHAIN_MAX
	};

	monsterFattyChain_t	chains[CHAIN_MAX];

	float				missHeight;		

	virtual bool		CheckActions			( void );

	void				PlayAttackAnim			( const idVec3& target, int blendFrames );
	
	void				ResetAllChains			( void );
	void				ChainIn					( int chain );
	void				ChainOut				( int chain );

private:

	// Custom actions
	bool				CheckAction_WhipAttack	( rvAIAction* action, int animNum );

	// Frame Commands
	stateResult_t		Frame_LeftChainOut		( const stateParms_t& parms );
	stateResult_t		Frame_LeftChainIn		( const stateParms_t& parms );
	
	stateResult_t		Frame_RightChainOut		( const stateParms_t& parms );
	stateResult_t		Frame_RightChainIn		( const stateParms_t& parms );

	// Torso States
	stateResult_t		State_Torso_WhipAttack	( const stateParms_t& parms );

	CLASS_STATES_PROTOTYPE ( rvMonsterFatty );
};

CLASS_DECLARATION( idAI, rvMonsterFatty )
END_CLASS

/*
================
rvMonsterFatty::~rvMonsterFatty
================
*/
rvMonsterFatty::~rvMonsterFatty ( void ) {
	ResetAllChains ( );
}

void rvMonsterFatty::InitSpawnArgsVariables ( void )
{
	chains[CHAIN_LEFT].orientationJoint		= animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChain", "chainb1" ) );
	chains[CHAIN_LEFT].attackJoint			= animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChainAttack", "hookb" ) );

	chains[CHAIN_RIGHT].orientationJoint	= animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChain", "chaina1" ) );
	chains[CHAIN_RIGHT].attackJoint			= animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChainAttack", "hooka" ) );
		
	missHeight = spawnArgs.GetFloat ( "missHeight", "72" );	
}
/*
================
rvMonsterFatty::Spawn
================
*/
void rvMonsterFatty::Spawn ( void ) {
	// Custom actions
	actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK );

	// Cache joints	
	chains[CHAIN_LEFT].out					= false;
	chains[CHAIN_LEFT].projectile			= NULL;

	chains[CHAIN_RIGHT].out					= false;
	chains[CHAIN_RIGHT].projectile			= NULL;

	InitSpawnArgsVariables();
}

/*
================
rvMonsterFatty::Save
================
*/
void rvMonsterFatty::Save ( idSaveGame *savefile ) const {
	int i;
	
	actionWhipAttack.Save( savefile );

	for ( i = 0; i < CHAIN_MAX; i ++ ) {
		savefile->WriteBool( chains[i].out );
		chains[i].projectile.Save ( savefile );		
	}
}

/*
================
rvMonsterFatty::Restore
================
*/
void rvMonsterFatty::Restore ( idRestoreGame *savefile ) {
	int i;
	
	actionWhipAttack.Restore( savefile );

	for ( i = 0; i < CHAIN_MAX; i ++ ) {
		savefile->ReadBool( chains[i].out );
		chains[i].projectile.Restore ( savefile );		
	}
	
	InitSpawnArgsVariables();
}

/*
================
rvMonsterFatty::UpdateAnimationControllers
================
*/
bool rvMonsterFatty::UpdateAnimationControllers ( void ) {
	if ( !idAI::UpdateAnimationControllers ( ) ) {
		return false;
	}

	if ( enemy.ent && CheckFOV ( enemy.lastKnownPosition ) ) {
		int		i;
		idVec3	origin;
		idMat3	axis;
		idVec3	dir;
		idVec3	localDir;
		idVec3	target;

		if ( enemy.ent->IsType ( idActor::GetClassType ( ) ) ) {
			target = static_cast<idActor*>(enemy.ent.GetEntity())->GetEyePosition ( );
		} else {
			target = enemy.ent->GetPhysics()->GetOrigin ( );
		}

		if ( !IsEnemyVisible ( ) ) {
			target -= enemy.ent->GetPhysics()->GetGravityNormal() * missHeight;
		}

		for ( i = 0; i < CHAIN_MAX; i ++ ) {
			if ( !chains[i].out ) {
				continue;
			}
			
			animator.ClearJoint ( chains[i].orientationJoint );
			GetJointWorldTransform ( chains[i].orientationJoint, gameLocal.time, origin, axis );
			dir = target - origin;
			dir.Normalize ( );	
			axis.ProjectVector ( dir, localDir );
			animator.SetJointAxis ( chains[i].orientationJoint, JOINTMOD_LOCAL, localDir.ToMat3() );
		}
	}

	return true;
}

/*
================
rvMonsterFatty::AddDamageEffect
================
*/
void rvMonsterFatty::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) {
	// If there are still shields remaining then play a shield effect at the impact point
	/*
	idVec3 dir;
	dir = collision.c.point - GetPhysics()->GetCenterMass ();
	PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true );
	*/
}

/*
================
rvMonsterFatty::CheckAction_WhipAttack
================
*/
bool rvMonsterFatty::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) {
	if ( !enemy.ent ) {
		return false;
	}
	return true;
}

/*
================
rvMonsterFatty::CheckActions
================
*/
bool rvMonsterFatty::CheckActions ( void ) {
	if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterFatty::CheckAction_WhipAttack, NULL ) ) {
		return true;
	}
	return idAI::CheckActions ( );
}

/*
================
rvMonsterFatty::PlayAttackAnim
================
*/
void rvMonsterFatty::PlayAttackAnim ( const idVec3& target, int blendFrames ) {
	idVec3 		dir;
	idVec3 		localDir;
	float  		yaw;
	const char* animName;
	
	// Get the local direction vector
	dir = target - GetPhysics()->GetOrigin();
	dir.Normalize ( );
	viewAxis.ProjectVector( dir, localDir );
	
	// Get the yaw relative to forward
	yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] );
	
	if ( yaw < -45.0f ) {
		animName = "attack4b";
	} else if ( yaw < -20.0f ) {
		animName = "attack3b";
	} else if ( yaw < -5.0f ) {
		animName = "attack5r";
	} else if ( yaw < 5.0f ) {
		animName = "attack5";
	} else if ( yaw < 20.0f ) {
		animName = "attack5l";
	} else if ( yaw < 45.0f ) {
		animName = "attack2b";
	} else{
		animName = "attack1b";
	}
	
	PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames );
}

/*
================
rvMonsterFatty::ChainOut
================
*/
void rvMonsterFatty::ChainOut ( int chain ) {
	idEntity*		ent;
	idProjectile*	proj;
	
	gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_hook" ) ), &ent, false );
	proj = dynamic_cast<idProjectile*>(ent);
	if ( !proj ) {
		delete ent;
		return;
	}

	chains[chain].out = true;
				
	proj->Create ( this, vec3_origin, idVec3(0,0,1) );
	proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin );

	chains[chain].projectile = proj;				
	ent->BindToJoint ( this, chains[chain].attackJoint, false );
	ent->SetOrigin ( vec3_origin );
	ent->SetAxis ( mat3_identity );	
}

/*
================
rvMonsterFatty::ChainIn
================
*/
void rvMonsterFatty::ChainIn ( int chain ) {
	chains[chain].out = false;
	if ( chains[chain].projectile ) {
		delete chains[chain].projectile;
		chains[chain].projectile = NULL;
	}
}	

/*
================
rvMonsterFatty::ResetChains
================
*/
void rvMonsterFatty::ResetAllChains ( void ) {
	int i;
	
	animator.ClearAllJoints ( );
	
	for ( i = 0; i < CHAIN_MAX; i ++ ) {
		ChainIn ( i );
	}
}

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

	States 

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

CLASS_STATES_DECLARATION ( rvMonsterFatty )
	STATE ( "Torso_WhipAttack",		rvMonsterFatty::State_Torso_WhipAttack )

	STATE ( "Frame_LeftChainOut",		rvMonsterFatty::Frame_LeftChainOut )
	STATE ( "Frame_LeftChainIn",		rvMonsterFatty::Frame_LeftChainIn )

	STATE ( "Frame_RightChainOut",		rvMonsterFatty::Frame_RightChainOut )
	STATE ( "Frame_RightChainIn",		rvMonsterFatty::Frame_RightChainIn )
END_CLASS_STATES


/*
================
rvMonsterFatty::State_Torso_WhipAttack
================
*/
stateResult_t rvMonsterFatty::State_Torso_WhipAttack ( const stateParms_t& parms ) {
	enum {
		STAGE_ATTACK,
		STAGE_ATTACK_WAIT,
	};
	switch ( parms.stage ) {
		case STAGE_ATTACK: {
			if ( !enemy.ent ) {
				return SRESULT_DONE;	
			}
		
			// Predict a bit				
			PlayAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames );

			return SRESULT_STAGE ( STAGE_ATTACK_WAIT );
		}
			
		case STAGE_ATTACK_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
				ResetAllChains ( );
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvMonsterFatty::Frame_LeftChainOut
================
*/
stateResult_t rvMonsterFatty::Frame_LeftChainOut ( const stateParms_t& parms ) {
	ChainOut ( CHAIN_LEFT );
	return SRESULT_OK;
}

/*
================
rvMonsterFatty::Frame_LeftChainIn
================
*/
stateResult_t rvMonsterFatty::Frame_LeftChainIn ( const stateParms_t& parms ) {
	ChainIn ( CHAIN_LEFT );
	return SRESULT_OK;
}

/*
================
rvMonsterFatty::Frame_RightChainOut
================
*/
stateResult_t rvMonsterFatty::Frame_RightChainOut ( const stateParms_t& parms ) {
	ChainOut ( CHAIN_RIGHT );
	return SRESULT_OK;
}

/*
================
rvMonsterFatty::Frame_RightChainIn
================
*/
stateResult_t rvMonsterFatty::Frame_RightChainIn ( const stateParms_t& parms ) {
	ChainIn ( CHAIN_RIGHT );
	return SRESULT_OK;
}
