



#include "../Game_local.h"

class rvMonsterIronMaiden : public idAI {
public:

	CLASS_PROTOTYPE( rvMonsterIronMaiden );

	rvMonsterIronMaiden ( void );
	
	void				InitSpawnArgsVariables( void );
	void				Spawn				( void );
	void				Save				( idSaveGame *savefile ) const;
	void				Restore				( idRestoreGame *savefile );

	// Add some dynamic externals for debugging
	virtual void		GetDebugInfo		( debugInfoProc_t proc, void* userData );

	virtual int			FilterTactical		( int availableTactical );

protected:

	int					phaseTime;
	jointHandle_t		jointBansheeAttack;
	int					enemyStunTime;

	rvAIAction			actionBansheeAttack;

	virtual bool		CheckActions		( void );
	
	void				PhaseOut			( void );
	void				PhaseIn				( void );
	
	virtual void		OnDeath				( void );

private:

	// Custom actions
	bool				CheckAction_BansheeAttack		( rvAIAction* action, int animNum );
	bool				PerformAction_PhaseOut			( void );
	bool				PerformAction_PhaseIn			( void );
	
	// Global States
	stateResult_t		State_Phased					( const stateParms_t& parms );
	
	// Torso States
	stateResult_t		State_Torso_PhaseIn				( const stateParms_t& parms );
	stateResult_t		State_Torso_PhaseOut			( const stateParms_t& parms );
	stateResult_t		State_Torso_BansheeAttack		( const stateParms_t& parms );
	stateResult_t		State_Torso_BansheeAttackDone	( const stateParms_t& parms );

	// Frame commands
	stateResult_t		Frame_PhaseIn					( const stateParms_t& parms );
	stateResult_t		Frame_PhaseOut					( const stateParms_t& parms );
	stateResult_t		Frame_BansheeAttack				( const stateParms_t& parms );
	stateResult_t		Frame_EndBansheeAttack			( const stateParms_t& parms );

	CLASS_STATES_PROTOTYPE ( rvMonsterIronMaiden );
};

CLASS_DECLARATION( idAI, rvMonsterIronMaiden )
END_CLASS

/*
================
rvMonsterIronMaiden::rvMonsterIronMaiden
================
*/
rvMonsterIronMaiden::rvMonsterIronMaiden ( void ) {
	enemyStunTime		= 0;
	phaseTime			= 0;
}

void rvMonsterIronMaiden::InitSpawnArgsVariables ( void ) {
	// Cache the mouth joint
	jointBansheeAttack = animator.GetJointHandle ( spawnArgs.GetString ( "joint_bansheeAttack", "mouth_effect" ) );	
}
/*
================
rvMonsterIronMaiden::Spawn
================
*/
void rvMonsterIronMaiden::Spawn ( void ) {
	// Custom actions
	actionBansheeAttack.Init ( spawnArgs, "action_bansheeAttack", "Torso_BansheeAttack", AIACTIONF_ATTACK );
		
	InitSpawnArgsVariables();

	PlayEffect ( "fx_dress", animator.GetJointHandle ( spawnArgs.GetString ( "joint_laser", "cog_bone" ) ), true );
}

/*
================
rvMonsterIronMaiden::Save
================
*/
void rvMonsterIronMaiden::Save( idSaveGame *savefile ) const {
	savefile->WriteInt ( phaseTime );
	savefile->WriteInt ( enemyStunTime );
	
	actionBansheeAttack.Save ( savefile );
}

/*
================
rvMonsterIronMaiden::Restore
================
*/
void rvMonsterIronMaiden::Restore( idRestoreGame *savefile ) {
	savefile->ReadInt ( phaseTime );
	savefile->ReadInt ( enemyStunTime );
	
	actionBansheeAttack.Restore ( savefile );

	InitSpawnArgsVariables();
}

/*
================
rvMonsterIronMaiden::FilterTactical
================
*/
int rvMonsterIronMaiden::FilterTactical ( int availableTactical ) {
	// When hidden the iron maiden only uses ranged tactical
	if ( fl.hidden ) {
		availableTactical &= (AITACTICAL_RANGED_BIT | AITACTICAL_TURRET_BIT);
	}	
	return idAI::FilterTactical( availableTactical );
}

/*
================
rvMonsterIronMaiden::CheckAction_BansheeAttack
================
*/
bool rvMonsterIronMaiden::CheckAction_BansheeAttack ( rvAIAction* action, int animNum ) {
	return CheckAction_RangedAttack ( action, animNum );
}

/*
================
rvMonsterIronMaiden::PerformAction_PhaseIn
================
*/
bool rvMonsterIronMaiden::PerformAction_PhaseIn ( void ) {
	if ( !phaseTime ) {
		return false;
	}
	
	// Must be out for at least 3 seconds 
	if ( gameLocal.time - phaseTime < 3000  ) {
		return false;
	}

	// Phase in after 10 seconds or our movement is done
	if ( gameLocal.time - phaseTime > 10000	|| move.fl.done ) {
		// Make sure we arent in something
		if ( CanBecomeSolid ( ) ) {
			PerformAction ( "Torso_PhaseIn", 4, true );
			return true;
		}
	}	
	
	return false;
}

