//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#undef strncpy // we use std::string below that needs a good strncpy define
#undef sprintf // "
#include "cbase.h"

#include "ai_behavior_lead.h"

#include "ai_goalentity.h"
#include "ai_navigator.h"
#include "ai_speech.h"
#include "ai_senses.h"
#include "ai_playerally.h"
#include "ai_route.h"
#include "ai_pathfinder.h"
#include "sceneentity.h"

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

// Minimum time between leader nags
#define LEAD_NAG_TIME		3.0

#define LEAD_MIN_RETRIEVEDIST_OFFSET		24

//-----------------------------------------------------------------------------
// class CAI_LeadBehavior
//
// Purpose:
//
//-----------------------------------------------------------------------------

BEGIN_SIMPLE_DATADESC( AI_LeadArgs_t )
	// Only the flags needs saving
	DEFINE_FIELD(		flags,			FIELD_INTEGER ),

	//DEFINE_FIELD(		pszGoal,		FIELD_STRING ),
	//DEFINE_FIELD(		pszWaitPoint,	FIELD_STRING ),
	//DEFINE_FIELD(		flWaitDistance,	FIELD_FLOAT ),
	//DEFINE_FIELD(		flLeadDistance,	FIELD_FLOAT ),
	//DEFINE_FIELD(		flRetrieveDistance,	FIELD_FLOAT ),
	//DEFINE_FIELD(		flSuccessDistance,	FIELD_FLOAT ),
	//DEFINE_FIELD(		bRun,			FIELD_BOOLEAN ),
	//DEFINE_FIELD(		bDontSpeakStart,			FIELD_BOOLEAN ),
	//DEFINE_FIELD(		bGagLeader,			FIELD_BOOLEAN ),

	DEFINE_FIELD(		iRetrievePlayer,			FIELD_INTEGER ),
	DEFINE_FIELD(		iRetrieveWaitForSpeak,		FIELD_INTEGER ),
	DEFINE_FIELD(		iComingBackWaitForSpeak,	FIELD_INTEGER ),
	DEFINE_FIELD(		bStopScenesWhenPlayerLost,	FIELD_BOOLEAN ),
	DEFINE_FIELD(		bLeadDuringCombat,			FIELD_BOOLEAN ),

END_DATADESC();


BEGIN_DATADESC( CAI_LeadBehavior )
	DEFINE_EMBEDDED(	m_args ),
	//					m_pSink		(reconnected on load)
	DEFINE_FIELD(		m_hSinkImplementor, FIELD_EHANDLE ),
	DEFINE_FIELD(		m_goal,			FIELD_POSITION_VECTOR ),
	DEFINE_FIELD(		m_goalyaw, 		FIELD_FLOAT ),
	DEFINE_FIELD(		m_waitpoint, 	FIELD_POSITION_VECTOR ),
	DEFINE_FIELD(		m_waitdistance, FIELD_FLOAT ),
	DEFINE_FIELD(		m_leaddistance, FIELD_FLOAT ),
	DEFINE_FIELD(		m_retrievedistance, FIELD_FLOAT ),
	DEFINE_FIELD(		m_successdistance, FIELD_FLOAT ),
	DEFINE_FIELD(		m_weaponname,	FIELD_STRING ),
	DEFINE_FIELD(		m_run,			FIELD_BOOLEAN ),
	DEFINE_FIELD(		m_gagleader, FIELD_BOOLEAN ),
	DEFINE_FIELD(		m_hasspokenstart, FIELD_BOOLEAN ),
	DEFINE_FIELD(		m_hasspokenarrival, FIELD_BOOLEAN ),
	DEFINE_FIELD(		m_hasPausedScenes, FIELD_BOOLEAN ),
	DEFINE_FIELD(		m_flSpeakNextNagTime, FIELD_TIME ),
	DEFINE_FIELD(		m_flWeaponSafetyTimeOut, FIELD_TIME ),
	DEFINE_FIELD(		m_flNextLeadIdle, FIELD_TIME ),
	DEFINE_FIELD(		m_bInitialAheadTest, FIELD_BOOLEAN ),
	DEFINE_EMBEDDED(	m_MoveMonitor ),
	DEFINE_EMBEDDED(	m_LostTimer ),
	DEFINE_EMBEDDED(	m_LostLOSTimer ),
END_DATADESC();

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


