



#include "../Game_local.h"

class rvMonsterSentry : public idAI {
public:

	CLASS_PROTOTYPE( rvMonsterSentry );

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

	virtual bool		Pain				( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );

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

protected:

	rvAIAction			actionBlasterAttack;
	rvAIAction			actionKamakaziAttack;
	rvAIAction			actionCircleStrafe;

	int					shots;
	int					minShots;
	int					maxShots;
	int					kamakaziHealth;
	int					strafeTime;
	bool				strafeRight;

	virtual bool		CheckActions		( void );
	virtual int			FilterTactical		( int availableTactical );

	virtual void		OnDeath				( void );

	void				Explode				( bool force = false );

private:

	int					nextChatterTime;

	bool				CheckAction_CircleStrafe		( rvAIAction* action, int animNum );

	// Torso States
	stateResult_t		State_Torso_BlasterAttack		( const stateParms_t& parms );
	stateResult_t		State_Torso_KamakaziAttack		( const stateParms_t& parms );
	stateResult_t		State_CircleStrafe				( const stateParms_t& parms );
	stateResult_t		State_Action_CircleStrafe		( const stateParms_t& parms );
	stateResult_t		State_Torso_EvadeLeft			( const stateParms_t& parms );
	stateResult_t		State_Torso_EvadeRight			( const stateParms_t& parms );

	CLASS_STATES_PROTOTYPE ( rvMonsterSentry );
};

CLASS_DECLARATION( idAI, rvMonsterSentry )
END_CLASS

/*
================
rvMonsterSentry::rvMonsterSentry
================
*/
rvMonsterSentry::rvMonsterSentry ( void ) {
	strafeTime	= 0;
	strafeRight = false;
}

void rvMonsterSentry::InitSpawnArgsVariables( void )
{
	minShots = spawnArgs.GetInt ( "minShots" );
	maxShots = spawnArgs.GetInt ( "maxShots" );
}
/*
================
rvMonsterSentry::Spawn
================
*/
void rvMonsterSentry::Spawn ( void ) {
	// Custom actions
	actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK );		
	actionKamakaziAttack.Init ( spawnArgs, "action_kamakaziAttack", "Torso_KamakaziAttack", AIACTIONF_ATTACK );
	actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "State_Action_CircleStrafe", AIACTIONF_ATTACK );
	
	InitSpawnArgsVariables();

	kamakaziHealth = spawnArgs.GetInt ( "kamakazi_Health", va("%d", health / 2) );
	nextChatterTime = 0;
} 

/*
================
rvMonsterSentry::Save
================
*/
void rvMonsterSentry::Save ( idSaveGame *savefile ) const {
	actionBlasterAttack.Save( savefile );
	actionKamakaziAttack.Save( savefile );
	actionCircleStrafe.Save( savefile );

	savefile->WriteInt( shots );
	savefile->WriteInt( kamakaziHealth );
	savefile->WriteInt ( strafeTime	);
	savefile->WriteBool ( strafeRight );
	savefile->WriteInt ( nextChatterTime );
}

/*
================
rvMonsterSentry::Restore
================
*/
void rvMonsterSentry::Restore ( idRestoreGame *savefile ) {
	actionBlasterAttack.Restore( savefile );
	actionKamakaziAttack.Restore( savefile );
	actionCircleStrafe.Restore( savefile );

	savefile->ReadInt( shots );
	savefile->ReadInt( kamakaziHealth );
	savefile->ReadInt ( strafeTime	);
	savefile->ReadBool ( strafeRight );
	savefile->ReadInt ( nextChatterTime );

	InitSpawnArgsVariables();
}

