//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "ai_behavior_functank.h"
#include "ai_navigator.h"
#include "ai_memory.h"
#include "ai_senses.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

// How long to fire a func tank before running schedule selection again.
#define FUNCTANK_FIRE_TIME	5.0f

BEGIN_DATADESC( CAI_FuncTankBehavior )
	DEFINE_FIELD( m_hFuncTank, FIELD_EHANDLE ),
	DEFINE_FIELD( m_bMounted, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flBusyTime, FIELD_TIME ),
	DEFINE_FIELD( m_bSpottedPlayerOutOfCover, FIELD_BOOLEAN ),
END_DATADESC();

//-----------------------------------------------------------------------------
// Purpose: Constructor
//--k---------------------------------------------------------------------------
CAI_FuncTankBehavior::CAI_FuncTankBehavior()
{
	m_hFuncTank = NULL;
	m_bMounted = false;
	m_flBusyTime = 0.0f;
	m_bSpottedPlayerOutOfCover = false;
}

//-----------------------------------------------------------------------------
// Purpose: Deconstructor
//-----------------------------------------------------------------------------
CAI_FuncTankBehavior::~CAI_FuncTankBehavior()
{
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CAI_FuncTankBehavior::CanSelectSchedule()
{
	// If we don't have a func_tank do not bother with conditions, schedules, etc.
	if ( !m_hFuncTank )
		return false;

	// Are you alive, in a script?
	if ( !GetOuter()->IsInterruptable() )
		return false;
	
	// Commander is giving you orders?
	if ( GetOuter()->HasCondition( COND_RECEIVED_ORDERS ) )
		return false;
	
	return true;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::BeginScheduleSelection()
{
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::EndScheduleSelection()
{
	if ( m_bMounted )
	{
		Dismount();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::PrescheduleThink()
{
	BaseClass::PrescheduleThink();

	if ( !HasCondition(COND_SEE_PLAYER) )
	{
		m_bSpottedPlayerOutOfCover = false;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int	CAI_FuncTankBehavior::SelectSchedule()
{
	// This shouldn't get called with an m_hFuncTank, see CanSelectSchedule.
	Assert( m_hFuncTank );

	// If we've been told to dismount, or we are out of ammo - dismount.
	if ( HasCondition( COND_FUNCTANK_DISMOUNT ) || m_hFuncTank->GetAmmoCount() == 0 )
	{
		if ( m_bMounted )
		{
			Dismount();
		}

		return BaseClass::SelectSchedule();
	}

	// If we are not mounted to a func_tank look for one.
	if ( !IsMounted() )
	{
		return SCHED_MOVE_TO_FUNCTANK;
	}

	// If we have an enemy, it's in the viewcone & we have LOS to it
	if ( GetEnemy() )
	{
		// Tell the func tank whenever we see the player for the first time since not seeing him for a while
		if ( HasCondition( COND_NEW_ENEMY ) && GetEnemy()->IsPlayer() && !m_bSpottedPlayerOutOfCover )
		{
			m_bSpottedPlayerOutOfCover = true;
			m_hFuncTank->NPC_JustSawPlayer( GetEnemy() );
		}

		// Fire at the enemy.
		return SCHED_FIRE_FUNCTANK;
	}
	else
	{
		// Scan for enemies.
		return SCHED_SCAN_WITH_FUNCTANK;
	}

	return SCHED_IDLE_STAND;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : activity - 
// Output : Activity
//-----------------------------------------------------------------------------
Activity CAI_FuncTankBehavior::NPC_TranslateActivity( Activity activity )
{
	// If I'm on the gun, I play the idle manned gun animation
	if ( m_bMounted ) 
		return ACT_IDLE_MANNEDGUN;

	return BaseClass::NPC_TranslateActivity( activity );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::Dismount( void )
{
	SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );

	Assert( m_hFuncTank );

	if ( m_hFuncTank )
	{
		GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_DISMOUNTING );

		Assert( m_hFuncTank->IsMarkedForDeletion() || m_hFuncTank->GetController() == GetOuter() );
		
		m_hFuncTank->NPC_SetInRoute( false );
		if ( m_hFuncTank->GetController() == GetOuter() )
			m_hFuncTank->StopControl();
		SetFuncTank( NULL );
	}

	m_bMounted = false;

	// Set this condition to force breakout of any func_tank behavior schedules
	SetCondition( COND_FUNCTANK_DISMOUNT );
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
int CAI_FuncTankBehavior::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	int iResult = BaseClass::OnTakeDamage_Alive( info );
	if ( !iResult )
		return 0;

	// If we've been hit by the player, and the player's not targetable 
	// by our func_tank, get off the tank.
	CBaseEntity *pAttacker = info.GetAttacker();
	if ( m_hFuncTank && pAttacker && pAttacker->IsPlayer() )
	{
		if ( !m_hFuncTank->IsEntityInViewCone( pAttacker ) )
		{
			SetCondition( COND_FUNCTANK_DISMOUNT );
		}
	}

	return iResult;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::StartTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_FUNCTANK_ANNOUNCE_SCAN:
		{
			if ( random->RandomInt( 0, 3 ) == 0 )
			{
				GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_SCAN_FOR_ENEMIES );
			}
			TaskComplete();
		}
		break;

	case TASK_GET_PATH_TO_FUNCTANK:
		{
			if ( !m_hFuncTank )
			{
				TaskFail( FAIL_NO_TARGET  );
				return;
			}

			Vector vecManPos;
			m_hFuncTank->NPC_FindManPoint( vecManPos );
			AI_NavGoal_t goal( vecManPos );
			goal.pTarget = m_hFuncTank;
			if ( GetNavigator()->SetGoal( goal ) )
			{
				GetNavigator()->SetArrivalDirection( m_hFuncTank->GetAbsAngles() );
				TaskComplete();
			}
			else
			{
				TaskFail("NO PATH");

				// Don't try and use me again for a while
				SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
			}
			break;
		}		
	case TASK_FACE_FUNCTANK:
		{
			if ( !m_hFuncTank )
			{
				TaskFail( FAIL_NO_TARGET  );
				return;
			}
			
			// Ensure we've reached the func_tank
			Vector vecManPos;
			m_hFuncTank->NPC_FindManPoint( vecManPos );

			// More leniency in Z.
			Vector vecDelta = (vecManPos - GetAbsOrigin());
			if ( fabs(vecDelta.x) > 16 || fabs(vecDelta.y) > 16 || fabs(vecDelta.z) > 48 )
			{
				TaskFail( "Not correctly on func_tank man point" );
				m_hFuncTank->NPC_InterruptRoute();
				return;
			}

			GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );
			GetOuter()->SetTurnActivity(); 
			break;
		}

	case TASK_FIRE_FUNCTANK:
		{
			if ( !m_hFuncTank )
			{
				TaskFail( FAIL_NO_TARGET );
				return;
			}
			GetOuter()->m_flWaitFinished = gpGlobals->curtime + FUNCTANK_FIRE_TIME;
			break;
		}
	case TASK_SCAN_LEFT_FUNCTANK:
		{
			if ( !m_hFuncTank )
			{
				TaskFail( FAIL_NO_TARGET );
				return;
			}

			GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );

			float flCenterYaw = m_hFuncTank->YawCenterWorld();
			float flYawRange = m_hFuncTank->YawRange();
			float flScanAmount = random->RandomFloat( 0, flYawRange );
			QAngle vecTargetAngles( 0, UTIL_AngleMod( flCenterYaw + flScanAmount ), 0 );

			/*
			float flCenterPitch = m_hFuncTank->YawCenterWorld();
			float flPitchRange = m_hFuncTank->PitchRange();
			float flPitch = random->RandomFloat( -flPitchRange, flPitchRange );
			QAngle vecTargetAngles( flCenterPitch + flPitch, UTIL_AngleMod( flCenterYaw + flScanAmount ), 0 );
			*/

			Vector vecTargetForward;
			AngleVectors( vecTargetAngles, &vecTargetForward );
			Vector vecTarget = GetOuter()->EyePosition() + (vecTargetForward * 256);
			GetOuter()->AddLookTarget( vecTarget, 1.0, 2.0, 0.2 );

			m_hFuncTank->NPC_SetIdleAngle( vecTarget );

			break;
		}
	case TASK_SCAN_RIGHT_FUNCTANK:
		{
			if ( !m_hFuncTank )
			{
				TaskFail( FAIL_NO_TARGET );
				return;
			}

			GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );

			float flCenterYaw = m_hFuncTank->YawCenterWorld();
			float flYawRange = m_hFuncTank->YawRange();
			float flScanAmount = random->RandomFloat( 0, flYawRange );
			QAngle vecTargetAngles( 0, UTIL_AngleMod( flCenterYaw - flScanAmount ), 0 );

			/*
			float flCenterPitch = m_hFuncTank->YawCenterWorld();
			float flPitchRange = m_hFuncTank->PitchRange();
			float flPitch = random->RandomFloat( -flPitchRange, flPitchRange );
			QAngle vecTargetAngles( flCenterPitch + flPitch, UTIL_AngleMod( flCenterYaw - flScanAmount ), 0 );
			*/

			Vector vecTargetForward;
			AngleVectors( vecTargetAngles, &vecTargetForward );
			Vector vecTarget = GetOuter()->EyePosition() + (vecTargetForward * 256);
			GetOuter()->AddLookTarget( vecTarget, 1.0, 2.0, 0.2 );

			m_hFuncTank->NPC_SetIdleAngle( vecTarget );

			break;
		}
	case TASK_FORGET_ABOUT_FUNCTANK:
		{
			if ( !m_hFuncTank )
			{
				TaskFail( FAIL_NO_TARGET  );
				return;
			}
			break;
		}
	default:
		{
			BaseClass::StartTask( pTask );
			break;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_FACE_FUNCTANK:
		{
			Assert( m_hFuncTank );

			GetMotor()->UpdateYaw();

			if ( GetOuter()->FacingIdeal() )
			{
				GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_JUST_MOUNTED );

				// We are at the correct position and facing for the func_tank, mount it.
				m_hFuncTank->StartControl( GetOuter() );
				GetOuter()->ClearEnemyMemory();
				m_bMounted = true;
				TaskComplete();

				GetOuter()->SetIdealActivity( ACT_IDLE_MANNEDGUN );
			}
			break;
		}
	case TASK_FIRE_FUNCTANK:
		{
			Assert( m_hFuncTank );

			if( GetOuter()->m_flWaitFinished < gpGlobals->curtime )
			{
				TaskComplete();
			}

			if ( m_hFuncTank->NPC_HasEnemy() )
			{
				GetOuter()->SetLastAttackTime( gpGlobals->curtime );
				m_hFuncTank->NPC_Fire();

				// The NPC may have decided to stop using the func_tank, because it's out of ammo.
				if ( !m_hFuncTank )
				{
					TaskComplete();
					break;
				}
			}
			else
			{
				TaskComplete();
			}
			
			Assert( m_hFuncTank );

			if ( m_hFuncTank->GetAmmoCount() == 0 )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_SCAN_LEFT_FUNCTANK:
	case TASK_SCAN_RIGHT_FUNCTANK:
		{
			GetMotor()->UpdateYaw();
			if ( GetOuter()->FacingIdeal() )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_FORGET_ABOUT_FUNCTANK:
		{
			m_hFuncTank->NPC_InterruptRoute();
			SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
			TaskComplete();
			break;
		}
	default:
		{
			BaseClass::RunTask( pTask );
			break;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::Event_Killed( const CTakeDamageInfo &info )
{
	if ( m_hFuncTank )
	{
		Dismount();
	}
	Assert( !m_hFuncTank );

	BaseClass::Event_Killed( info );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::UpdateOnRemove( void )
{
	if ( m_hFuncTank )
	{
		Dismount();
	}

	BaseClass::UpdateOnRemove();
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::SetFuncTank( CHandle<CFuncTank> hFuncTank )			
{ 
	if ( m_hFuncTank && !hFuncTank )
	{
		SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
		SetCondition( COND_FUNCTANK_DISMOUNT );
	}

	m_hFuncTank = hFuncTank; 
	GetOuter()->ClearSchedule();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::AimGun( void )
{
	if ( m_bMounted && m_hFuncTank)
	{
		Vector vecForward;
		AngleVectors( m_hFuncTank->GetAbsAngles(), &vecForward );
		GetOuter()->SetAim( vecForward );
		return;
	}

	BaseClass::AimGun();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_FuncTankBehavior::GatherConditions()
{
	BaseClass::GatherConditions();

	// Since we can't pathfind, if we can't see the enemy, he's eluded us
	// So we deliberately ignore unreachability 
	if ( GetEnemy() && !HasCondition(COND_SEE_ENEMY) )
	{
		if ( gpGlobals->curtime - GetOuter()->GetEnemyLastTimeSeen() >= 3.0f )
		{
			GetOuter()->MarkEnemyAsEluded();
		}
	}

	if ( !m_hFuncTank )
		m_bMounted = false;
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseEntity *CAI_FuncTankBehavior::BestEnemy( void )
{
	// Only use this BestEnemy call when we are on the manned gun.
	if ( !m_hFuncTank ||!IsMounted() )
		return BaseClass::BestEnemy();

	CBaseEntity *pBestEnemy	= NULL;
	int	iBestDistSq	= MAX_COORD_RANGE * MAX_COORD_RANGE;	// so first visible entity will become the closest.
	int	iBestPriority = -1000;
	bool bBestUnreachable = false;							// Forces initial check
	bool bBestSeen = false;
	bool bUnreachable = false;
	int	iDistSq;

	AIEnemiesIter_t iter;

	// Get the current npc for checking from.
	CAI_BaseNPC *pNPC = GetOuter();
	if ( !pNPC )
		return NULL;

	for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) )
	{
		CBaseEntity *pEnemy = pEMemory->hEnemy;
		if ( !pEnemy || !pEnemy->IsAlive() )
			continue;
		
		// UNDONE: Move relationship checks into IsValidEnemy?
		if ( ( pEnemy->GetFlags() & FL_NOTARGET ) || 
			 ( pNPC->IRelationType( pEnemy ) != D_HT && pNPC->IRelationType( pEnemy ) != D_FR ) ||
			 !IsValidEnemy( pEnemy ) )
			continue;

		if ( pEMemory->timeLastSeen < pNPC->GetAcceptableTimeSeenEnemy() )
			continue;

		if ( pEMemory->timeValidEnemy > gpGlobals->curtime )
			continue;

		// Skip enemies that have eluded me to prevent infinite loops
		if ( GetEnemies()->HasEludedMe( pEnemy ) )
			continue;

		// Establish the reachability of this enemy
		bUnreachable = pNPC->IsUnreachable( pEnemy );

		// Check view cone of the view tank here.
		bUnreachable = !m_hFuncTank->IsEntityInViewCone( pEnemy );
		if ( !bUnreachable )
		{
			// It's in the viewcone. Now make sure we have LOS to it.
			bUnreachable = !m_hFuncTank->HasLOSTo( pEnemy );
		}

		// If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority
		if ( !bBestUnreachable && bUnreachable )
			continue;

		//  If best is unreachable and current is reachable, always pick the current regardless of priority
		if ( bBestUnreachable && !bUnreachable )
		{
			bBestSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) ); // @TODO (toml 04-02-03): Need to optimize CanSeeEntity() so multiple calls in frame do not recalculate, rather cache
			iBestPriority = pNPC->IRelationPriority( pEnemy );
			iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
			pBestEnemy = pEnemy;
			bBestUnreachable = bUnreachable;
		}
		// If both are unreachable or both are reachable, chose enemy based on priority and distance
		else if ( pNPC->IRelationPriority( pEnemy ) > iBestPriority )
		{
			// this entity is disliked MORE than the entity that we
			// currently think is the best visible enemy. No need to do
			// a distance check, just get mad at this one for now.
			iBestPriority = pNPC->IRelationPriority ( pEnemy );
			iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
			pBestEnemy = pEnemy;
			bBestUnreachable = bUnreachable;
		}
		else if ( pNPC->IRelationPriority( pEnemy ) == iBestPriority )
		{
			// this entity is disliked just as much as the entity that
			// we currently think is the best visible enemy, so we only
			// get mad at it if it is closer.
			iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();

			bool bCloser = ( iDistSq < iBestDistSq ) ;

			if ( bCloser || !bBestSeen )
			{
				// @TODO (toml 04-02-03): Need to optimize FVisible() so multiple calls in frame do not recalculate, rather cache
				bool fSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) );
				if ( ( bCloser && ( fSeen || !bBestSeen ) ) || ( !bCloser && !bBestSeen && fSeen ) )
				{
					bBestSeen = fSeen;
					iBestDistSq = iDistSq;
					iBestPriority = pNPC->IRelationPriority( pEnemy );
					pBestEnemy = pEnemy;
					bBestUnreachable = bUnreachable;
				}
			}
		}
	}
	return pBestEnemy;
}

//=============================================================================
//
// Custom AI schedule data
//

AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FuncTankBehavior )

	DECLARE_TASK( TASK_GET_PATH_TO_FUNCTANK )
	DECLARE_TASK( TASK_FACE_FUNCTANK )
	DECLARE_TASK( TASK_FIRE_FUNCTANK )
	DECLARE_TASK( TASK_SCAN_LEFT_FUNCTANK )
	DECLARE_TASK( TASK_SCAN_RIGHT_FUNCTANK )
	DECLARE_TASK( TASK_FORGET_ABOUT_FUNCTANK )
	DECLARE_TASK( TASK_FUNCTANK_ANNOUNCE_SCAN )

	DECLARE_CONDITION( COND_FUNCTANK_DISMOUNT )

	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE 
	(
		SCHED_MOVE_TO_FUNCTANK,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE		SCHEDULE: SCHED_FAIL_MOVE_TO_FUNCTANK"
		"		TASK_GET_PATH_TO_FUNCTANK	0"
		"		TASK_SPEAK_SENTENCE			1000"	// FUNCTANK_SENTENCE_MOVE_TO_MOUNT
		"		TASK_RUN_PATH				0"
		"		TASK_WAIT_FOR_MOVEMENT		0"
		"		TASK_STOP_MOVING			0"
		"		TASK_FACE_FUNCTANK			0"
		"	"
		"	Interrupts"
		"		COND_FUNCTANK_DISMOUNT"
	)

	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE 
	(
		SCHED_FIRE_FUNCTANK,

		"	Tasks"
		"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
		"       TASK_FIRE_FUNCTANK      0"
		"   "
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_LOST_ENEMY"
		"		COND_ENEMY_OCCLUDED"
		"		COND_WEAPON_BLOCKED_BY_FRIEND"
		"		COND_WEAPON_SIGHT_OCCLUDED"
		"		COND_FUNCTANK_DISMOUNT"
	)

	DEFINE_SCHEDULE 
	(
		SCHED_SCAN_WITH_FUNCTANK,

		"	Tasks"
		"		TASK_FUNCTANK_ANNOUNCE_SCAN	0"
		"		TASK_STOP_MOVING			0"
		"		TASK_WAIT					4"
		"		TASK_SCAN_LEFT_FUNCTANK		0"
		"		TASK_WAIT					4"
		"		TASK_SCAN_RIGHT_FUNCTANK	0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_PROVOKED"
		"		COND_FUNCTANK_DISMOUNT"
	)

	DEFINE_SCHEDULE 
	(
		SCHED_FAIL_MOVE_TO_FUNCTANK,

		"	Tasks"
		"		TASK_FORGET_ABOUT_FUNCTANK		0"
		""
		"	Interrupts"
	)	

AI_END_CUSTOM_SCHEDULE_PROVIDER()