void CAI_LeadBehavior::OnRestore()
{
	CBaseEntity *pSinkImplementor = m_hSinkImplementor;
	if ( pSinkImplementor )
	{
		m_pSink = dynamic_cast<CAI_LeadBehaviorHandler *>(pSinkImplementor);
		if ( !m_pSink )
		{
			DevMsg( "Failed to reconnect to CAI_LeadBehaviorHandler\n" );
			m_hSinkImplementor = NULL;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Draw any text overlays
// Input  : Previous text offset from the top
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CAI_LeadBehavior::DrawDebugTextOverlays( int text_offset )
{
	char			tempstr[ 512 ];
	int				offset;

	offset = BaseClass::DrawDebugTextOverlays( text_offset );
	if ( GetOuter()->m_debugOverlays & OVERLAY_TEXT_BIT )
	{	
		if ( HasGoal() )
		{
			Q_snprintf( tempstr, sizeof(tempstr), "Goal: %s %s", m_args.pszGoal, VecToString( m_goal ) );
			GetOuter()->EntityText( offset, tempstr, 0 );
			offset++;
		}
		else 
		{
			Q_snprintf( tempstr, sizeof(tempstr), "Goal: None" );
			GetOuter()->EntityText( offset, tempstr, 0 );
			offset++;
		}
	}

	return offset;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CAI_LeadBehavior::IsNavigationUrgent( void )
{
#if defined( HL2_DLL )
	if( HasGoal() && !hl2_episodic.GetBool() )
	{
		return (GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL);
	}
#endif
	return BaseClass::IsNavigationUrgent();
}

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

void CAI_LeadBehavior::LeadPlayer( const AI_LeadArgs_t &leadArgs, CAI_LeadBehaviorHandler *pSink )
{
#ifndef CSTRIKE_DLL
	CAI_PlayerAlly *pOuter = dynamic_cast<CAI_PlayerAlly*>(GetOuter());
	if ( pOuter && AI_IsSinglePlayer() )
	{
		pOuter->SetSpeechTarget( UTIL_GetLocalPlayer() );
	}
#endif

	if( SetGoal( leadArgs ) )
	{
		SetCondition( COND_PROVOKED );
		Connect( pSink );
		NotifyChangeBehaviorStatus();
	}
	else
	{
		DevMsg( "*** Warning! LeadPlayer() has a NULL Goal Ent\n" );
	}
}

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

void CAI_LeadBehavior::StopLeading( void )
{
	ClearGoal();
	m_pSink = NULL;
	NotifyChangeBehaviorStatus();
}

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

bool CAI_LeadBehavior::CanSelectSchedule()
{
 	if ( !AI_GetSinglePlayer() || AI_GetSinglePlayer()->IsDead() )
		return false;

	bool fAttacked = ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) );
	bool fNonCombat = ( GetNpcState() == NPC_STATE_IDLE || GetNpcState() == NPC_STATE_ALERT );
	
	return ( !fAttacked && (fNonCombat || m_args.bLeadDuringCombat) && HasGoal() );
}

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

void CAI_LeadBehavior::BeginScheduleSelection()
{
	SetTarget( AI_GetSinglePlayer() );
	CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
	if ( pExpresser )
		pExpresser->ClearSpokeConcept( TLK_LEAD_ARRIVAL );
}

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

bool CAI_LeadBehavior::SetGoal( const AI_LeadArgs_t &args )
{
	CBaseEntity *pGoalEnt;
	pGoalEnt = gEntList.FindEntityByName( NULL, args.pszGoal );
	
	if ( !pGoalEnt )
		return false;

	m_args 		= args;	// @Q (toml 08-13-02): need to copy string?
	m_goal 		= pGoalEnt->GetLocalOrigin();
	m_goalyaw 	= (args.flags & AILF_USE_GOAL_FACING) ? pGoalEnt->GetLocalAngles().y : -1;
	m_waitpoint = vec3_origin;
	m_waitdistance = args.flWaitDistance;
	m_leaddistance = args.flLeadDistance ? args.flLeadDistance : 64;
	m_retrievedistance = args.flRetrieveDistance ? args.flRetrieveDistance : (m_leaddistance + LEAD_MIN_RETRIEVEDIST_OFFSET);
	m_successdistance = args.flSuccessDistance ? args.flSuccessDistance : 0;
	m_run = args.bRun;
	m_gagleader = args.bGagLeader;
	m_hasspokenstart = args.bDontSpeakStart;
	m_hasspokenarrival = false;
	m_hasPausedScenes = false;
	m_flSpeakNextNagTime = 0;
	m_flWeaponSafetyTimeOut = 0;
	m_flNextLeadIdle = gpGlobals->curtime + 10;
	m_bInitialAheadTest = true;

	if ( args.pszWaitPoint && args.pszWaitPoint[0] )
	{
		CBaseEntity *pWaitPoint = gEntList.FindEntityByName( NULL, args.pszWaitPoint );
		if ( pWaitPoint )
		{
			m_waitpoint = pWaitPoint->GetLocalOrigin();
		}
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CAI_LeadBehavior::GetClosestPointOnRoute( const Vector &targetPos, Vector *pVecClosestPoint )
{
	AI_Waypoint_t *waypoint = GetOuter()->GetNavigator()->GetPath()->GetCurWaypoint();
	AI_Waypoint_t *builtwaypoints = NULL;
	if ( !waypoint )
	{
		// We arrive here twice when lead behaviour starts:
		//	- When the lead behaviour is first enabled. We have no schedule. We want to know if the player is ahead of us.
		//	- A frame later when we've chosen to lead the player, but we still haven't built our route. We know that the
		//	  the player isn't lagging, so it's safe to go ahead and simply say he's ahead of us. This avoids building 
		//	  the temp route twice.
		if ( IsCurSchedule( SCHED_LEAD_PLAYER, false ) )
			return true;

		// Build a temp route to the gold and use that
		builtwaypoints = GetOuter()->GetPathfinder()->BuildRoute( GetOuter()->GetAbsOrigin(), m_goal, NULL, GetOuter()->GetDefaultNavGoalTolerance(), GetOuter()->GetNavType(), true );
		if ( !builtwaypoints )
			return false;

		GetOuter()->GetPathfinder()->UnlockRouteNodes( builtwaypoints );
		waypoint = builtwaypoints;
	}

	// Find the nearest node to the target (going forward)
	float		flNearestDist2D	= 999999999;
	float		flNearestDist	= 999999999;
	float		flPathDist, flPathDist2D;

	Vector vecNearestPoint(0, 0, 0);
	Vector vecPrevPos = GetOuter()->GetAbsOrigin();
	for ( ; (waypoint != NULL) ; waypoint = waypoint->GetNext() )
	{
		// Find the closest point on the line segment on the path
		Vector vecClosest;
		CalcClosestPointOnLineSegment( targetPos, vecPrevPos, waypoint->GetPos(), vecClosest );
		/*
		if ( builtwaypoints )
		{
			NDebugOverlay::Line( vecPrevPos, waypoint->GetPos(), 0,0,255,true, 10.0 );
		}
		*/
		vecPrevPos = waypoint->GetPos();

		// Find the distance between this test point and our goal point
		flPathDist2D = vecClosest.AsVector2D().DistToSqr( targetPos.AsVector2D() );
		if ( flPathDist2D > flNearestDist2D )
			continue;

		flPathDist = vecClosest.z - targetPos.z;
		flPathDist *= flPathDist;
		flPathDist += flPathDist2D;
		if (( flPathDist2D == flNearestDist2D ) && ( flPathDist >= flNearestDist ))
			continue;

		flNearestDist2D	= flPathDist2D;
		flNearestDist	= flPathDist;
		vecNearestPoint	= vecClosest;
	}

	if ( builtwaypoints )
	{
		//NDebugOverlay::Line( vecNearestPoint, targetPos, 0,255,0,true, 10.0 );
		DeleteAll( builtwaypoints );
	}

	*pVecClosestPoint = vecNearestPoint;
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Return true if the player is further ahead on the lead route than I am
//-----------------------------------------------------------------------------
bool CAI_LeadBehavior::PlayerIsAheadOfMe( bool bForce )
{
	// Find the nearest point on our route to the player, and see if that's further 
	// ahead of us than our nearest point. 

	// If we're not leading, our route doesn't lead to the goal, so we can't use it.
	// If we just started leading, go ahead and test, and we'll build a temp route.
	if ( !m_bInitialAheadTest && !IsCurSchedule( SCHED_LEAD_PLAYER, false ) && !bForce )
		return false;

	m_bInitialAheadTest = false;

	Vector vecClosestPoint;
	if ( GetClosestPointOnRoute( AI_GetSinglePlayer()->GetAbsOrigin(), &vecClosestPoint ) )
	{
		// If the closest point is not right next to me, then 
		// the player is somewhere ahead of me on the route.
		if ( (vecClosestPoint - GetOuter()->GetAbsOrigin()).LengthSqr() > (32*32) )
			return true;
	}

	return false;
}

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

	if ( HasGoal() )
	{
		// Fix for bad transition case (to investigate)
		if ( ( WorldSpaceCenter() - m_goal ).LengthSqr() > (64*64) && IsCurSchedule( SCHED_LEAD_AWAIT_SUCCESS, false)  )
		{
			GetOuter()->ClearSchedule( "Lead behavior - bad transition?" );
		}

		// We have to collect data about the person we're leading around.
		CBaseEntity *pFollower = AI_GetSinglePlayer();

		if( pFollower )
		{
			ClearCondition( COND_LEAD_FOLLOWER_VERY_CLOSE );
			ClearCondition( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME );

			// Check distance to the follower
			float flFollowerDist = ( WorldSpaceCenter() - pFollower->WorldSpaceCenter() ).Length();
			bool bLagging = flFollowerDist > (m_leaddistance*4);
			if ( bLagging )
			{
				if ( PlayerIsAheadOfMe() )
				{
					bLagging = false;
				}
			}

			// Player heading towards me?
			// Only factor this in if you're not too far from them
			if ( flFollowerDist < (m_leaddistance*4) )
			{
				Vector vecVelocity = pFollower->GetSmoothedVelocity();
				if ( VectorNormalize(vecVelocity) > 50 )
				{
					Vector vecToPlayer = (GetAbsOrigin() - pFollower->GetAbsOrigin());
					VectorNormalize( vecToPlayer );
					if ( DotProduct( vecVelocity, vecToPlayer ) > 0.5 )
					{
						SetCondition( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME );
						bLagging = false;
					}
				}
			}

			// If he's outside our lag range, consider him lagging
			if ( bLagging )
			{
				SetCondition( COND_LEAD_FOLLOWER_LAGGING );
				ClearCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );
			}
			else
			{
				ClearCondition( COND_LEAD_FOLLOWER_LAGGING );
				SetCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );

				// If he's really close, note that
				if ( flFollowerDist < m_leaddistance )
				{
					SetCondition( COND_LEAD_FOLLOWER_VERY_CLOSE );
				}
			}

			// To be considered not lagging, the follower must be visible, and within the lead distance
			if ( GetOuter()->FVisible( pFollower ) && GetOuter()->GetSenses()->ShouldSeeEntity( pFollower ) )
			{
				SetCondition( COND_LEAD_HAVE_FOLLOWER_LOS );
				m_LostLOSTimer.Stop();
			}
			else
			{
				ClearCondition( COND_LEAD_HAVE_FOLLOWER_LOS );

				// We don't have a LOS. But if we did have LOS, don't clear it until the timer is up.
				if ( m_LostLOSTimer.IsRunning() )
				{
					if ( m_LostLOSTimer.Expired() )
					{
						SetCondition( COND_LEAD_FOLLOWER_LAGGING );
						ClearCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );
					}
				}
				else
				{
					m_LostLOSTimer.Start();
				}
			}

			// Now we want to see if the follower is lost. Being lost means being (far away || out of LOS ) 
			// && some time has passed. Also, lagging players are considered lost if the NPC's never delivered
			// the start speech, because it means the NPC should run to the player to start the lead.
			if( HasCondition( COND_LEAD_FOLLOWER_LAGGING ) )
			{
				if ( !m_hasspokenstart )
				{
					SetCondition( COND_LEAD_FOLLOWER_LOST );
				}
				else
				{
					if ( m_args.bStopScenesWhenPlayerLost )
					{
						// Try and stop me speaking my monolog, if I am
						if ( !m_hasPausedScenes && IsRunningScriptedScene( GetOuter() ) )
						{
							//Msg("Stopping scenes.\n");
							PauseActorsScriptedScenes( GetOuter(), false );
							m_hasPausedScenes = true;
						}
					}

					if( m_LostTimer.IsRunning() )
					{
						if( m_LostTimer.Expired() )
						{
							SetCondition( COND_LEAD_FOLLOWER_LOST );
						}
					}
					else
					{
						m_LostTimer.Start();
					}
				}
			}
			else
			{
				// If I was speaking a monolog, resume it
				if ( m_args.bStopScenesWhenPlayerLost && m_hasPausedScenes )
				{
					if ( IsRunningScriptedScene( GetOuter() ) )
					{
						//Msg("Resuming scenes.\n");
						ResumeActorsScriptedScenes( GetOuter(), false );
					}

					m_hasPausedScenes = false;
				}

				m_LostTimer.Stop();
				ClearCondition( COND_LEAD_FOLLOWER_LOST );
			}

			// Evaluate for success
			// Success right now means being stationary, close to the goal, and having the player close by
			if ( !( m_args.flags & AILF_NO_DEF_SUCCESS ) )
			{
				ClearCondition( COND_LEAD_SUCCESS );

				// Check Z first, and only check 2d if we're within that
				bool bWithinZ = fabs(GetLocalOrigin().z - m_goal.z) < 64;
				if ( bWithinZ && (GetLocalOrigin() - m_goal).Length2D() <= 64 )
				{
					if ( HasCondition( COND_LEAD_FOLLOWER_VERY_CLOSE ) )
					{
						SetCondition( COND_LEAD_SUCCESS );
					}
					else if ( m_successdistance )
					{
						float flDistSqr = (pFollower->GetAbsOrigin() - GetLocalOrigin()).Length2DSqr();
						if ( flDistSqr < (m_successdistance*m_successdistance) )
						{
							SetCondition( COND_LEAD_SUCCESS );
						}
					}
				}
			}
			if ( m_MoveMonitor.IsMarkSet() && m_MoveMonitor.TargetMoved( pFollower ) )
				SetCondition( COND_LEAD_FOLLOWER_MOVED_FROM_MARK );
			else
				ClearCondition( COND_LEAD_FOLLOWER_MOVED_FROM_MARK );
		}
	}

	if( m_args.bLeadDuringCombat )
	{
		ClearCondition( COND_LIGHT_DAMAGE );
		ClearCondition( COND_HEAVY_DAMAGE );
	}
}

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

int CAI_LeadBehavior::SelectSchedule()
{
	if ( HasGoal() )
	{
		if( HasCondition(COND_LEAD_SUCCESS) )
		{
			return SCHED_LEAD_SUCCEED;
		}

		// Player's here, but does he have the weapon we want him to have?
		if ( m_weaponname != NULL_STRING )
		{
			CBasePlayer *pFollower = AI_GetSinglePlayer();
			if ( pFollower && !pFollower->Weapon_OwnsThisType( STRING(m_weaponname) ) )
			{
				// If the safety timeout has run out, just give the player the weapon
				if ( !m_flWeaponSafetyTimeOut || (m_flWeaponSafetyTimeOut > gpGlobals->curtime) )
					return SCHED_LEAD_PLAYERNEEDSWEAPON;

				string_t iszItem = AllocPooledString( "weapon_bugbait" );
				pFollower->GiveNamedItem( STRING(iszItem) );
			}
		}

		// If we have a waitpoint, we want to wait at it for the player.
		if( HasWaitPoint() && !PlayerIsAheadOfMe( true ) )
		{
			bool bKeepWaiting = true;

			// If we have no wait distance, trigger as soon as the player comes in view
			if ( !m_waitdistance )
			{
				if ( HasCondition( COND_SEE_PLAYER ) )
				{
					// We've spotted the player, so stop waiting
					bKeepWaiting = false;
				}
			}
			else
			{
				// We have to collect data about the person we're leading around.
				CBaseEntity *pFollower = AI_GetSinglePlayer();
				if( pFollower )
				{
					float flFollowerDist = ( WorldSpaceCenter() - pFollower->WorldSpaceCenter() ).Length();
					if ( flFollowerDist < m_waitdistance )
					{
						bKeepWaiting = false;
					}
				}
			}

			// Player still not here?
			if ( bKeepWaiting )
				return SCHED_LEAD_WAITFORPLAYER;

			// We're finished waiting
			m_waitpoint = vec3_origin;
			Speak( TLK_LEAD_WAITOVER );

			// Don't speak the start line, because we've said 
			m_hasspokenstart = true;
			return SCHED_WAIT_FOR_SPEAK_FINISH;
		}

		// If we haven't spoken our start speech, do that first
		if ( !m_hasspokenstart )
		{
			if ( HasCondition(COND_LEAD_HAVE_FOLLOWER_LOS) && HasCondition(COND_LEAD_FOLLOWER_VERY_CLOSE) )
				return SCHED_LEAD_SPEAK_START;

			// We haven't spoken to him, and we still need to. Go get him.
			return SCHED_LEAD_RETRIEVE;
		}

		if( HasCondition( COND_LEAD_FOLLOWER_LOST ) )
		{
			if( m_args.iRetrievePlayer )
			{
				// If not, we want to go get the player.
				DevMsg( GetOuter(), "Follower lost. Spoke COMING_BACK.\n");

				Speak( TLK_LEAD_COMINGBACK );
				m_MoveMonitor.ClearMark();

				// If we spoke something, wait for it to finish
				if ( m_args.iComingBackWaitForSpeak && IsSpeaking() )
					return SCHED_LEAD_SPEAK_THEN_RETRIEVE_PLAYER;

				return SCHED_LEAD_RETRIEVE;
			}
			else
			{
				// Just stay right here and wait.
				return SCHED_LEAD_WAITFORPLAYERIDLE;
			}
		}

		if( HasCondition( COND_LEAD_FOLLOWER_LAGGING ) )
		{
			DevMsg( GetOuter(), "Follower lagging. Spoke CATCHUP.\n");

			Speak( TLK_LEAD_CATCHUP );
			return SCHED_LEAD_PAUSE;
		}
		else
		{
			// If we're at the goal, wait for the player to get here
			if ( ( WorldSpaceCenter() - m_goal ).LengthSqr() < (64*64) )
				return SCHED_LEAD_AWAIT_SUCCESS;

			// If we were retrieving the player, speak the resume
			if ( IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) || IsCurSchedule( SCHED_LEAD_WAITFORPLAYERIDLE, false ) )
			{
				Speak( TLK_LEAD_RETRIEVE );

				// If we spoke something, wait for it to finish, if the mapmakers wants us to
				if ( m_args.iRetrieveWaitForSpeak && IsSpeaking() )
					return SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER;
			}

			DevMsg( GetOuter(), "Leading Follower.\n");
			return SCHED_LEAD_PLAYER;
		}
	}
	return BaseClass::SelectSchedule();
}

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