/*
================
rvMonsterSentry::Pain
================
*/
bool rvMonsterSentry::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
	if ( kamakaziHealth > 0 && health < kamakaziHealth ) {
		kamakaziHealth = 0;		
		move.speed = move.fly_speed = spawnArgs.GetFloat ( "kamakazi_fly_speed", va("%g", move.fly_speed ) );
		move.turnRate = spawnArgs.GetFloat ( "kamakazi_turn_rate", va("%g", move.turnRate ) );
		move.fly_pitch_max = spawnArgs.GetFloat ( "kamakazi_fly_pitch_max" );
		move.fly_pitch_scale = spawnArgs.GetFloat ( "kamakazi_fly_pitch_scale" );
	}
		
	return idAI::Pain ( inflictor, attacker, damage, dir, location );
}

/*
================
rvMonsterSentry::Think
================
*/
void rvMonsterSentry::Think ( void ) {
	// Explode whenever we hit something 
	if ( kamakaziHealth == 0 && physicsObj.GetSlideMoveEntity () ) {
		Explode ( );
	} else {
		/*
		if ( nextChatterTime < gameLocal.GetTime() ) {
			if ( GetEnemy() ) {
				Speak( "lipsync_chatter_combat" );
				nextChatterTime = speakTime + 2000 + gameLocal.random.RandomInt( 2000 );
			} else {
				Speak( "lipsync_chatter_idle" );
				nextChatterTime = speakTime + 3000 + gameLocal.random.RandomInt( 3000 );
			}
		}
		*/

		idAI::Think ( );
	}
}

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

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

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

	return true;
}

/*
================
rvMonsterSentry::CheckActions
================
*/
bool rvMonsterSentry::CheckActions ( void ) {
	if ( kamakaziHealth != 0 ) {
		if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) {
			return true;
		}
		if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterSentry::CheckAction_CircleStrafe ) ) {
			return true;
		}
	} else {
		if ( PerformAction ( &actionKamakaziAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) {
			return true;
		}
	}
	
	if ( idAI::CheckActions ( ) ) {
		return true;
	}

	return false;
}

/*
================
rvMonsterSentry::FilterTactical
================
*/
int rvMonsterSentry::FilterTactical ( int availableTactical ) {
	// Kamakazi Health will be set to 0 when its time to kamakazi
	if ( !kamakaziHealth ) {
		return availableTactical & (AITACTICAL_MELEE_BIT|AITACTICAL_TURRET_BIT);
	// No rush when not in kamakazi mode
	} else {
		availableTactical &= ~(AITACTICAL_MELEE_BIT);
	}
		
	return idAI::FilterTactical ( availableTactical );
}

/*
================
rvMonsterSentry::OnDeath
================
*/
void rvMonsterSentry::OnDeath ( void ) {
	idAI::OnDeath ( );
	
	Explode ( true );
}

/*
================
rvMonsterSentry::Explode
================
*/
void rvMonsterSentry::Explode ( bool force ) {
	if ( health > 0 || force ) {
		gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString ( "def_kamakazi_damage" ), 1.0f );
		
		// Kill ourselves
		Damage ( this, this, viewAxis[0], "damage_gib", 1.0f, 0 );
	}
}

/*
================
rvMonsterSentry::GetDebugInfo
================
*/
void rvMonsterSentry::GetDebugInfo	( debugInfoProc_t proc, void* userData ) {
	// Base class first
	idAI::GetDebugInfo ( proc, userData );
	
	proc ( "idAI", "action_blasterAttack",	aiActionStatusString[actionBlasterAttack.status], userData );
	proc ( "idAI", "action_kamakaziAttack",	aiActionStatusString[actionKamakaziAttack.status], userData );	
	proc ( "idAI", "action_circleStrafe",	aiActionStatusString[actionCircleStrafe.status], userData );	
	proc ( "rvMonsterSentry", "strafeRight",strafeRight?"true":"false", userData );
	proc ( "rvMonsterSentry", "strafeTime",	va("%d",strafeTime), userData );
	proc ( "rvMonsterSentry", "nextChatterTime",	va("%d",nextChatterTime), userData );
}

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

	States 

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

