//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "ai_motor.h"
#include "ai_behavior_fear.h"
#include "ai_hint.h"
#include "ai_navigator.h"

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

BEGIN_DATADESC( CAI_FearBehavior )
	DEFINE_FIELD( m_flTimeToSafety, FIELD_TIME ),
	DEFINE_FIELD( m_flTimePlayerLastVisible, FIELD_TIME ),
	DEFINE_FIELD( m_hSafePlaceHint, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hMovingToHint, FIELD_EHANDLE ),
	DEFINE_EMBEDDED( m_SafePlaceMoveMonitor ),
	DEFINE_FIELD( m_flDeferUntil, FIELD_TIME ),
#ifdef MAPBASE
	DEFINE_FIELD( m_hFearGoal, FIELD_EHANDLE ),
#endif
END_DATADESC();

#define BEHAVIOR_FEAR_SAFETY_TIME		5
#define FEAR_SAFE_PLACE_TOLERANCE		36.0f
#define FEAR_ENEMY_TOLERANCE_CLOSE_DIST_SQR		Square(300.0f) // (25 feet)
#define FEAR_ENEMY_TOLERANCE_TOO_CLOSE_DIST_SQR	Square( 60.0f ) // (5 Feet)

ConVar ai_enable_fear_behavior( "ai_enable_fear_behavior", "1" );