int CAI_LeadBehavior::TranslateSchedule( int scheduleType )
{
	bool bInCombat = (m_args.bLeadDuringCombat && GetOuter()->GetState() == NPC_STATE_COMBAT);
	switch( scheduleType )
	{
	case SCHED_LEAD_PAUSE:
		if( bInCombat )
			return SCHED_LEAD_PAUSE_COMBAT;
		break;
	}
	return BaseClass::TranslateSchedule( scheduleType );
}

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

bool CAI_LeadBehavior::IsCurTaskContinuousMove()
{
	const Task_t *pCurTask = GetCurTask();
	if ( pCurTask && pCurTask->iTask == TASK_LEAD_MOVE_TO_RANGE )
		return true;
	return BaseClass::IsCurTaskContinuousMove();
}

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

void CAI_LeadBehavior::StartTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
		case TASK_LEAD_FACE_GOAL:
		{
			if ( m_goalyaw != -1 )
			{
				GetMotor()->SetIdealYaw( m_goalyaw ); 
			}

			TaskComplete();
			break;
		}

		case TASK_LEAD_SUCCEED:
		{
			Speak( TLK_LEAD_SUCCESS );
			NotifyEvent( LBE_SUCCESS );

			break;
		}

		case TASK_LEAD_ARRIVE:
		{
			// Only speak the first time we arrive
			if ( !m_hasspokenarrival )
			{
				Speak( TLK_LEAD_ARRIVAL );
				NotifyEvent( LBE_ARRIVAL );

				m_hasspokenarrival = true;
			}
			else
			{
				TaskComplete();
			}
			
			break;
		}
		
		case TASK_STOP_LEADING:
		{
			ClearGoal();
			TaskComplete();
			break;
		}

		case TASK_GET_PATH_TO_LEAD_GOAL:
		{
			if ( GetNavigator()->SetGoal( m_goal ) )
			{
				TaskComplete();
			}
			else
			{
				TaskFail("NO PATH");
			}
			break;
		}
		
		case TASK_LEAD_GET_PATH_TO_WAITPOINT:
		{
			if ( GetNavigator()->SetGoal( m_waitpoint ) )
			{
				TaskComplete();
			}
			else
			{
				TaskFail("NO PATH");
			}
			break;
		}

		case TASK_LEAD_WALK_PATH:
		{
			// If we're leading, and we're supposed to run, run instead of walking
			if ( m_run && 
				( IsCurSchedule( SCHED_LEAD_WAITFORPLAYER, false ) || IsCurSchedule( SCHED_LEAD_PLAYER, false ) || IsCurSchedule( SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER, false )|| IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) ) )
			{
				ChainStartTask( TASK_RUN_PATH );
			}
			else
			{
				ChainStartTask( TASK_WALK_PATH );
			}
			break;
		}

		case TASK_LEAD_WAVE_TO_PLAYER:
		{
			// Wave to the player if we can see him. Otherwise, just idle.
			if ( HasCondition( COND_SEE_PLAYER ) )
			{
				Speak( TLK_LEAD_ATTRACTPLAYER );
				if ( HaveSequenceForActivity(ACT_SIGNAL1) )
				{
					SetActivity(ACT_SIGNAL1);
				}
			}
			else
			{
				SetActivity(ACT_IDLE);
			}

			TaskComplete();
			break;
		}

		case TASK_LEAD_PLAYER_NEEDS_WEAPON:
		{
			float flAvailableTime = GetOuter()->GetExpresser()->GetSemaphoreAvailableTime( GetOuter() );

			// if someone else is talking, don't speak
			if ( flAvailableTime <= gpGlobals->curtime )
			{
				Speak( TLK_LEAD_MISSINGWEAPON );
			}

			SetActivity(ACT_IDLE);
			TaskComplete();
			break;
		}

		case TASK_LEAD_SPEAK_START:
		{
			m_hasspokenstart = true;

			Speak( TLK_LEAD_START );
			SetActivity(ACT_IDLE);
			TaskComplete();
			break;
		}

		case TASK_LEAD_MOVE_TO_RANGE:
		{
			// If we haven't spoken our start speech, move closer
			if ( !m_hasspokenstart)
			{
				ChainStartTask( TASK_MOVE_TO_GOAL_RANGE, m_leaddistance - 24 );
			}
			else
			{
				ChainStartTask( TASK_MOVE_TO_GOAL_RANGE, m_retrievedistance );
			}
			break;
		}

		case TASK_LEAD_RETRIEVE_WAIT:
		{
			m_MoveMonitor.SetMark( AI_GetSinglePlayer(), 24 );
			ChainStartTask( TASK_WAIT_INDEFINITE );
			break;
		}

		case TASK_STOP_MOVING:
		{
			BaseClass::StartTask( pTask);

			if ( IsCurSchedule( SCHED_LEAD_PAUSE, false ) && pTask->flTaskData == 1 )
			{
				GetNavigator()->SetArrivalDirection( GetTarget() );
			}
			break;
		}

		case TASK_WAIT_FOR_SPEAK_FINISH:
		{
			BaseClass::StartTask( pTask);

			if( GetOuter()->GetState() == NPC_STATE_COMBAT )
			{
				// Don't stand around jabbering in combat. 
				TaskComplete();
			}

			// If we're not supposed to wait for the player, don't wait for speech to finish.
			// Instead, just wait a wee tad, and then start moving. NPC will speak on the go.
			if ( TaskIsRunning() && !m_args.iRetrievePlayer )
			{
				if ( gpGlobals->curtime - GetOuter()->GetTimeTaskStarted() > 0.3 )
				{
					TaskComplete();
				}
			}
			break;
		}

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

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

