



#include "../Game_local.h"

//------------------------------------------------------------
class rvMonsterBossBuddy : public idAI 
//------------------------------------------------------------
{
public:

	CLASS_PROTOTYPE( rvMonsterBossBuddy );

	rvMonsterBossBuddy( void );

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

	bool		CanTurn							( void ) const;

	void		Think							( void );
	bool		Pain							( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
	void		Damage							( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
	void		OnWakeUp						( void );

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

protected:

	bool		CheckActions					( void );
//	void		PerformAction					( const char* stateName, int blendFrames, bool noPain );
//	bool		PerformAction					( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer );

	void		AdjustShieldState				( bool becomeShielded );
	void		ReduceShields					( int amount );

	int			mShots;
	int			mShields;
	int			mMaxShields;
	int			mLastDamageTime;
	int			mShieldsLastFor;		// read from def file, shouldn't need to be saved.

	bool		mIsShielded;
	bool		mRequestedZoneMove;
	bool		mRequestedRecharge;

	//bool		mCanIdle;
	//bool		mChaseMode;

private:

	rvAIAction	mActionRocketAttack;
	rvAIAction	mActionSlashMoveAttack;
	rvAIAction	mActionLightningAttack;
	rvAIAction	mActionDarkMatterAttack;
	rvAIAction	mActionMeleeMoveAttack;
	rvAIAction	mActionMeleeAttack;

	rvScriptFuncUtility	mRequestRecharge;	// script to run when a projectile is launched
	rvScriptFuncUtility	mRequestZoneMove;	// script to run when he should move to the next zone

	stateResult_t		State_Torso_RocketAttack		( const stateParms_t& parms );
	stateResult_t		State_Torso_SlashAttack			( const stateParms_t& parms );
	stateResult_t		State_Torso_TurnRight90			( const stateParms_t& parms );
	stateResult_t		State_Torso_TurnLeft90			( const stateParms_t& parms );

	void				Event_RechargeShields( float amount );

	CLASS_STATES_PROTOTYPE( rvMonsterBossBuddy );
};

#define BOSS_BUDDY_MAX_SHIELDS 8500
//------------------------------------------------------------
// rvMonsterBossBuddy::rvMonsterBossBuddy
//------------------------------------------------------------
rvMonsterBossBuddy::rvMonsterBossBuddy( void ) 
{
	mMaxShields = BOSS_BUDDY_MAX_SHIELDS;
	mShields = mMaxShields;
	mLastDamageTime = 0;
	mIsShielded = false;
	mRequestedZoneMove = false;
	mRequestedRecharge = false;
//	mCanIdle = false;
//	mChaseMode = true;
}

void rvMonsterBossBuddy::InitSpawnArgsVariables ( void )
{
	mShieldsLastFor = (int)(spawnArgs.GetFloat( "mShieldsLastFor", "6" ) * 1000.0f);
	mMaxShields = BOSS_BUDDY_MAX_SHIELDS;
}

//------------------------------------------------------------
// rvMonsterBossBuddy::Spawn
//------------------------------------------------------------
void rvMonsterBossBuddy::Spawn( void ) 
{
	mActionRocketAttack.Init( spawnArgs,	"action_rocketAttack",		NULL,	AIACTIONF_ATTACK );
	mActionLightningAttack.Init( spawnArgs,"action_lightningAttack",	NULL,	AIACTIONF_ATTACK );
	mActionDarkMatterAttack.Init( spawnArgs,"action_dmgAttack",			NULL,	AIACTIONF_ATTACK );
	mActionMeleeMoveAttack.Init( spawnArgs,	"action_meleeMoveAttack",	NULL,	AIACTIONF_ATTACK );
	mActionSlashMoveAttack.Init( spawnArgs,	"action_slashMoveAttack",	NULL,	AIACTIONF_ATTACK );
	mActionMeleeAttack.Init( spawnArgs,		"action_meleeAttack",		NULL,	AIACTIONF_ATTACK );

	InitSpawnArgsVariables();
	mShields = mMaxShields;

	const char  *func;
	if ( spawnArgs.GetString( "requestRecharge", "", &func ) ) 
	{
		mRequestRecharge.Init( func );
	}
	if ( spawnArgs.GetString( "requestZoneMove", "", &func ) ) 
	{
		mRequestZoneMove.Init( func );
	}

	HideSurface( "models/monsters/bossbuddy/forcefield" );
}

//------------------------------------------------------------
// rvMonsterBossBuddy::Save
//------------------------------------------------------------
void rvMonsterBossBuddy::Save( idSaveGame *savefile ) const 
{
	savefile->WriteInt( mShots );
	savefile->WriteInt( mShields );
	savefile->WriteInt( mMaxShields );
	savefile->WriteInt( mLastDamageTime );
	savefile->WriteBool( mIsShielded );
	savefile->WriteBool( mRequestedZoneMove );
	savefile->WriteBool( mRequestedRecharge );

	mActionRocketAttack.Save( savefile );
	mActionLightningAttack.Save( savefile );
	mActionDarkMatterAttack.Save( savefile );
	mActionMeleeMoveAttack.Save( savefile );
	mActionMeleeAttack.Save( savefile );
	mActionSlashMoveAttack.Save( savefile );

	mRequestRecharge.Save( savefile );
	mRequestZoneMove.Save( savefile );
}

//------------------------------------------------------------
// rvMonsterBossBuddy::Restore
//------------------------------------------------------------
void rvMonsterBossBuddy::Restore( idRestoreGame *savefile ) 
{
	savefile->ReadInt( mShots );
	savefile->ReadInt( mShields );
	savefile->ReadInt( mMaxShields );
	savefile->ReadInt( mLastDamageTime );
	savefile->ReadBool( mIsShielded );
	savefile->ReadBool( mRequestedZoneMove );
	savefile->ReadBool( mRequestedRecharge );

	mActionRocketAttack.Restore( savefile );
	mActionLightningAttack.Restore( savefile );
	mActionDarkMatterAttack.Restore( savefile );
	mActionMeleeMoveAttack.Restore( savefile );
	mActionMeleeAttack.Restore( savefile );
	mActionSlashMoveAttack.Restore( savefile );

	mRequestRecharge.Restore( savefile );
	mRequestZoneMove.Restore( savefile );

	InitSpawnArgsVariables();
}

//------------------------------------------------------------
// rvMonsterBerserker::GetDebugInfo
//------------------------------------------------------------
void rvMonsterBossBuddy::GetDebugInfo( debugInfoProc_t proc, void* userData ) 
{
	// Base class first
	idAI::GetDebugInfo( proc, userData );
	
	proc ( "idAI", "action_darkMatterAttack",	aiActionStatusString[mActionDarkMatterAttack.status], userData );
	proc ( "idAI", "action_rocketAttack",		aiActionStatusString[mActionRocketAttack.status], userData );
	proc ( "idAI", "action_meleeMoveAttack",	aiActionStatusString[mActionMeleeMoveAttack.status], userData );
	proc ( "idAI", "action_lightningAttack",	aiActionStatusString[mActionLightningAttack.status], userData );
}

//--------------------------------------------------------------
// Custom Script Events
//--------------------------------------------------------------
const idEventDef EV_RechargeShields( "rechargeShields",	"f", 'f' );

CLASS_DECLARATION( idAI, rvMonsterBossBuddy )
	EVENT( EV_RechargeShields, rvMonsterBossBuddy::Event_RechargeShields )
END_CLASS

//------------------------------------------------------------
// rvMonsterBossBuddy::Event_RechargeShields
//------------------------------------------------------------
void rvMonsterBossBuddy::Event_RechargeShields( float amount ) 
{
	mShields += (int)amount;

	if ( mShields >= mMaxShields ) 
	{
		// charge is done
		mShields = mMaxShields;
		idThread::ReturnInt(0);

		// reset request states
		mRequestedRecharge = false;
		mRequestedZoneMove = false;

		// shield warning no longer neede for now
		gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("hideBossShieldWarn");
	}
	else 
	{
		// still charging
		idThread::ReturnInt(1);
	}
}

//------------------------------------------------------------
// rvMonsterBossBuddy::ReduceShields
//------------------------------------------------------------
void rvMonsterBossBuddy::ReduceShields( int amount )
{
	mShields -= amount;

	// if no mShields left... or the last time we took damage was more than 8 seconds ago
	if ( mShields <= 0 || (mLastDamageTime + 8000) < gameLocal.time )
	{
		//....remove the shielding
		AdjustShieldState( false );
    }

	if (mShields < 1000)
	{
		if  (!mRequestedRecharge ) 
		{
			// entering a dangerous state!  Get to the recharge station, fast!
			gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("showBossShieldWarn");
			ExecScriptFunction( mRequestRecharge );
			mRequestedRecharge = true;
		}
	}
	else if (mShields < 4000)
	{
		if ( !mRequestedZoneMove ) 
		{
			// Getting low, so move him close to the next zone so he can be ready to recharge 
			ExecScriptFunction( mRequestZoneMove );
			mRequestedZoneMove = true;
		}
	}
}

//------------------------------------------------------------
// rvMonsterBossBuddy::AdjustShieldState
//------------------------------------------------------------
void rvMonsterBossBuddy::AdjustShieldState( bool becomeShielded )
{
	// only do the work for adjusting the state when it doesn't match our current state
	if ( !mIsShielded && becomeShielded )
	{
		// Activate Shields!
		ShowSurface( "models/monsters/bossbuddy/forcefield" );
		StartSound( "snd_enable_shields", SND_CHANNEL_ANY, 0, false, NULL );
		gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "showBossShieldBar" );
	}
	else if ( mIsShielded && !becomeShielded )
	{
		// Deactivate Shields!
		HideSurface( "models/monsters/bossbuddy/forcefield" );
		StartSound ( "snd_disable_shields", SND_CHANNEL_ANY, 0, false, NULL );
//		gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "hideBossShieldBar" );
	}
	mIsShielded = becomeShielded;
}

//------------------------------------------------------------
// rvMonsterBossBuddy::Damage
//------------------------------------------------------------
void rvMonsterBossBuddy::Think() 
{
	if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) 
	{
		// run simple shielding logic when we have them active
		if ( mIsShielded )
		{
			ReduceShields( 1 );

			// if they are on but we haven't taken damage in x seconds, turn them off to conserve on shields
			if ( (mLastDamageTime + mShieldsLastFor) < gameLocal.time )
			{
				AdjustShieldState( false );
            }
		}

		// update shield bar
		idUserInterface *hud = gameLocal.GetLocalPlayer()->hud;
		if ( hud ) 
		{
			float percent = ((float)mShields/mMaxShields);

			hud->SetStateFloat( "boss_shield_percent", percent );
			hud->HandleNamedEvent( "updateBossShield" );
		}
	}

	if ( move.obstacle.GetEntity() )
	{
		PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack );
	}

	idAI::Think();
}
/*
//------------------------------------------------------------
// rvMonsterBossBuddy::PerformAction
//------------------------------------------------------------
void rvMonsterBossBuddy::PerformAction( const char* stateName, int blendFrames, bool noPain ) 
{
	// Allow movement in actions
	move.fl.allowAnimMove = true;

	if ( mChaseMode )
	{
		return;
	}

	// Start the action
	SetAnimState( ANIMCHANNEL_TORSO, stateName, blendFrames );

	// Always call finish action when the action is done, it will clear the action flag
	aifl.action = true;
	PostAnimState( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR );

	// Go back to idle when done-- sometimes.
	if ( mCanIdle )	
	{	
		PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames );
	}

	// Main state will wait until action is finished before continuing
	InterruptState( "Wait_ActionNoPain" );
	OnStartAction( );
}

//------------------------------------------------------------
// rvMonsterBossBuddy::PerformAction
//------------------------------------------------------------
bool rvMonsterBossBuddy::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer )
{
	if ( mChaseMode )
	{
		return false;
	}

	return idAI::PerformAction( action, condition ,timer );
}
*/
//------------------------------------------------------------
// rvMonsterBossBuddy::Damage
//------------------------------------------------------------
void rvMonsterBossBuddy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, 
								const char *damageDefName, const float damageScale, const int location ) 
{
	// get damage amount so we can decay the shields and check for ignoreShields
	const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false );
	if ( !damageDef ) 
	{
		gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName );
	}

	// NOTE: there is a damage def for the electrocution that is marked 'ignoreShields'.. when present on the damage def,
	//	we don't run shielding logic
	bool directDamage = damageDef->GetBool( "ignoreShields" );

	int loc = location;
	if ( directDamage )
	{
		// Lame, I know, but hack the location
		loc = INVALID_JOINT;
	}
	else if ( attacker == this )
	{
		// can't damage self
		return;
	}

	float scale = 1;

	// Shields will activate for a set amount of time when damage is being taken
	mLastDamageTime = gameLocal.time;

	// if shields are active, we should try to 'eat' them before directing damage to the BB
	if ( mIsShielded && !directDamage ) 
	{
		// BB is resistant to any kind of splash damage when the shields are up
		if ( loc <= INVALID_JOINT )
		{
			// damage must have been done by splash damage
			return;
		}

		int	damage = damageDef->GetInt( "damage" );
		ReduceShields( damage * 8 );

		// Shielding dramatically reduces actual damage done to BB
		scale = 0.1f;
	}
	else if ( mShields > 0 ) // not currently shielded...does he have shields to use?
	{
		// Yep, so turn them on
		AdjustShieldState( true );
	}

	idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale * scale, loc );
}