ConVar ai_fear_player_dist("ai_fear_player_dist", "720" );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CAI_FearBehavior::CAI_FearBehavior()
{
	ReleaseAllHints();
	m_SafePlaceMoveMonitor.ClearMark();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_FearBehavior::Precache( void )
{

}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CAI_FearBehavior::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_FEAR_IN_SAFE_PLACE:
		// We've arrived! Lock the hint and set the marker. we're safe for now.
		m_hSafePlaceHint = m_hMovingToHint;
		m_hSafePlaceHint->Lock( GetOuter() );
		m_SafePlaceMoveMonitor.SetMark( GetOuter(), FEAR_SAFE_PLACE_TOLERANCE );
#ifdef MAPBASE
		m_hSafePlaceHint->NPCStartedUsing( GetOuter() );
		if (m_hFearGoal)
			m_hFearGoal->m_OnArriveAtFearNode.FireOutput(m_hSafePlaceHint, GetOuter());
#endif
		TaskComplete();
		break;

	case TASK_FEAR_GET_PATH_TO_SAFETY_HINT:
		// Using TaskInterrupt() optimizations. See RunTask().
		break;

	case TASK_FEAR_WAIT_FOR_SAFETY:
		m_flTimeToSafety = gpGlobals->curtime + BEHAVIOR_FEAR_SAFETY_TIME;
		break;

	default:
		BaseClass::StartTask( pTask );
		break;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CAI_FearBehavior::RunTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_FEAR_WAIT_FOR_SAFETY:
		if( HasCondition(COND_SEE_ENEMY) )
		{
			m_flTimeToSafety = gpGlobals->curtime + BEHAVIOR_FEAR_SAFETY_TIME;
		}
		else
		{
			if( gpGlobals->curtime > m_flTimeToSafety )
			{
				TaskComplete();
			}
		}
		break;

	case TASK_FEAR_GET_PATH_TO_SAFETY_HINT:
		{
			switch( GetOuter()->GetTaskInterrupt() )
			{
			case 0:// Find the hint node
				{
					ReleaseAllHints();
					CAI_Hint *pHint = FindFearWithdrawalDest();

					if( pHint == NULL )
					{
						TaskFail("Fear: Couldn't find hint node\n");
						m_flDeferUntil = gpGlobals->curtime + 3.0f;// Don't bang the hell out of this behavior. If we don't find a node, take a short break and run regular AI.
					}
					else
					{
						m_hMovingToHint.Set( pHint );
						GetOuter()->TaskInterrupt();
					}
				}
				break;

			case 1:// Do the pathfinding.
				{
					Assert( m_hMovingToHint != NULL );

					AI_NavGoal_t goal(m_hMovingToHint->GetAbsOrigin());
					goal.pTarget = NULL;
					if( GetNavigator()->SetGoal( goal ) == false )
					{
						m_hMovingToHint.Set( NULL );
						// Do whatever we'd want to do if we can't find a path
						/*
						Msg("Can't path to the Fear Hint!\n");

						AI_NavGoal_t nearGoal( GOALTYPE_LOCATION_NEAREST_NODE, m_hRallyPoint->GetAbsOrigin(), AIN_DEF_ACTIVITY, 256 );
						if ( GetNavigator()->SetGoal( nearGoal, AIN_CLEAR_PREVIOUS_STATE ) )
						{
						//FIXME: HACK! The internal pathfinding is setting this without our consent, so override it!
						ClearCondition( COND_TASK_FAILED );
						GetNavigator()->SetArrivalDirection( m_hRallyPoint->GetAbsAngles() );
						TaskComplete();
						return;
						}
						*/
					}
					else
					{
#ifdef MAPBASE
						m_hMovingToHint->NPCHandleStartNav( GetOuter(), true );
#else
						GetNavigator()->SetArrivalDirection( m_hMovingToHint->GetAbsAngles() );
#endif
					}
				}
				break;
			}
		}
		break;

	default:
		BaseClass::RunTask( pTask );
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : TRUE if I have an enemy and that enemy would attack me if it could
// Notes  : Returns FALSE if the enemy is neutral or likes me.
//-----------------------------------------------------------------------------
bool CAI_FearBehavior::EnemyDislikesMe()
{
	CBaseEntity *pEnemy = GetEnemy();

	if( pEnemy == NULL )
		return false;

	if( pEnemy->MyNPCPointer() == NULL )
		return false;

	Disposition_t disposition = pEnemy->MyNPCPointer()->IRelationType(GetOuter());

	Assert(disposition != D_ER);

	if( disposition >= D_LI )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// This place is definitely no longer safe. Stop picking it for a while.
//-----------------------------------------------------------------------------
void CAI_FearBehavior::MarkAsUnsafe()
{
	Assert( m_hSafePlaceHint );

	// Disable the node to stop anyone from picking it for a while.
	m_hSafePlaceHint->DisableForSeconds( 5.0f );
}

//-----------------------------------------------------------------------------
// Am I in safe place from my enemy?
//-----------------------------------------------------------------------------
bool CAI_FearBehavior::IsInASafePlace()
{
	// No safe place in mind.
	if( !m_SafePlaceMoveMonitor.IsMarkSet() )
		return false;

	// I have a safe place, but I'm not there.
	if( m_SafePlaceMoveMonitor.TargetMoved(GetOuter()) )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearBehavior::SpoilSafePlace()
{
	m_SafePlaceMoveMonitor.ClearMark();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearBehavior::ReleaseAllHints()
{
	if( m_hSafePlaceHint )
	{
		// If I have a safe place, unlock it for others.
		m_hSafePlaceHint->Unlock();

#ifdef MAPBASE
		m_hSafePlaceHint->NPCStoppedUsing(GetOuter());
#endif

		// Don't make it available right away. I probably left for a good reason.
		// We also don't want to oscillate
		m_hSafePlaceHint->DisableForSeconds( 4.0f );
		m_hSafePlaceHint = NULL;
	}

	if( m_hMovingToHint )
	{
		m_hMovingToHint->Unlock();
		m_hMovingToHint = NULL;
	}

	m_SafePlaceMoveMonitor.ClearMark();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
// Notes  : This behavior runs when I have an enemy that I fear, but who
//			does NOT hate or fear me (meaning they aren't going to fight me)
//-----------------------------------------------------------------------------
bool CAI_FearBehavior::CanSelectSchedule()
{
	if( !GetOuter()->IsInterruptable() )
		return false;

	if( m_flDeferUntil > gpGlobals->curtime )
		return false;

	CBaseEntity *pEnemy = GetEnemy();

	if( pEnemy == NULL )
		return false;

	//if( !HasCondition(COND_SEE_PLAYER) )
	//	return false;

	if( !ai_enable_fear_behavior.GetBool() )
		return false;
	
	if( GetOuter()->IRelationType(pEnemy) != D_FR )
		return false;

#ifdef MAPBASE
	// Don't run fear behavior if we've been ordered somewhere
	if (GetOuter()->GetCommandGoal() != vec3_invalid)
		return false;

	// Don't run fear behavior if we're running any non-follow behaviors
	if (GetOuter()->GetRunningBehavior() && GetOuter()->GetRunningBehavior() != this && !FStrEq(GetOuter()->GetRunningBehavior()->GetName(), "Follow"))
		return false;

	if (m_hFearGoal && m_iszFearTarget != NULL_STRING)
	{
		if (pEnemy->NameMatches(m_iszFearTarget) || pEnemy->ClassMatches(m_iszFearTarget))
			return true;
	}
#endif

	if( !pEnemy->ClassMatches("npc_hunter") )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearBehavior::GatherConditions()
{
	BaseClass::GatherConditions();

	ClearCondition( COND_FEAR_ENEMY_CLOSE );
	ClearCondition( COND_FEAR_ENEMY_TOO_CLOSE );
	if( GetEnemy() )
	{
		float flEnemyDistSqr = GetAbsOrigin().DistToSqr(GetEnemy()->GetAbsOrigin());

		if( flEnemyDistSqr < FEAR_ENEMY_TOLERANCE_TOO_CLOSE_DIST_SQR )
		{
			SetCondition( COND_FEAR_ENEMY_TOO_CLOSE );
			if( IsInASafePlace() )
			{
				SpoilSafePlace();
			}
		}
		else if( flEnemyDistSqr < FEAR_ENEMY_TOLERANCE_CLOSE_DIST_SQR && GetEnemy()->GetEnemy() == GetOuter() )	
		{
			// Only become scared of an enemy at this range if they're my enemy, too
			SetCondition( COND_FEAR_ENEMY_CLOSE );
			if( IsInASafePlace() )
			{
				SpoilSafePlace();
			}
		}
	}

	ClearCondition(COND_FEAR_SEPARATED_FROM_PLAYER);

	// Check for separation from the player
	//	-The player is farther away than 60 feet
	//  -I haven't seen the player in 2 seconds
	//
	// Here's the distance check:
	CBasePlayer *pPlayer = AI_GetSinglePlayer();
	if( pPlayer != NULL && GetAbsOrigin().DistToSqr(pPlayer->GetAbsOrigin()) >= Square( ai_fear_player_dist.GetFloat() * 1.5f )  )
	{
		SetCondition(COND_FEAR_SEPARATED_FROM_PLAYER);
	}

	// Here's the visibility check. We can't skip this because it's time-sensitive
	if( GetOuter()->FVisible(pPlayer) )
	{
		m_flTimePlayerLastVisible = gpGlobals->curtime;
	}
	else
	{
		if( gpGlobals->curtime - m_flTimePlayerLastVisible >= 2.0f )
		{
			SetCondition(COND_FEAR_SEPARATED_FROM_PLAYER);
		}
	}

	if( HasCondition(COND_FEAR_SEPARATED_FROM_PLAYER) )
	{
		//Msg("I am separated from player\n");

		if( IsInASafePlace() )
		{
			SpoilSafePlace();
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearBehavior::BeginScheduleSelection()
{
	if( m_hSafePlaceHint )
	{
		// We think we're safe. Is it true?
		if( !IsInASafePlace() )
		{
			// no! So mark it so.
			ReleaseAllHints();
		}
	}

	m_flTimePlayerLastVisible = gpGlobals->curtime;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearBehavior::EndScheduleSelection()
{
	// We don't have to release our hints or markers or anything here. 
	// Just because we ran other AI for a while doesn't mean we aren't still in a safe place.
	//ReleaseAllHints();
}




//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
// Notes  : If fear behavior is running at all, we know we're afraid of our enemy
//-----------------------------------------------------------------------------
int CAI_FearBehavior::SelectSchedule()
{
	bool bInSafePlace = IsInASafePlace();

	if( !HasCondition(COND_HEAR_DANGER) )
	{
		if( !bInSafePlace )
		{
			// Always move to a safe place if we're not running from a danger sound
			return SCHED_FEAR_MOVE_TO_SAFE_PLACE;
		}
		else
		{
			// We ARE in a safe place
			if( HasCondition(COND_CAN_RANGE_ATTACK1) )
				return SCHED_RANGE_ATTACK1;

			return SCHED_FEAR_STAY_IN_SAFE_PLACE;
		}
	}

	return BaseClass::SelectSchedule();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearBehavior::BuildScheduleTestBits()
{
	BaseClass::BuildScheduleTestBits();

	if( GetOuter()->GetState() != NPC_STATE_SCRIPT )
	{
		// Stop doing ANYTHING if we get scared.
		//GetOuter()->SetCustomInterruptCondition( COND_HEAR_DANGER );

		if( !IsCurSchedule(SCHED_FEAR_MOVE_TO_SAFE_PLACE_RETRY, false) && !IsCurSchedule(SCHED_FEAR_MOVE_TO_SAFE_PLACE, false) )
		{
			GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal(COND_FEAR_SEPARATED_FROM_PLAYER) );
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CAI_FearBehavior::TranslateSchedule( int scheduleType )
{
	switch( scheduleType )
	{
	case SCHED_FEAR_MOVE_TO_SAFE_PLACE:
		if( HasCondition(COND_FEAR_ENEMY_TOO_CLOSE) )
		{
			// If I'm moving to a safe place AND have an enemy too close to me,
			// make the move to safety while ignoring the condition.
			// this stops an oscillation
			// IS THIS CODE EVER EVEN BEING CALLED? (sjb)
			return SCHED_FEAR_MOVE_TO_SAFE_PLACE_RETRY;
		}
		break;
	}

	return BaseClass::TranslateSchedule( scheduleType );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CAI_Hint *CAI_FearBehavior::FindFearWithdrawalDest()
{
	CAI_Hint *pHint;
	CHintCriteria hintCriteria;
	CAI_BaseNPC *pOuter = GetOuter();

	Assert(pOuter != NULL);

	hintCriteria.AddHintType( HINT_PLAYER_ALLY_FEAR_DEST );
	hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE_TO_PLAYER | bits_HINT_NOT_CLOSE_TO_ENEMY /*| bits_HINT_NODE_IN_VIEWCONE | bits_HINT_NPC_IN_NODE_FOV*/ );
#ifdef MAPBASE
	hintCriteria.SetFlag(bits_HINT_NODE_USE_GROUP);
#endif
	hintCriteria.AddIncludePosition( AI_GetSinglePlayer()->GetAbsOrigin(), ( ai_fear_player_dist.GetFloat() ) );

	pHint = CAI_HintManager::FindHint( pOuter, hintCriteria );

	if( pHint )
	{
		// Reserve this node while I try to get to it. When I get there I will lock it.
		// Otherwise, if I fail to get there, the node will come available again soon.
		pHint->DisableForSeconds( 4.0f );
	}
#if 0
	else
	{
		Msg("DID NOT FIND HINT\n");
		NDebugOverlay::Cross3D( GetOuter()->WorldSpaceCenter(), 32, 255, 255, 0, false, 10.0f );
	}
#endif

	return pHint;
}

#ifdef MAPBASE
//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
Activity CAI_FearBehavior::NPC_TranslateActivity( Activity activity )
{
	if ( activity == ACT_IDLE && m_hSafePlaceHint && m_hSafePlaceHint->HintActivityName() != NULL_STRING )
	{
		return GetOuter()->GetHintActivity(m_hSafePlaceHint->HintType(), (Activity)CAI_BaseNPC::GetActivityID( STRING(m_hSafePlaceHint->HintActivityName()) ) );
	}
	return BaseClass::NPC_TranslateActivity( activity );
}

//-----------------------------------------------------------------------------
// Updates the fear goal's target.
//-----------------------------------------------------------------------------
void CAI_FearBehavior::OnRestore()
{
	BaseClass::OnRestore();

	if (m_hFearGoal.Get() != NULL)
	{
		m_iszFearTarget = m_hFearGoal->m_target;
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearBehavior::SetParameters( CAI_FearGoal *pGoal, string_t target )
{
	m_hFearGoal = pGoal;
	m_iszFearTarget = target;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

//=============================================================================
//=============================================================================
// >AI_GOAL_FEAR
//=============================================================================
//=============================================================================
LINK_ENTITY_TO_CLASS( ai_goal_fear, CAI_FearGoal );

BEGIN_DATADESC( CAI_FearGoal )
	//DEFINE_KEYFIELD( m_iSomething, FIELD_INTEGER, "something" ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),

	// Outputs
	//DEFINE_OUTPUT( m_OnSeeFearEntity, "OnSeeFearEntity" ),
	DEFINE_OUTPUT( m_OnArriveAtFearNode, "OnArrivedAtNode" ),
END_DATADESC()

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearGoal::EnableGoal( CAI_BaseNPC *pAI )
{
	CAI_FearBehavior *pBehavior;

	if ( !pAI->GetBehavior( &pBehavior ) )
	{
		return;
	}

	pBehavior->SetParameters(this, m_target);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_FearGoal::DisableGoal( CAI_BaseNPC *pAI )
{
	CAI_FearBehavior *pBehavior;

	if ( !pAI->GetBehavior( &pBehavior ) )
	{
		return;
	}

	pBehavior->SetParameters(NULL, NULL_STRING);
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CAI_FearGoal::InputActivate( inputdata_t &inputdata )
{
	BaseClass::InputActivate( inputdata );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CAI_FearGoal::InputDeactivate( inputdata_t &inputdata )
{
	BaseClass::InputDeactivate( inputdata );
}
#endif

AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FearBehavior )

	DECLARE_TASK( TASK_FEAR_GET_PATH_TO_SAFETY_HINT )
	DECLARE_TASK( TASK_FEAR_WAIT_FOR_SAFETY )
	DECLARE_TASK( TASK_FEAR_IN_SAFE_PLACE )

	DECLARE_CONDITION( COND_FEAR_ENEMY_CLOSE )
	DECLARE_CONDITION( COND_FEAR_ENEMY_TOO_CLOSE )
	DECLARE_CONDITION( COND_FEAR_SEPARATED_FROM_PLAYER )

	//===============================================
	//===============================================
	DEFINE_SCHEDULE
	(
		SCHED_FEAR_MOVE_TO_SAFE_PLACE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_RUN_FROM_ENEMY"
		"		TASK_FEAR_GET_PATH_TO_SAFETY_HINT	0"
		"		TASK_RUN_PATH						0"
		"		TASK_WAIT_FOR_MOVEMENT				0"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"		TASK_FEAR_IN_SAFE_PLACE				0"
		"		TASK_SET_SCHEDULE					SCHEDULE:SCHED_FEAR_STAY_IN_SAFE_PLACE"
		""
		"	Interrupts"
		""
		"		COND_HEAR_DANGER"
		"		COND_NEW_ENEMY"
		"		COND_FEAR_ENEMY_TOO_CLOSE"
	);

	DEFINE_SCHEDULE
		(
		SCHED_FEAR_MOVE_TO_SAFE_PLACE_RETRY,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_RUN_FROM_ENEMY"
		"		TASK_FEAR_GET_PATH_TO_SAFETY_HINT	0"
		"		TASK_RUN_PATH						0"
		"		TASK_WAIT_FOR_MOVEMENT				0"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"		TASK_FEAR_IN_SAFE_PLACE				0"
		"		TASK_SET_SCHEDULE					SCHEDULE:SCHED_FEAR_STAY_IN_SAFE_PLACE"
		""
		"	Interrupts"
		""
		"		COND_HEAR_DANGER"
		"		COND_NEW_ENEMY"
		);

	//===============================================
	//===============================================
#ifdef MAPBASE
	DEFINE_SCHEDULE
		(
		SCHED_FEAR_STAY_IN_SAFE_PLACE,

		"	Tasks"
		"		TASK_FEAR_WAIT_FOR_SAFETY			0"
		""
		"	Interrupts"
		""
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
		"		COND_FEAR_ENEMY_CLOSE"
		"		COND_FEAR_ENEMY_TOO_CLOSE"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_FEAR_SEPARATED_FROM_PLAYER"
		"		COND_ENEMY_DEAD" // Allows the fearful to follow the player when enemy dies
		);
#else
	DEFINE_SCHEDULE
		(
		SCHED_FEAR_STAY_IN_SAFE_PLACE,

		"	Tasks"
		"		TASK_FEAR_WAIT_FOR_SAFETY			0"
		""
		"	Interrupts"
		""
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
		"		COND_FEAR_ENEMY_CLOSE"
		"		COND_FEAR_ENEMY_TOO_CLOSE"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_FEAR_SEPARATED_FROM_PLAYER"
		);
#endif


AI_END_CUSTOM_SCHEDULE_PROVIDER()