void CAI_LeadBehavior::RunTask( const Task_t *pTask )		
{ 
	switch ( pTask->iTask )
	{
		case TASK_LEAD_SUCCEED:
		{
			if ( !IsSpeaking() )
			{
				TaskComplete();
				NotifyEvent( LBE_DONE );
			}
			break;
		}
		case TASK_LEAD_ARRIVE:
		{
			if ( !IsSpeaking() )
			{
				TaskComplete();
				NotifyEvent( LBE_ARRIVAL_DONE );
			}
			break;
		}

		case TASK_LEAD_MOVE_TO_RANGE:
		{
			// If we haven't spoken our start speech, move closer
 			if ( !m_hasspokenstart)
			{
				ChainRunTask( TASK_MOVE_TO_GOAL_RANGE, m_leaddistance - 24 );
			}
			else
			{
 				ChainRunTask( TASK_MOVE_TO_GOAL_RANGE, m_retrievedistance );

				if ( !TaskIsComplete() )
				{
					// Transition to a walk when we get near the player
					// Check Z first, and only check 2d if we're within that
					Vector vecGoalPos = GetNavigator()->GetGoalPos();
					float distance = fabs(vecGoalPos.z - GetLocalOrigin().z);
					bool bWithinZ = false;
					if ( distance < m_retrievedistance )
					{
						distance = ( vecGoalPos - GetLocalOrigin() ).Length2D();
						bWithinZ = true;
					}

					if ( distance > m_retrievedistance )
					{
						Activity followActivity = ACT_WALK;
						if ( GetOuter()->GetState() == NPC_STATE_COMBAT || ( (!bWithinZ || distance < (m_retrievedistance*4)) && GetOuter()->GetState() != NPC_STATE_COMBAT ) )
						{
							followActivity = ACT_RUN;
						}

						// Don't confuse move and shoot by resetting the activity every think
						Activity curActivity = GetNavigator()->GetMovementActivity();
						switch( curActivity )
						{
						case ACT_WALK_AIM:	curActivity = ACT_WALK;	break;
						case ACT_RUN_AIM:	curActivity = ACT_RUN;	break;
						}
						
						if ( curActivity != followActivity )
						{
							GetNavigator()->SetMovementActivity(followActivity);
						}
						GetNavigator()->SetArrivalDirection( GetOuter()->GetTarget() );
					}
				}
			}
			break;
		}

		case TASK_LEAD_RETRIEVE_WAIT:
		{
			ChainRunTask( TASK_WAIT_INDEFINITE );
			break;
		}

		case TASK_LEAD_WALK_PATH:
		{
			// If we're leading, and we're supposed to run, run instead of walking
			if ( m_run && 
				( IsCurSchedule( SCHED_LEAD_WAITFORPLAYER, false ) || IsCurSchedule( SCHED_LEAD_PLAYER, false ) || IsCurSchedule( SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER, false )|| IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) ) )
			{
				ChainRunTask( TASK_RUN_PATH );
			}
			else
			{
				ChainRunTask( TASK_WALK_PATH );
			}

			// While we're walking
			if ( TaskIsRunning() && IsCurSchedule( SCHED_LEAD_PLAYER, false ) )
			{
				// If we're not speaking, and we haven't tried for a while, try to speak lead idle
				if ( m_flNextLeadIdle < gpGlobals->curtime && !IsSpeaking() )
				{
					m_flNextLeadIdle = gpGlobals->curtime + RandomFloat( 10,15 );

					if ( !m_args.iRetrievePlayer && HasCondition( COND_LEAD_FOLLOWER_LOST ) && HasCondition(COND_SEE_PLAYER) )
					{
						Speak( TLK_LEAD_COMINGBACK );
					}
					else
					{
						Speak( TLK_LEAD_IDLE );
					}
				}
			}

			break;
		}

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

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

bool CAI_LeadBehavior::Speak( AIConcept_t concept )
{
	CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
	if ( !pExpresser )
		return false;

	// If the leader is gagged, don't speak any lead speech
	if ( m_gagleader )
		return false;

	// If we haven't said the start speech, don't nag
	bool bNag = ( FStrEq(concept,TLK_LEAD_COMINGBACK) || FStrEq(concept, TLK_LEAD_CATCHUP) || FStrEq(concept, TLK_LEAD_RETRIEVE) );
	if ( !m_hasspokenstart && bNag )
		return false;

	if ( hl2_episodic.GetBool() )
	{
		// If we're a player ally, only speak the concept if we're allowed to.
		// This allows the response rules to control it better (i.e. handles respeakdelay)
		// We ignore nag timers for this, because the response rules will control refire rates.
		CAI_PlayerAlly *pAlly = dynamic_cast<CAI_PlayerAlly*>(GetOuter());
		if ( pAlly )
 			return pAlly->SpeakIfAllowed( concept, GetConceptModifiers( concept ) );
	}

	// Don't spam Nags
	if ( bNag )
	{
		if ( m_flSpeakNextNagTime > gpGlobals->curtime )
		{
			DevMsg( GetOuter(), "Leader didn't speak due to Nag timer.\n");
			return false;
		}
	}
	
	if ( pExpresser->Speak( concept, GetConceptModifiers( concept ) ) )
	{
		m_flSpeakNextNagTime = gpGlobals->curtime + LEAD_NAG_TIME;
		return true;
	}

	return false;
}

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

bool CAI_LeadBehavior::IsSpeaking()
{
	CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
	if ( !pExpresser )
		return false;
		
	return pExpresser->IsSpeaking();
}

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

bool CAI_LeadBehavior::Connect( CAI_LeadBehaviorHandler *pSink )
{
	m_pSink = pSink;
	m_hSinkImplementor = dynamic_cast<CBaseEntity *>(pSink);

	if ( m_hSinkImplementor == NULL )
		DevMsg( 2, "Note: CAI_LeadBehaviorHandler connected to a sink that isn't an entity. Manual fixup on load will be necessary\n" );

	return true;
}

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

bool CAI_LeadBehavior::Disconnect( CAI_LeadBehaviorHandler *pSink )
{
	Assert( pSink == m_pSink );
	m_pSink = NULL;
	m_hSinkImplementor = NULL;
	return true;
}

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

AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_LeadBehavior )

	DECLARE_CONDITION( COND_LEAD_FOLLOWER_LOST )
	DECLARE_CONDITION( COND_LEAD_FOLLOWER_LAGGING )
	DECLARE_CONDITION( COND_LEAD_FOLLOWER_NOT_LAGGING )
	DECLARE_CONDITION( COND_LEAD_FOLLOWER_VERY_CLOSE )
	DECLARE_CONDITION( COND_LEAD_SUCCESS )
	DECLARE_CONDITION( COND_LEAD_HAVE_FOLLOWER_LOS )
	DECLARE_CONDITION( COND_LEAD_FOLLOWER_MOVED_FROM_MARK )
	DECLARE_CONDITION( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME )

	//---------------------------------
	//
	// Lead
	//
	DECLARE_TASK( TASK_GET_PATH_TO_LEAD_GOAL )
	DECLARE_TASK( TASK_STOP_LEADING )
	DECLARE_TASK( TASK_LEAD_ARRIVE )
	DECLARE_TASK( TASK_LEAD_SUCCEED )
	DECLARE_TASK( TASK_LEAD_FACE_GOAL )
	DECLARE_TASK( TASK_LEAD_GET_PATH_TO_WAITPOINT )
	DECLARE_TASK( TASK_LEAD_WAVE_TO_PLAYER )
	DECLARE_TASK( TASK_LEAD_PLAYER_NEEDS_WEAPON )
	DECLARE_TASK( TASK_LEAD_MOVE_TO_RANGE )
	DECLARE_TASK( TASK_LEAD_SPEAK_START )
	DECLARE_TASK( TASK_LEAD_RETRIEVE_WAIT )
	DECLARE_TASK( TASK_LEAD_WALK_PATH )

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_RETRIEVE,

		"	Tasks"
		"		TASK_GET_PATH_TO_PLAYER			0"
		"		TASK_LEAD_MOVE_TO_RANGE			0"
		"		TASK_STOP_MOVING				0"
		"		TASK_WAIT_FOR_SPEAK_FINISH		1"
		"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_LEAD_RETRIEVE_WAIT"
		"		"
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_VERY_CLOSE"
		"		COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
		"		COND_LIGHT_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

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

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_SPEAK_THEN_RETRIEVE_PLAYER,

		"	Tasks"
		"		TASK_WAIT_FOR_SPEAK_FINISH		1"
		"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_LEAD_RETRIEVE"
		"		"
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_VERY_CLOSE"
		"		COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
		"		COND_LIGHT_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

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

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_RETRIEVE_WAIT,

		"	Tasks"
		"		TASK_LEAD_RETRIEVE_WAIT			0"
		"		"
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_LOST"
		"		COND_LEAD_FOLLOWER_LAGGING"
		"		COND_LEAD_FOLLOWER_VERY_CLOSE"
		"		COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
		"		COND_LEAD_FOLLOWER_MOVED_FROM_MARK"
		"		COND_LIGHT_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

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

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_PLAYER,

		"	Tasks"
		"		TASK_WAIT_FOR_SPEAK_FINISH	1"
		"		TASK_GET_PATH_TO_LEAD_GOAL	0"
		"		TASK_LEAD_WALK_PATH			0"
		"		TASK_WAIT_FOR_MOVEMENT		0"
		"		TASK_STOP_MOVING			0"
		""
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_LOST"
		"		COND_LEAD_FOLLOWER_LAGGING"
		"		COND_LIGHT_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

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

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_AWAIT_SUCCESS,

		"	Tasks"
		"		TASK_LEAD_FACE_GOAL			0"
		"		TASK_FACE_IDEAL				0"
		"		TASK_LEAD_ARRIVE			0"
		"		TASK_WAIT_INDEFINITE		0"
		""
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_LOST"
		"		COND_LEAD_FOLLOWER_LAGGING"
		"		COND_LEAD_SUCCESS"
		"		COND_LIGHT_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

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

	DEFINE_SCHEDULE
	(
		SCHED_LEAD_SUCCEED,

		"	Tasks"
		"		TASK_LEAD_SUCCEED			0"
		"		TASK_STOP_LEADING			0"
		""
	)

	//---------------------------------
	// This is the schedule Odell uses to pause the tour momentarily
	// if the player lags behind. If the player shows up in a 
	// couple of seconds, the tour will resume. Otherwise, Odell
	// moves to retrieve.
	//---------------------------------

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_PAUSE,

		"	Tasks"
		"		TASK_STOP_MOVING			1"
		"		TASK_FACE_TARGET			0"
		"		TASK_WAIT					5"
		"		TASK_WAIT_RANDOM			5"
		"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_LEAD_RETRIEVE"
		""
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_VERY_CLOSE"
		"		COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
		"		COND_LEAD_FOLLOWER_NOT_LAGGING"
		"		COND_LEAD_FOLLOWER_LOST"
		"		COND_LIGHT_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

	DEFINE_SCHEDULE
	(
		SCHED_LEAD_PAUSE_COMBAT,

		"	Tasks"
		"		TASK_STOP_MOVING			1"
		"		TASK_FACE_TARGET			0"
		"		TASK_WAIT					1"
		"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_LEAD_RETRIEVE"
		""
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_VERY_CLOSE"
		"		COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
		"		COND_LEAD_FOLLOWER_NOT_LAGGING"
		"		COND_LEAD_FOLLOWER_LOST"
		"		COND_HEAR_DANGER"
	)

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

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_WAITFORPLAYER,

		"	Tasks"
		"		TASK_LEAD_GET_PATH_TO_WAITPOINT	0"
		"		TASK_LEAD_WALK_PATH				0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"		TASK_STOP_MOVING				0"
		"		TASK_WAIT						0.5"
		"		TASK_FACE_TARGET				0"
		"		TASK_LEAD_WAVE_TO_PLAYER		0"
		"		TASK_WAIT						5.0"
		"		"
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_VERY_CLOSE"
		"		COND_LIGHT_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

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

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_WAITFORPLAYERIDLE,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_WAIT						0.5"
		"		TASK_FACE_TARGET				0"
		"		TASK_PLAY_SEQUENCE				ACTIVITY:ACT_IDLE"
		"		TASK_WAIT						2"
		"		"
		"	Interrupts"
		"		COND_LEAD_FOLLOWER_VERY_CLOSE"
		"		COND_LIGHT_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

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

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_PLAYERNEEDSWEAPON,

		"	Tasks"
		"		TASK_FACE_PLAYER				0"
		"		TASK_LEAD_PLAYER_NEEDS_WEAPON	0"
		"		TASK_WAIT_FOR_SPEAK_FINISH		1"
		"		TASK_WAIT						8"
		"		"
		"	Interrupts"
	)

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

	DEFINE_SCHEDULE
	( 
		SCHED_LEAD_SPEAK_START,

		"	Tasks"
		"		TASK_LEAD_SPEAK_START			0"
		"		TASK_WAIT_FOR_SPEAK_FINISH		1"
		""
		"	Interrupts"
	)

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

	DEFINE_SCHEDULE
	( 
	SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER,

	"	Tasks"
	"		TASK_STOP_MOVING			0"
	"		TASK_WAIT_FOR_SPEAK_FINISH	1"
	"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_LEAD_PLAYER"
	""
	"	Interrupts"
	"		COND_LEAD_FOLLOWER_LOST"
	"		COND_LEAD_FOLLOWER_LAGGING"
	"		COND_LIGHT_DAMAGE"
	"		COND_NEW_ENEMY"
	"		COND_HEAR_DANGER"
	)

