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

#include "cbase.h"
#include "entitylist.h"
#include "ai_navigator.h"
#include "ai_behavior_operator.h"


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

//=============================================================================
//=============================================================================
// >OPERATOR BEHAVIOR
//=============================================================================
//=============================================================================
BEGIN_DATADESC( CAI_OperatorBehavior )
	DEFINE_FIELD( m_hGoalEntity, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hPositionEnt, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hContextTarget, FIELD_EHANDLE ),
	DEFINE_EMBEDDED( m_WatchSeeEntity ),
END_DATADESC();

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CAI_OperatorBehavior::CAI_OperatorBehavior()
{
	m_hPositionEnt.Set(NULL);
	m_hGoalEntity.Set(NULL);
	m_hContextTarget.Set(NULL);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#define POSITION_ENT_ALWAYS_SEE_DIST	Square(120)
bool CAI_OperatorBehavior::CanSeePositionEntity()
{
	CAI_BaseNPC *pOuter = GetOuter();

	Assert( m_hPositionEnt.Get() != NULL );

	// early out here.
	if( !pOuter->QuerySeeEntity(m_hPositionEnt) )
	{
		m_WatchSeeEntity.Stop();
		return false;
	}

	bool bSpotted = (pOuter->EyePosition().DistToSqr(m_hPositionEnt->GetAbsOrigin()) <= POSITION_ENT_ALWAYS_SEE_DIST);
	if ( !bSpotted )
	{
		bSpotted = ( pOuter->FInViewCone(m_hPositionEnt) && pOuter->FVisible(m_hPositionEnt) );
	}

	if (bSpotted )
	{
		// If we haven't seen it up until now, start a timer. If we have seen it, wait for the
		// timer to finish. This prevents edge cases where turning on the flashlight makes
		// NPC spot the position entity a frame before she spots an enemy.
		if ( !m_WatchSeeEntity.IsRunning() )
		{
			m_WatchSeeEntity.Start( 0.3,0.31 );
			return false;
		}

		if ( !m_WatchSeeEntity.Expired() )
			return false;

		return true;
	}

	m_WatchSeeEntity.Stop();
	return false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CAI_OperatorBehavior::IsAtPositionEntity()
{
	Vector myPos = GetAbsOrigin();
	Vector objectPos = m_hPositionEnt->GetAbsOrigin();

	Vector vecDir = objectPos - myPos;

	return (vecDir.Length2D() <= 12.0f);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_OperatorBehavior::GatherConditionsNotActive()
{
	if( m_hPositionEnt )
	{
		// If we're not currently the active behavior, we have a position ent, and the 
		// NPC can see it, coax the AI out of IDLE/ALERT schedules with this condition.
		if( CanSeePositionEntity() )
		{
			SetCondition( COND_IDLE_INTERRUPT );
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_OperatorBehavior::GatherConditions( void )
{
	if( GetGoalEntity() )
	{
		if( GetGoalEntity()->GetState() == OPERATOR_STATE_FINISHED )
		{
			if( IsCurSchedule(SCHED_OPERATOR_OPERATE) )
			{
				// Break us out of the operator schedule if the operation completes.
				SetCondition(COND_PROVOKED);
			}

			m_hGoalEntity.Set(NULL);
			m_hPositionEnt.Set(NULL);
		}
		else
		{
			if( CanSeePositionEntity() )
			{
				ClearCondition( COND_OPERATOR_LOST_SIGHT_OF_POSITION );
			}
			else
			{
				SetCondition( COND_OPERATOR_LOST_SIGHT_OF_POSITION );
			}
		}
	}

	BaseClass::GatherConditions();

	// Ignore player pushing.
	ClearCondition( COND_PLAYER_PUSHING );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CAI_OperatorBehavior::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_OPERATOR_OPERATE:
		{
			// Fire the appropriate output!
			switch( GetGoalEntity()->GetState() )
			{
			case OPERATOR_STATE_NOT_READY:
				GetGoalEntity()->m_OnMakeReady.FireOutput(NULL, NULL, 0);
				break;

			case OPERATOR_STATE_READY:
				GetGoalEntity()->m_OnBeginOperating.FireOutput(NULL, NULL, 0);
				break;

			default:
				//!!!HACKHACK
				Assert(0);
				break;
			}
		}
		TaskComplete();
		break;

	case TASK_OPERATOR_START_PATH:
		{
			ChainStartTask(TASK_WALK_PATH);
		}
		break;

	case TASK_OPERATOR_GET_PATH_TO_POSITION:
		{
			CBaseEntity *pGoal = m_hPositionEnt;

			if( !pGoal )
			{
				TaskFail("ai_goal_operator has no location entity\n");
				break;
			}

			AI_NavGoal_t goal( pGoal->GetAbsOrigin() );
			goal.pTarget = pGoal;

			if ( GetNavigator()->SetGoal( goal ) == false )
			{
				TaskFail( "Can't build path\n" );
				/*
				// Try and get as close as possible otherwise
				AI_NavGoal_t nearGoal( GOALTYPE_LOCATION_NEAREST_NODE, m_hTargetObject->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_hTargetObject->GetAbsAngles() );
					TaskComplete();
					return;
				}
				*/
			}

			GetNavigator()->SetArrivalDirection( pGoal->GetAbsAngles() );
		}
		break;

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


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CAI_OperatorBehavior::RunTask( const Task_t *pTask )
{
/*
	switch( pTask->iTask )
	{
	default:
		BaseClass::RunTask( pTask );
		break;
	}
*/
	BaseClass::RunTask( pTask );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CAI_OperatorGoal *CAI_OperatorBehavior::GetGoalEntity()
{
	CAI_OperatorGoal *pGoal = dynamic_cast<CAI_OperatorGoal*>(m_hGoalEntity.Get());

	// NULL is OK.
	return pGoal;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CAI_OperatorBehavior::IsGoalReady()
{
	if( GetGoalEntity()->GetState() == OPERATOR_STATE_READY )
	{
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_OperatorBehavior::SetParameters( CAI_OperatorGoal *pGoal, CBaseEntity *pPositionEnt, CBaseEntity *pContextTarget )
{
	m_hGoalEntity.Set( pGoal );
	m_hPositionEnt.Set( pPositionEnt );
	m_hContextTarget.Set( pContextTarget );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_OperatorBehavior::CanSelectSchedule()
{
	if ( m_hGoalEntity.Get() == NULL )
		return false;

	if ( m_hPositionEnt.Get() == NULL ) 
		return false;

	if( GetGoalEntity()->GetState() == OPERATOR_STATE_FINISHED )
	{
		m_hGoalEntity.Set(NULL);
		m_hPositionEnt.Set(NULL);
		return false;
	}

	if ( !GetOuter()->IsInterruptable() )
		return false;

	if ( GetOuter()->m_NPCState == NPC_STATE_COMBAT || GetOuter()->m_NPCState == NPC_STATE_SCRIPT )
		return false;

	// Don't grab NPCs who have been in combat recently
	if ( GetOuter()->GetLastEnemyTime() && (gpGlobals->curtime - GetOuter()->GetLastEnemyTime()) < 3.0 )
		return false;

	if( !CanSeePositionEntity() )
		return false;

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
//-----------------------------------------------------------------------------
int CAI_OperatorBehavior::SelectSchedule()
{
	if( !IsAtPositionEntity() )
	{
		GetGoalEntity()->m_OnBeginApproach.FireOutput( GetOuter(), GetOuter(), 0 );
		return SCHED_OPERATOR_APPROACH_POSITION;
	}

	if( GetGoalEntity() && GetGoalEntity()->GetState() != OPERATOR_STATE_FINISHED)
	{
		if( GetOuter()->GetActiveWeapon() && !GetOuter()->IsWeaponHolstered() )
		{
			GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED );
			return SCHED_OPERATOR_WAIT_FOR_HOLSTER;
		}

		return SCHED_OPERATOR_OPERATE;
	}

	return BaseClass::SelectSchedule();
}


//=============================================================================
//=============================================================================
// >AI_GOAL_OPERATOR
//=============================================================================
//=============================================================================
LINK_ENTITY_TO_CLASS( ai_goal_operator, CAI_OperatorGoal );

BEGIN_DATADESC( CAI_OperatorGoal )
	DEFINE_KEYFIELD( m_iState, FIELD_INTEGER, "state" ),
	DEFINE_KEYFIELD( m_iMoveTo, FIELD_INTEGER, "moveto" ),
	DEFINE_KEYFIELD( m_iszContextTarget, FIELD_STRING, "contexttarget" ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "SetStateReady", InputSetStateReady ),
	DEFINE_INPUTFUNC( FIELD_VOID, "SetStateFinished", InputSetStateFinished ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),

	// Outputs
	DEFINE_OUTPUT( m_OnBeginApproach, "OnBeginApproach" ),
	DEFINE_OUTPUT( m_OnMakeReady, "OnMakeReady" ),
	DEFINE_OUTPUT( m_OnBeginOperating, "OnBeginOperating" ),
	DEFINE_OUTPUT( m_OnFinished, "OnFinished" ),
END_DATADESC()

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_OperatorGoal::EnableGoal( CAI_BaseNPC *pAI )
{
	CAI_OperatorBehavior *pBehavior;

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

	CBaseEntity *pPosition = gEntList.FindEntityByName(NULL, m_target);

	if( !pPosition )
	{
		DevMsg("ai_goal_operator called %s with invalid position ent!\n", GetDebugName() );
		return;
	}

	
	CBaseEntity *pContextTarget = NULL;
	
	if( m_iszContextTarget != NULL_STRING )
	{
		pContextTarget = gEntList.FindEntityByName( NULL, m_iszContextTarget );
	}

	pBehavior->SetParameters(this, pPosition, pContextTarget);
}

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

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

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_OperatorGoal::InputSetStateReady( inputdata_t &inputdata )
{
	m_iState = OPERATOR_STATE_READY;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CAI_OperatorGoal::InputSetStateFinished( inputdata_t &inputdata )
{
	m_iState = OPERATOR_STATE_FINISHED;
	m_OnFinished.FireOutput( NULL, NULL, 0 );
}

//=============================================================================
//=============================================================================
// >SCHEDULES
//=============================================================================
//=============================================================================
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_OperatorBehavior )

DECLARE_TASK( TASK_OPERATOR_GET_PATH_TO_POSITION )
DECLARE_TASK( TASK_OPERATOR_START_PATH )
DECLARE_TASK( TASK_OPERATOR_OPERATE )

DECLARE_CONDITION( COND_OPERATOR_LOST_SIGHT_OF_POSITION )

//=========================================================
//=========================================================
DEFINE_SCHEDULE 
(
 SCHED_OPERATOR_APPROACH_POSITION,
 "	Tasks"
 "		TASK_OPERATOR_GET_PATH_TO_POSITION	0"
 "		TASK_OPERATOR_START_PATH			0"
 "		TASK_WAIT_FOR_MOVEMENT				0"
 "		TASK_STOP_MOVING					0"

 "	"
 "	Interrupts"
 "		COND_NEW_ENEMY"
 "		COND_HEAR_DANGER"
 "		COND_OPERATOR_LOST_SIGHT_OF_POSITION"
 )

 //=========================================================
 //=========================================================
 DEFINE_SCHEDULE 
 (
 SCHED_OPERATOR_OPERATE,
 "	Tasks"
 "		TASK_WAIT					0.2" // Allow pending entity I/O to settle
 "		TASK_OPERATOR_OPERATE		0"
 "		TASK_WAIT_INDEFINITE		0"
 "	"
 "	Interrupts"
 "		COND_PROVOKED"
 )

//=========================================================
//=========================================================
DEFINE_SCHEDULE 
(
 SCHED_OPERATOR_WAIT_FOR_HOLSTER,
 "	Tasks"
 "		TASK_WAIT					1.0" 
 "	"
 "	Interrupts"
 "	"
 )

 AI_END_CUSTOM_SCHEDULE_PROVIDER()