/*
================
rvMonsterIronMaiden::PerformAction_PhaseOut
================
*/
bool rvMonsterIronMaiden::PerformAction_PhaseOut ( void ) {
	// Little randomization 
	if ( gameLocal.random.RandomFloat ( ) > 0.5f ) {
		return false;
	}
	if ( !enemyStunTime || gameLocal.time - enemyStunTime > 1500 ) {
		return false;
	}
	
	PerformAction ( "Torso_PhaseOut", 4, true );
	return true;		
}

/*
================
rvMonsterIronMaiden::CheckActions
================
*/
bool rvMonsterIronMaiden::CheckActions ( void ) {
	// When phased the only available action is phase in
	if ( phaseTime ) {
		if ( PerformAction_PhaseIn ( ) ) {
			return true;
		}
		return false;
	}

	if ( PerformAction ( &actionBansheeAttack, (checkAction_t)&rvMonsterIronMaiden::CheckAction_BansheeAttack, &actionTimerRangedAttack ) ) {
		return true;
	}
	
	if ( PerformAction_PhaseOut ( ) ) {
		return true;
	}
	
	return idAI::CheckActions ( );
}

/*
================
rvMonsterIronMaiden::PhaseOut
================
*/
void rvMonsterIronMaiden::PhaseOut ( void ) {
	if ( phaseTime ) {
		return;
	}

	rvClientEffect* effect;
	effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") );
	if ( effect ) {
		effect->Unbind ( );
	}
	
	Hide ( );
	
	move.fl.allowHiddenMove = true;

	ProcessEvent ( &EV_BecomeNonSolid );
	
	StopMove ( MOVE_STATUS_DONE );
	SetState ( "State_Phased" );
	
	// Move away from here, to anywhere
	MoveOutOfRange ( this, 500.0f, 150.0f );

	SetShaderParm ( 5, MS2SEC ( gameLocal.time ) );
		
	phaseTime = gameLocal.time;
}

/*
================
rvMonsterIronMaiden::PhaseIn
================
*/
void rvMonsterIronMaiden::PhaseIn ( void ) {
	if ( !phaseTime ) {
		return;
	}

	rvClientEffect* effect;
	effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") );
	if ( effect ) {
		effect->Unbind ( );
	}
	
	if ( enemy.ent ) {
		TurnToward ( enemy.lastKnownPosition );
	}

	ProcessEvent ( &AI_BecomeSolid );

	Show ( );

//	PlayEffect ( "fx_dress", animator.GetJointHandle ( "cog_bone" ), true );

	phaseTime = 0;
	// Wait for the action that started the phase in to finish, then go back to combat
	SetState ( "Wait_Action" );
	PostState ( "State_Combat" );
	
	SetShaderParm ( 5, MS2SEC ( gameLocal.time ) );
}

/*
================
rvMonsterIronMaiden::OnDeath
================
*/
void rvMonsterIronMaiden::OnDeath ( void ) {
	StopSound ( SND_CHANNEL_ITEM, false );

	// Stop looping effects
	StopEffect ( "fx_banshee" );
	StopEffect ( "fx_dress" );
	
	idAI::OnDeath( );
}

/*
================
rvMonsterIronMaiden::GetDebugInfo
================
*/
void rvMonsterIronMaiden::GetDebugInfo	( debugInfoProc_t proc, void* userData ) {
	// Base class first
	idAI::GetDebugInfo( proc, userData );
	
	proc ( "idAI", "action_BansheeAttack",	aiActionStatusString[actionBansheeAttack.status], userData );
}

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

	States 

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

CLASS_STATES_DECLARATION ( rvMonsterIronMaiden )
	STATE ( "State_Phased",				rvMonsterIronMaiden::State_Phased )

	STATE ( "Torso_PhaseOut",			rvMonsterIronMaiden::State_Torso_PhaseOut )
	STATE ( "Torso_PhaseIn",			rvMonsterIronMaiden::State_Torso_PhaseIn )
	STATE ( "Torso_BansheeAttack",		rvMonsterIronMaiden::State_Torso_BansheeAttack )
	STATE ( "Torso_BansheeAttackDone",	rvMonsterIronMaiden::State_Torso_BansheeAttackDone )
	
	STATE ( "Frame_PhaseIn",			rvMonsterIronMaiden::Frame_PhaseIn )
	STATE ( "Frame_PhaseOut",			rvMonsterIronMaiden::Frame_PhaseOut )
	STATE ( "Frame_BansheeAttack",		rvMonsterIronMaiden::Frame_BansheeAttack )
	STATE ( "Frame_EndBansheeAttack",	rvMonsterIronMaiden::Frame_EndBansheeAttack )
END_CLASS_STATES