AI_END_CUSTOM_SCHEDULE_PROVIDER()


//-----------------------------------------------------------------------------
//
// Purpose: A level tool to control the lead behavior. Use is not required
//			in order to use behavior.
//

class CAI_LeadGoal : public CAI_GoalEntity,
					 public CAI_LeadBehaviorHandler
{
	DECLARE_CLASS( CAI_LeadGoal, CAI_GoalEntity );
public:
	CAI_LeadGoal()
	 :	m_fArrived( false )
	{
		// These fields got added after existing levels shipped, so we set
		// the default values here in the constructor.
		m_iRetrievePlayer = 1;
		m_iRetrieveWaitForSpeak = 0;
		m_iComingBackWaitForSpeak = 0;
		m_bStopScenesWhenPlayerLost = false;
		m_bDontSpeakStart = false;
		m_bLeadDuringCombat = false;
		m_bGagLeader = false;
	}

	CAI_LeadBehavior *GetLeadBehavior();

	virtual const char *GetConceptModifiers( const char *pszConcept );

	virtual void InputActivate( inputdata_t &inputdata );
	virtual void InputDeactivate( inputdata_t &inputdata );
	
	DECLARE_DATADESC();
private:

	virtual void OnEvent( int event );
	void InputSetSuccess( inputdata_t &inputdata );
	void InputSetFailure( inputdata_t &inputdata );
	
	bool	 m_fArrived; // @TODO (toml 08-16-02): move arrived tracking onto behavior
	float	 m_flWaitDistance;
	float	 m_flLeadDistance;
	float	 m_flRetrieveDistance;
	float	 m_flSuccessDistance;
	bool	 m_bRun;
	int		 m_iRetrievePlayer;
	int		 m_iRetrieveWaitForSpeak;
	int		 m_iComingBackWaitForSpeak;
	bool	 m_bStopScenesWhenPlayerLost;
	bool	 m_bDontSpeakStart;
	bool	 m_bLeadDuringCombat;
	bool	 m_bGagLeader;

	string_t m_iszWaitPointName;

	string_t m_iszStartConceptModifier;
	string_t m_iszAttractPlayerConceptModifier;
	string_t m_iszWaitOverConceptModifier;
	string_t m_iszArrivalConceptModifier;
	string_t m_iszPostArrivalConceptModifier;
	string_t m_iszSuccessConceptModifier;
	string_t m_iszFailureConceptModifier;
	string_t m_iszRetrieveConceptModifier;
	string_t m_iszComingBackConceptModifier;

	// Output handlers
	COutputEvent	m_OnArrival;
	COutputEvent	m_OnArrivalDone;
	COutputEvent	m_OnSuccess;
	COutputEvent	m_OnFailure;
	COutputEvent	m_OnDone;
};