CLASS_STATES_DECLARATION ( rvMonsterSentry )
	STATE ( "Torso_BlasterAttack",		rvMonsterSentry::State_Torso_BlasterAttack )
	STATE ( "Torso_KamakaziAttack",		rvMonsterSentry::State_Torso_KamakaziAttack )
	STATE ( "State_CircleStrafe",		rvMonsterSentry::State_CircleStrafe )
	STATE ( "State_Action_CircleStrafe",rvMonsterSentry::State_Action_CircleStrafe )
	STATE ( "Torso_EvadeLeft",			rvMonsterSentry::State_Torso_EvadeLeft )
	STATE ( "Torso_EvadeRight",			rvMonsterSentry::State_Torso_EvadeRight )
END_CLASS_STATES

/*
================
rvMonsterSentry::State_Torso_BlasterAttack
================
*/
stateResult_t rvMonsterSentry::State_Torso_BlasterAttack ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_ATTACK,
		STAGE_WAIT,
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale;
			return SRESULT_STAGE ( STAGE_ATTACK );

		case STAGE_ATTACK:			
			PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames );
			shots--;
			return SRESULT_STAGE ( STAGE_WAIT );
			
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov)  ) {
					return SRESULT_DONE;
				}
				return SRESULT_STAGE ( STAGE_ATTACK );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvMonsterSentry::State_Torso_KamakaziAttack
================
*/
stateResult_t rvMonsterSentry::State_Torso_KamakaziAttack ( const stateParms_t& parms ) {
	Explode ( );
	return SRESULT_DONE;
}

stateResult_t  rvMonsterSentry::State_CircleStrafe ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_CIRCLE
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			if ( strafeTime ) {
				//already strafing, just change dir?
				strafeTime = (strafeTime<gameLocal.GetTime()+2000)?gameLocal.GetTime()+2000:strafeTime;
				strafeRight = !strafeRight;
			} else {
				strafeTime = gameLocal.GetTime() + 8000;
				//FIXME: try to see which side it clear?
				strafeRight = (gameLocal.random.RandomFloat()>0.5f);
			}
			return SRESULT_STAGE ( STAGE_CIRCLE );
		case STAGE_CIRCLE:
			if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) {
				//FIXME: also stop if I bump into something
				strafeTime = 0;
				SetState( "State_Combat" );
				return SRESULT_DONE;
			}
			idVec3 vel = GetPhysics()->GetLinearVelocity();
			vel += viewAxis[1] * (strafeRight?-200:200);
			vel.Normalize();
			vel *= 200.0f;
			physicsObj.UseVelocityMove( true );
			GetPhysics()->SetLinearVelocity( vel );
			TurnToward( enemy.lastKnownPosition );

			// Perform actions
			if ( UpdateAction ( ) ) {
				return SRESULT_WAIT;
			}

			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

stateResult_t rvMonsterSentry::State_Action_CircleStrafe ( const stateParms_t& parms ) {
	SetState( "State_CircleStrafe" );
	return SRESULT_DONE;
}

stateResult_t rvMonsterSentry::State_Torso_EvadeLeft ( const stateParms_t& parms ) {
	if ( strafeTime ) {
		strafeRight = false;
	} else {
		idVec3 vel = GetPhysics()->GetLinearVelocity();
		vel += viewAxis[1] * 400;
		physicsObj.UseVelocityMove( true );
		GetPhysics()->SetLinearVelocity( vel );
	}
	return SRESULT_DONE;
}

stateResult_t rvMonsterSentry::State_Torso_EvadeRight ( const stateParms_t& parms ) {
	if ( strafeTime ) {
		strafeRight = true;
	} else {
		idVec3 vel = GetPhysics()->GetLinearVelocity();
		vel += viewAxis[1] * -400;
		physicsObj.UseVelocityMove( true );
		GetPhysics()->SetLinearVelocity( vel );
	}
	return SRESULT_DONE;
}