/*
================
rvMonsterIronMaiden::State_Phased
================
*/
stateResult_t rvMonsterIronMaiden::State_Phased ( const stateParms_t& parms ) {
	// If done moving and cant become solid here, move again
	if ( move.fl.done ) {
		if ( !CanBecomeSolid ( ) ) {
			MoveOutOfRange ( this, 300.0f, 150.0f );
		}
	}

	// Keep the enemy status up to date
	if ( !enemy.ent ) {
		CheckForEnemy ( true );
	}

	// Make sure we keep facing our enemy
	if ( enemy.ent ) {
		TurnToward ( enemy.lastKnownPosition );
	}

	// Make sure we are checking actions if we have no tactical move
	UpdateAction ( );
	
	return SRESULT_WAIT;
}

/*
================
rvMonsterIronMaiden::State_Torso_PhaseIn
================
*/
stateResult_t rvMonsterIronMaiden::State_Torso_PhaseIn ( const stateParms_t& parms ) {
	enum {
		STAGE_ANIM,
		STAGE_ANIM_WAIT,
		STAGE_PHASE,
	};
	switch ( parms.stage ) {
		case STAGE_ANIM:
			if ( enemy.ent ) {
				TurnToward ( enemy.lastKnownPosition );
			}
			DisableAnimState ( ANIMCHANNEL_LEGS );
			PlayAnim ( ANIMCHANNEL_TORSO, "phase_in", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_ANIM_WAIT );
	
		case STAGE_ANIM_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;			
	}						
	return SRESULT_ERROR;
}

/*
================
rvMonsterIronMaiden::State_Torso_PhaseOut
================
*/
stateResult_t rvMonsterIronMaiden::State_Torso_PhaseOut ( const stateParms_t& parms ) {
	enum {
		STAGE_ANIM,
		STAGE_ANIM_WAIT,
	};
	switch ( parms.stage ) {
		case STAGE_ANIM:
			DisableAnimState ( ANIMCHANNEL_LEGS );
			PlayAnim ( ANIMCHANNEL_TORSO, "phase_out", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_ANIM_WAIT );
	
		case STAGE_ANIM_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}						
	return SRESULT_ERROR;
}

/*
================
rvMonsterIronMaiden::State_Torso_BansheeAttack
================
*/
stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttack ( const stateParms_t& parms ) {
	enum {
		STAGE_ATTACK,
		STAGE_ATTACK_WAIT,
	};
	switch ( parms.stage ) {
		case STAGE_ATTACK:
			PlayAnim ( ANIMCHANNEL_TORSO, "banshee", parms.blendFrames );
			PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BansheeAttackDone", 0, 0, SFLAG_ONCLEAR );
			return SRESULT_STAGE ( STAGE_ATTACK_WAIT );
			
		case STAGE_ATTACK_WAIT:
			if ( enemy.ent ) {
				TurnToward ( enemy.lastKnownPosition );
			}			
			if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}


/*
================
rvMonsterIronMaiden::State_Torso_BansheeAttackDone

To ensure the movement is enabled after the banshee attack and that the effect is stopped this state
will be posted to be run after the banshe attack finishes.
================
*/
stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttackDone ( const stateParms_t& parms ) {
	Frame_EndBansheeAttack ( parms );
	return SRESULT_DONE;
}

/*
================
rvMonsterIronMaiden::Frame_PhaseIn
================
*/
stateResult_t rvMonsterIronMaiden::Frame_PhaseIn ( const stateParms_t& parms ) {
	PhaseIn ( );
	return SRESULT_OK;
}


/*
================
rvMonsterIronMaiden::Frame_PhaseOut
================
*/
stateResult_t rvMonsterIronMaiden::Frame_PhaseOut ( const stateParms_t& parms ) {
	PhaseOut ( );
	return SRESULT_OK;
}

/*
================
rvMonsterIronMaiden::Frame_BansheeAttack
================
*/
stateResult_t rvMonsterIronMaiden::Frame_BansheeAttack ( const stateParms_t& parms ) {
	idVec3		origin;
	idMat3		axis;
	idEntity*	entities[ 1024 ];
	int			count;
	int			i;
	
	// Get mouth origin
	GetJointWorldTransform ( jointBansheeAttack, gameLocal.time, origin, axis );
	
	// Find all entities within the banshee attacks radius 
	count = gameLocal.EntitiesWithinRadius ( origin, actionBansheeAttack.maxRange, entities, 1024 );
	for ( i = 0; i < count; i ++ ) {
		idEntity* ent = entities[i];		
		if ( !ent || ent == this ) {
			continue;
		}

		// Must be an actor that takes damage to be affected		
		if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) {
			continue;
		}
				
		// Has to be within fov
		if ( !CheckFOV ( ent->GetEyePosition ( ) ) ) {
			continue;
		}

		// Do some damage		
		idVec3 dir;
		dir = origin = ent->GetEyePosition ( );
		dir.NormalizeFast ( );
		ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_banshee_damage" ), 1.0f, 0 );
		
		// Cache the last time we stunned our own enemy for the phase out
		if ( ent == enemy.ent ) {
			enemyStunTime = gameLocal.time;
		}
	}
	
	return SRESULT_OK;
}

/*
================
rvMonsterIronMaiden::Frame_EndBansheeAttack
================
*/
stateResult_t rvMonsterIronMaiden::Frame_EndBansheeAttack ( const stateParms_t& parms ) {
	StopEffect ( "fx_banshee" );
	return SRESULT_OK;
}