//-----------------------------------------------------------------------------
//
// CAI_LeadGoal implementation
//

LINK_ENTITY_TO_CLASS( ai_goal_lead, CAI_LeadGoal );

BEGIN_DATADESC( CAI_LeadGoal )

	DEFINE_FIELD( m_fArrived, FIELD_BOOLEAN ),

	DEFINE_KEYFIELD(m_flWaitDistance, 		FIELD_FLOAT, 	"WaitDistance"),
	DEFINE_KEYFIELD(m_iszWaitPointName, 	FIELD_STRING, 	"WaitPointName"),
	DEFINE_KEYFIELD(m_flLeadDistance, 		FIELD_FLOAT, 	"LeadDistance"),
	DEFINE_KEYFIELD(m_flRetrieveDistance, 	FIELD_FLOAT, 	"RetrieveDistance"),
	DEFINE_KEYFIELD(m_flSuccessDistance, 	FIELD_FLOAT, 	"SuccessDistance"),
	DEFINE_KEYFIELD(m_bRun, 				FIELD_BOOLEAN, 	"Run"),
	DEFINE_KEYFIELD(m_iRetrievePlayer,		FIELD_INTEGER,	"Retrieve"),
	DEFINE_KEYFIELD(m_iRetrieveWaitForSpeak,		FIELD_INTEGER,	"RetrieveWaitForSpeak"),
	DEFINE_KEYFIELD(m_iComingBackWaitForSpeak,		FIELD_INTEGER,	"ComingBackWaitForSpeak"),
	DEFINE_KEYFIELD(m_bStopScenesWhenPlayerLost,	FIELD_BOOLEAN,	"StopScenes"),
	DEFINE_KEYFIELD(m_bDontSpeakStart,	FIELD_BOOLEAN,	"DontSpeakStart"),
	DEFINE_KEYFIELD(m_bLeadDuringCombat, FIELD_BOOLEAN, "LeadDuringCombat"),
	DEFINE_KEYFIELD(m_bGagLeader, FIELD_BOOLEAN, "GagLeader"),

	DEFINE_KEYFIELD(m_iszStartConceptModifier,			FIELD_STRING, 	"StartConceptModifier"),
	DEFINE_KEYFIELD(m_iszAttractPlayerConceptModifier,	FIELD_STRING, 	"AttractPlayerConceptModifier"),
	DEFINE_KEYFIELD(m_iszWaitOverConceptModifier, 		FIELD_STRING, 	"WaitOverConceptModifier"),
	DEFINE_KEYFIELD(m_iszArrivalConceptModifier, 		FIELD_STRING, 	"ArrivalConceptModifier"),
	DEFINE_KEYFIELD(m_iszPostArrivalConceptModifier,	FIELD_STRING,	"PostArrivalConceptModifier"),
	DEFINE_KEYFIELD(m_iszSuccessConceptModifier,		FIELD_STRING,	"SuccessConceptModifier"),
	DEFINE_KEYFIELD(m_iszFailureConceptModifier,		FIELD_STRING,	"FailureConceptModifier"),
	DEFINE_KEYFIELD(m_iszRetrieveConceptModifier,		FIELD_STRING,	"RetrieveConceptModifier"),
	DEFINE_KEYFIELD(m_iszComingBackConceptModifier,		FIELD_STRING,	"ComingBackConceptModifier"),

	DEFINE_OUTPUT( m_OnSuccess, 		"OnSuccess" ),
	DEFINE_OUTPUT( m_OnArrival, 		"OnArrival" ),
	DEFINE_OUTPUT( m_OnArrivalDone, 	"OnArrivalDone" ),
	DEFINE_OUTPUT( m_OnFailure, 		"OnFailure" ),
	DEFINE_OUTPUT( m_OnDone,	  		"OnDone" ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "SetSuccess", InputSetSuccess ),
	DEFINE_INPUTFUNC( FIELD_VOID, "SetFailure", InputSetFailure ),