//------------------------------------------------------------
// rvMonsterBossBuddy::Pain
//------------------------------------------------------------
bool rvMonsterBossBuddy::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) 
{	
	// immune to small damage.  Is this safe to do?
	if ( damage > 5 )
	{
		rvClientCrawlEffect* effect;
		effect = new rvClientCrawlEffect( gameLocal.GetEffect( spawnArgs, "fx_shieldcrawl" ), this, SEC2MS(spawnArgs.GetFloat ( "shieldCrawlTime", ".2" )) );
		effect->Play( gameLocal.time, false );

		return idAI::Pain( inflictor, attacker, damage, dir, location );
	}
	return false;
}

//------------------------------------------------------------
// rvMonsterBossBuddy::CheckActions
//------------------------------------------------------------
bool rvMonsterBossBuddy::CheckActions( void ) 
{
	// If not moving, try turning in place
/*	if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) 
	{
		float turnYaw = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw );
		if ( turnYaw > lookMax[YAW] * 0.75f ) 
		{
			PerformAction( "Torso_TurnRight90", 4, true );
			return true;
		} else if ( turnYaw < -lookMax[YAW] * 0.75f ) 
		{
			PerformAction( "Torso_TurnLeft90", 4, true );
			return true;
		}
	}
*/
	if ( PerformAction( &mActionMeleeMoveAttack,	(checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ||
		 PerformAction( &mActionSlashMoveAttack,	(checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack )) 
	{
		return true;
	}

	if ( PerformAction( &mActionDarkMatterAttack,	(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ||
		 PerformAction( &mActionRocketAttack,		(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ||
		 PerformAction( &mActionLightningAttack,	(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ))
	{
		return true;
	}

	return idAI::CheckActions( );
}

//------------------------------------------------------------
// rvMonsterBossBuddy::CanTurn
//------------------------------------------------------------
bool rvMonsterBossBuddy::CanTurn( void ) const 
{
/* 	if ( !idAI::CanTurn ( ) ) {
		return false;
	}
	return move.anim_turn_angles != 0.0f || move.fl.moving; 
*/
	return idAI::CanTurn ( );
}

//------------------------------------------------------------
// rvMonsterBossBuddy::OnWakeUp
//------------------------------------------------------------
void rvMonsterBossBuddy::OnWakeUp( void ) 
{
	mActionDarkMatterAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity );
	mActionRocketAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity );
	idAI::OnWakeUp( );
}

//------------------------------------------------------------
//	States 
//------------------------------------------------------------

CLASS_STATES_DECLARATION( rvMonsterBossBuddy )
	STATE( "Torso_RocketAttack",	rvMonsterBossBuddy::State_Torso_RocketAttack )
	STATE( "Torso_SlashAttack",		rvMonsterBossBuddy::State_Torso_SlashAttack )
	STATE( "Torso_TurnRight90",		rvMonsterBossBuddy::State_Torso_TurnRight90 )
	STATE( "Torso_TurnLeft90",		rvMonsterBossBuddy::State_Torso_TurnLeft90 )
END_CLASS_STATES

//------------------------------------------------------------
// rvMonsterBossBuddy::State_Torso_RocketAttack
//------------------------------------------------------------
stateResult_t rvMonsterBossBuddy::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, "attack_rocket2start", parms.blendFrames );
			mShots = (gameLocal.random.RandomInt( 3 ) + 2) * combat.aggressiveScale;
			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, "attack_rocket2loop2", 0 );
			return SRESULT_STAGE( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) 
			{
				if ( --mShots <= 0 ||										// exhausted mShots? .. or
						(!IsEnemyVisible() && rvRandom::irand(0,10)>=8 ) ||	// ... player is no longer visible .. or
						( enemy.ent && DistanceTo(enemy.ent)<256 ) ) 		// ... player is so close, we prolly want to do a melee attack
				{
					PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2end", 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; 
}

//------------------------------------------------------------
// rvMonsterBossBuddy::State_Torso_SlashAttack
//------------------------------------------------------------
stateResult_t rvMonsterBossBuddy::State_Torso_SlashAttack( const stateParms_t& parms ) 
{
	enum 
	{ 
		STAGE_INIT,
		STAGE_WAIT_FIRST_SWIPE,
		STAGE_WAIT_FINISH
	};
	switch ( parms.stage ) 
	{
		case STAGE_INIT:
			DisableAnimState( ANIMCHANNEL_LEGS );
			PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames );
			return SRESULT_STAGE( STAGE_WAIT_FIRST_SWIPE );
			
		case STAGE_WAIT_FIRST_SWIPE:
			if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) 
			{
				PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames );
				return SRESULT_STAGE( STAGE_WAIT_FINISH );
			}
			return SRESULT_WAIT;

		case STAGE_WAIT_FINISH:
			if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) 
			{
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR; 
}

//------------------------------------------------------------
// rvMonsterBossBuddy::State_Torso_TurnRight90
//------------------------------------------------------------
stateResult_t rvMonsterBossBuddy::State_Torso_TurnRight90( const stateParms_t& parms ) 
{
	enum 
	{ 
		STAGE_INIT,
		STAGE_WAIT
	};
	switch ( parms.stage ) 
	{
		case STAGE_INIT:
			DisableAnimState( ANIMCHANNEL_LEGS );
			PlayAnim( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames );
			AnimTurn( 90.0f, true );
			return SRESULT_STAGE( STAGE_WAIT );
			
		case STAGE_WAIT:
			if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) 
			{
				AnimTurn( 0, true );
				combat.investigateTime = gameLocal.time + 250;
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR; 
}

//------------------------------------------------------------
// rvMonsterBossBuddy::State_Torso_TurnLeft90
//------------------------------------------------------------
stateResult_t rvMonsterBossBuddy::State_Torso_TurnLeft90( const stateParms_t& parms ) 
{	
	enum 
	{ 
		STAGE_INIT,
		STAGE_WAIT
	};
	switch ( parms.stage ) 
	{
		case STAGE_INIT:
			DisableAnimState( ANIMCHANNEL_LEGS );
			PlayAnim( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames );
			AnimTurn( 90.0f, true );
			return SRESULT_STAGE( STAGE_WAIT );
			
		case STAGE_WAIT:
			if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) 
			{
				AnimTurn( 0, true );
				combat.investigateTime = gameLocal.time + 250;
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR; 
}