END_DATADESC()


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

CAI_LeadBehavior *CAI_LeadGoal::GetLeadBehavior()
{
	CAI_BaseNPC *pActor = GetActor();
	if ( !pActor )
		return NULL;

	CAI_LeadBehavior *pBehavior;
	if ( !pActor->GetBehavior( &pBehavior ) )
	{
		return NULL;
	}
	
	return pBehavior;
}

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

void CAI_LeadGoal::InputSetSuccess( inputdata_t &inputdata )
{
	CAI_LeadBehavior *pBehavior = GetLeadBehavior();
	if ( !pBehavior )
		return;
		
	// @TODO (toml 02-14-03): Hackly!
	pBehavior->SetCondition( CAI_LeadBehavior::COND_LEAD_SUCCESS);
}


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

void CAI_LeadGoal::InputSetFailure( inputdata_t &inputdata )
{
	DevMsg( "SetFailure unimplemented\n" );
}


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

void CAI_LeadGoal::InputActivate( inputdata_t &inputdata )
{
	BaseClass::InputActivate( inputdata );
	
	CAI_LeadBehavior *pBehavior = GetLeadBehavior();
	if ( !pBehavior )
	{
		DevMsg( "Lead goal entity activated for an NPC that doesn't have the lead behavior\n" );
		return;
	}
#ifdef HL2_EPISODIC
 	if ( (m_flLeadDistance*4) < m_flRetrieveDistance )
	{
		Warning("ai_goal_lead '%s': lead distance (%.2f) * 4 is < retrieve distance (%.2f). This will make the NPC act stupid. Either reduce the retrieve distance, or increase the lead distance.\n", GetDebugName(), m_flLeadDistance, m_flRetrieveDistance );
	}
#endif

	if ( m_flRetrieveDistance < (m_flLeadDistance + LEAD_MIN_RETRIEVEDIST_OFFSET) )
	{
#ifdef HL2_EPISODIC
		Warning("ai_goal_lead '%s': retrieve distance (%.2f) < lead distance (%.2f) + %d. Retrieve distance should be at least %d greater than the lead distance, or NPC will ping-pong while retrieving.\n", GetDebugName(), m_flRetrieveDistance, m_flLeadDistance, LEAD_MIN_RETRIEVEDIST_OFFSET, LEAD_MIN_RETRIEVEDIST_OFFSET );
#endif
		m_flRetrieveDistance = m_flLeadDistance + LEAD_MIN_RETRIEVEDIST_OFFSET;
	}

	AI_LeadArgs_t leadArgs = {
		GetGoalEntityName(),
		STRING(m_iszWaitPointName),
		(unsigned)m_spawnflags,
		m_flWaitDistance,
		m_flLeadDistance,
		m_flRetrieveDistance,
		m_flSuccessDistance,
		m_bRun,
		m_iRetrievePlayer,
		m_iRetrieveWaitForSpeak,
		m_iComingBackWaitForSpeak,
		m_bStopScenesWhenPlayerLost,
		m_bDontSpeakStart,
		m_bLeadDuringCombat,
		m_bGagLeader,
	};

	pBehavior->LeadPlayer( leadArgs, this );
}

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

void CAI_LeadGoal::InputDeactivate( inputdata_t &inputdata )
{
	BaseClass::InputDeactivate( inputdata );

	CAI_LeadBehavior *pBehavior = GetLeadBehavior();
	if ( !pBehavior )
		return;
		
	pBehavior->StopLeading();
}

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

void CAI_LeadGoal::OnEvent( int event )
{
	COutputEvent *pOutputEvent = NULL;

	switch ( event )
	{
		case LBE_ARRIVAL:		pOutputEvent = &m_OnArrival;		break;
		case LBE_ARRIVAL_DONE:	pOutputEvent = &m_OnArrivalDone;	break;
		case LBE_SUCCESS:		pOutputEvent = &m_OnSuccess;		break;
		case LBE_FAILURE:		pOutputEvent = &m_OnFailure;		break;
		case LBE_DONE:			pOutputEvent = &m_OnDone;			break;
	}
	
	// @TODO (toml 08-16-02): move arrived tracking onto behavior
	if ( event == LBE_ARRIVAL )
		m_fArrived = true;

	if ( pOutputEvent )
		pOutputEvent->FireOutput( this, this );
}

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

const char *CAI_LeadGoal::GetConceptModifiers( const char *pszConcept )	
{ 
	if ( m_iszStartConceptModifier != NULL_STRING && *STRING(m_iszStartConceptModifier) && strcmp( pszConcept, TLK_LEAD_START) == 0 )
		return STRING( m_iszStartConceptModifier );

	if ( m_iszAttractPlayerConceptModifier != NULL_STRING && *STRING(m_iszAttractPlayerConceptModifier) && strcmp( pszConcept, TLK_LEAD_ATTRACTPLAYER) == 0 )
		return STRING( m_iszAttractPlayerConceptModifier );

	if ( m_iszWaitOverConceptModifier != NULL_STRING && *STRING(m_iszWaitOverConceptModifier) && strcmp( pszConcept, TLK_LEAD_WAITOVER) == 0 )
		return STRING( m_iszWaitOverConceptModifier );

	if ( m_iszArrivalConceptModifier != NULL_STRING && *STRING(m_iszArrivalConceptModifier) && strcmp( pszConcept, TLK_LEAD_ARRIVAL) == 0 )
		return STRING( m_iszArrivalConceptModifier );
		
	if ( m_iszSuccessConceptModifier != NULL_STRING && *STRING(m_iszSuccessConceptModifier) && strcmp( pszConcept, TLK_LEAD_SUCCESS) == 0 )
		return STRING( m_iszSuccessConceptModifier );
		
	if (m_iszFailureConceptModifier != NULL_STRING && *STRING(m_iszFailureConceptModifier) && strcmp( pszConcept, TLK_LEAD_FAILURE) == 0 )
		return STRING( m_iszFailureConceptModifier );
	
	if (m_iszRetrieveConceptModifier != NULL_STRING && *STRING(m_iszRetrieveConceptModifier) && strcmp( pszConcept, TLK_LEAD_RETRIEVE) == 0 )
		return STRING( m_iszRetrieveConceptModifier );

	if (m_iszComingBackConceptModifier != NULL_STRING && *STRING(m_iszComingBackConceptModifier) && strcmp( pszConcept, TLK_LEAD_COMINGBACK) == 0 )
		return STRING( m_iszComingBackConceptModifier );

	if ( m_fArrived && m_iszPostArrivalConceptModifier != NULL_STRING && *STRING(m_iszPostArrivalConceptModifier) )
		return STRING( m_iszPostArrivalConceptModifier );
	
	return NULL; 
}


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

//-----------------------------------------------------------------------------
//
// Purpose: A custom lead goal that waits until the player has a weapon.
//

class CAI_LeadGoal_Weapon : public CAI_LeadGoal
{
	DECLARE_CLASS( CAI_LeadGoal_Weapon, CAI_LeadGoal );
public:

	virtual const char *GetConceptModifiers( const char *pszConcept );
	virtual void InputActivate( inputdata_t &inputdata );

private:
	string_t	m_iszWeaponName;
	string_t	m_iszMissingWeaponConceptModifier;

	DECLARE_DATADESC();
};

//-----------------------------------------------------------------------------
//
// CAI_LeadGoal_Weapon implementation
//

LINK_ENTITY_TO_CLASS( ai_goal_lead_weapon, CAI_LeadGoal_Weapon );

BEGIN_DATADESC( CAI_LeadGoal_Weapon )

	DEFINE_KEYFIELD( m_iszWeaponName, 		FIELD_STRING, 	"WeaponName"),
	DEFINE_KEYFIELD( m_iszMissingWeaponConceptModifier, FIELD_STRING, 	"MissingWeaponConceptModifier"),

END_DATADESC()

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

const char *CAI_LeadGoal_Weapon::GetConceptModifiers( const char *pszConcept )	
{ 
	if ( m_iszMissingWeaponConceptModifier != NULL_STRING && *STRING(m_iszMissingWeaponConceptModifier) && strcmp( pszConcept, TLK_LEAD_MISSINGWEAPON) == 0 )
		return STRING( m_iszMissingWeaponConceptModifier );

	return BaseClass::GetConceptModifiers( pszConcept ); 
}


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

void CAI_LeadGoal_Weapon::InputActivate( inputdata_t &inputdata )
{
	BaseClass::InputActivate( inputdata );

	CAI_LeadBehavior *pBehavior = GetLeadBehavior();
	if ( pBehavior )
	{
		pBehavior->SetWaitForWeapon( m_iszWeaponName );
	}
}