//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose: Behavior for NPCs riding in cars (with boys)
//
//=============================================================================

#include "cbase.h"
#include "ai_playerally.h"
#include "ai_motor.h"
#include "bone_setup.h"
#include "vehicle_base.h"
#include "entityblocker.h"
#include "ai_behavior_passenger.h"
#include "ai_pathfinder.h"
#include "ai_network.h"
#include "ai_node.h"
#include "ai_moveprobe.h"
#include "env_debughistory.h"

// Custom activities
int ACT_PASSENGER_IDLE;
int	ACT_PASSENGER_RANGE_ATTACK1;

ConVar passenger_debug_transition( "passenger_debug_transition", "0" );
ConVar passenger_impact_response_threshold( "passenger_impact_response_threshold", "-350.0" );

#define ORIGIN_KEYNAME "origin"
#define ANGLES_KEYNAME "angles"

BEGIN_DATADESC( CAI_PassengerBehavior )

	DEFINE_EMBEDDED( m_vehicleState ),
	DEFINE_FIELD( m_bEnabled,			FIELD_BOOLEAN ),
	DEFINE_FIELD( m_PassengerIntent,	FIELD_INTEGER ),
	DEFINE_FIELD( m_PassengerState,		FIELD_INTEGER ),
	DEFINE_FIELD( m_hVehicle,			FIELD_EHANDLE ),
	DEFINE_FIELD( m_hBlocker,			FIELD_EHANDLE ),
	DEFINE_FIELD( m_vecTargetPosition,	FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_vecTargetAngles,	FIELD_VECTOR ),
	DEFINE_FIELD( m_flOriginStartFrame, FIELD_FLOAT ),
	DEFINE_FIELD( m_flOriginEndFrame,	FIELD_FLOAT ),
	DEFINE_FIELD( m_flAnglesStartFrame, FIELD_FLOAT ),
	DEFINE_FIELD( m_flAnglesEndFrame,	FIELD_FLOAT ),
	DEFINE_FIELD( m_nTransitionSequence,FIELD_INTEGER ),

END_DATADESC();

BEGIN_SIMPLE_DATADESC( passengerVehicleState_t )

	DEFINE_FIELD( m_bWasBoosting,			FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bWasOverturned,			FIELD_BOOLEAN ),
	DEFINE_FIELD( m_vecLastLocalVelocity,	FIELD_VECTOR ),
	DEFINE_FIELD( m_vecDeltaVelocity,		FIELD_VECTOR ),
	DEFINE_FIELD( m_vecLastAngles,			FIELD_VECTOR ),
	DEFINE_FIELD( m_flNextWarningTime,		FIELD_TIME ),
	DEFINE_FIELD( m_flLastSpeedSqr,			FIELD_FLOAT ),
	DEFINE_FIELD( m_bPlayerInVehicle,		FIELD_BOOLEAN ),

END_DATADESC();

//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CAI_PassengerBehavior::CAI_PassengerBehavior( void ) : 
m_PassengerState( PASSENGER_STATE_OUTSIDE ),
m_hVehicle( NULL ),
m_bEnabled( false ),
m_PassengerIntent( PASSENGER_INTENT_NONE ),
m_nTransitionSequence( -1 )
{
}

//-----------------------------------------------------------------------------
// Purpose: Enables the behavior to run
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::Enable( CPropJeepEpisodic *pVehicle, bool bImmediateEnter /*= false*/ ) 
{ 
	if ( m_bEnabled && m_hVehicle.Get() )
		return;

	m_bEnabled = true; 
	m_hVehicle = pVehicle;
	SetPassengerState( PASSENGER_STATE_OUTSIDE );

	// Init our starting information about the vehicle
	InitVehicleState();
}

void CAI_PassengerBehavior::OnRestore()
{
	if ( m_bEnabled && !m_hVehicle.Get() )
	{
		Disable();
	}
	
	BaseClass::OnRestore();
}


//-----------------------------------------------------------------------------
// Purpose: Stops the behavior from being run
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::Disable( void )
{
	m_bEnabled = false;
	m_hVehicle = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: Starts the process of entering a vehicle
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::EnterVehicle( void )
{
	// If we're already in the vehicle, simply cancel out our intents
	if ( GetPassengerState() == PASSENGER_STATE_INSIDE || GetPassengerState() == PASSENGER_STATE_ENTERING )
	{
		// Clear out an exit
		if ( m_PassengerIntent == PASSENGER_INTENT_EXIT )
		{
			m_PassengerIntent = PASSENGER_INTENT_NONE;
			ClearCondition( COND_PASSENGER_ENTERING );
			ClearCondition( COND_PASSENGER_EXITING );
		}

		return;
	}

	// Update our internal state
	m_PassengerIntent = PASSENGER_INTENT_ENTER;

	// Otherwise set this condition and go!
	SetCondition( COND_PASSENGER_ENTERING );
}

//-----------------------------------------------------------------------------
// Purpose: Starts the process of exiting a vehicle
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::ExitVehicle( void )
{
	// Must be in the seat
	if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE || GetPassengerState() == PASSENGER_STATE_EXITING )
	{
		// Clear out an entrance
		if ( m_PassengerIntent == PASSENGER_INTENT_ENTER )
		{
			m_PassengerIntent = PASSENGER_INTENT_NONE;
			SetCondition( COND_PASSENGER_CANCEL_ENTER );
			ClearCondition( COND_PASSENGER_ENTERING );
			ClearCondition( COND_PASSENGER_EXITING );
		}
		return;
	}

	// Update our internal state
	m_PassengerIntent = PASSENGER_INTENT_EXIT;

	//
	// Everything below this point will still attempt to exit the vehicle, once able
	//

	// Must have a valid vehicle
	if ( m_hVehicle == NULL )
		return;

	// Cannot exit while we're upside down
	if ( m_hVehicle->IsOverturned() )
		return;

	// Interrupt what we're doing
	SetCondition( COND_PASSENGER_EXITING );
}

//-----------------------------------------------------------------------------
// Purpose: FIXME - This should move into something a bit more flexible
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::AddPhysicsPush( float force )
{
	/*
	// Kick the vehicle so the player knows we've arrived
	Vector impulse = m_hVehicle->GetAbsOrigin() - GetOuter()->GetAbsOrigin();
	VectorNormalize( impulse );
	impulse.z = -0.75;
	VectorNormalize( impulse );
	Vector vecForce = impulse * force;
	
	m_hVehicle->VPhysicsGetObject()->ApplyForceOffset( vecForce, GetOuter()->GetAbsOrigin() );
	*/

	Vector vecDir;

	IPhysicsObject *pObject = GetOuter()->VPhysicsGetObject();
	Vector vecVelocity;
	pObject->GetVelocity( &vecVelocity, NULL );
	GetOuter()->GetVectors( NULL, NULL, &vecDir );
	vecDir.Negate();

	Vector vecForce = vecDir * force;

	m_hVehicle->VPhysicsGetObject()->ApplyForceOffset( vecForce, GetOuter()->GetAbsOrigin() );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::IsPassengerHostile( void )
{
	CBaseEntity *pPlayer = AI_GetSinglePlayer();
	
	// If the player hates or fears the passenger, they're hostile
	if ( GetOuter()->IRelationType( pPlayer ) == D_HT || GetOuter()->IRelationType( pPlayer ) == D_FR )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::InitVehicleState( void )
{
	// Set the player's state
	CBasePlayer *pPlayer = AI_GetSinglePlayer();
	m_vehicleState.m_bPlayerInVehicle = ( pPlayer && pPlayer->IsInAVehicle() && pPlayer->GetServerVehicle() == m_hVehicle->GetServerVehicle() );

	// Update our vehicle state so we don't confuse our previous velocity on the first frame!
	m_vehicleState.m_bWasBoosting = false;
	m_vehicleState.m_bWasOverturned = false;
	m_vehicleState.m_flNextWarningTime = 0.0f;
	m_vehicleState.m_vecDeltaVelocity = vec3_origin;
	m_vehicleState.m_flNextWarningTime = gpGlobals->curtime;
	m_vehicleState.m_vecLastAngles = m_hVehicle->GetAbsAngles();

	Vector localVelocity(0.0f, 0.0f, 0.0f);
	GetLocalVehicleVelocity( &m_vehicleState.m_vecLastLocalVelocity );

	m_vehicleState.m_flLastSpeedSqr = localVelocity.LengthSqr();
}

//-----------------------------------------------------------------------------
// Purpose: Puts the NPC in hierarchy with the vehicle and makes them intangible
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::FinishEnterVehicle( void )
{
	if ( m_hVehicle == NULL )
		return;

	// Get the ultimate position we want to be in
	Vector vecFinalPos;
	QAngle vecFinalAngles;
	GetEntryTarget( &vecFinalPos, &vecFinalAngles );

	// Make sure we're exactly where we need to be
	GetOuter()->SetLocalOrigin( vecFinalPos );
	GetOuter()->SetLocalAngles( vecFinalAngles );
	GetOuter()->SetMoveType( MOVETYPE_NONE );
	GetOuter()->GetMotor()->SetYawLocked( true );

	// We're now riding inside the vehicle
	SetPassengerState( PASSENGER_STATE_INSIDE );
	
	// If we've not been told to leave immediately, we're done
	if ( m_PassengerIntent == PASSENGER_INTENT_ENTER )
	{
		m_PassengerIntent = PASSENGER_INTENT_NONE;
	}
	
	// Tell the vehicle we've succeeded 
	m_hVehicle->NPC_FinishedEnterVehicle( GetOuter(), (IsPassengerHostile()==false) );
}

//-----------------------------------------------------------------------------
// Purpose: Removes the NPC from the car
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::FinishExitVehicle( void )
{
	if ( m_hVehicle == NULL )
		return;

	// Destroy the blocker
	if ( m_hBlocker != NULL )
	{
		UTIL_Remove( m_hBlocker );
		m_hBlocker = NULL;
	}

	// To do this, we need to be very sure we're in a good spot
	GetOuter()->SetCondition( COND_PROVOKED );
	GetOuter()->SetMoveType( MOVETYPE_STEP );
	GetOuter()->RemoveFlag( FL_FLY );
	GetOuter()->GetMotor()->SetYawLocked( false );

	// Re-enable the physical collisions for this NPC
	IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject();
	if ( pPhysObj != NULL )
	{
		pPhysObj->EnableCollisions( true );
	}

	m_hVehicle->NPC_RemovePassenger( GetOuter() );
	m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), (IsPassengerHostile()==false) );

	SetPassengerState( PASSENGER_STATE_OUTSIDE );

	// Stop our custom move sequence
	GetOuter()->m_iszSceneCustomMoveSeq = NULL_STRING;

	// If we've not been told to enter immediately, we're done
	if ( m_PassengerIntent == PASSENGER_INTENT_EXIT )
	{
		m_PassengerIntent = PASSENGER_INTENT_NONE;
		Disable();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Build our custom interrupt cases for the behavior
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::BuildScheduleTestBits( void )
{
	// Always interrupt when we need to get in or out
	if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE || GetPassengerState() == PASSENGER_STATE_INSIDE )
	{
		GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_ENTERING ) );
		GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_EXITING ) );
		GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_CANCEL_ENTER ) );
	}

	BaseClass::BuildScheduleTestBits();
}

//-----------------------------------------------------------------------------
// Purpose: Dictates whether or not the behavior is active and working
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::CanSelectSchedule( void )
{
	return m_bEnabled;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::CanExitVehicle( void )
{
	// Vehicle must not be overturned
	if ( m_hVehicle->IsOverturned() )
		return false;

	// Vehicle must be at rest
	Vector vecVelocity;
	m_hVehicle->GetVelocity( &vecVelocity, NULL );	
	if ( vecVelocity.LengthSqr() > Square( 8.0f ) )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Handles passengers deciding to enter or exit the vehicle
// Output : int
//-----------------------------------------------------------------------------
int CAI_PassengerBehavior::SelectTransitionSchedule( void )
{
	// Handle our various states
	if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
	{
		// Exiting schedule
		if ( HasCondition( COND_PASSENGER_EXITING ) || m_PassengerIntent == PASSENGER_INTENT_EXIT )
		{
			if ( CanExitVehicle( ) )
			{
				ClearCondition( COND_PASSENGER_EXITING );
				return SCHED_PASSENGER_EXIT_VEHICLE;
			}
		}
	}
	else if ( GetPassengerState() == PASSENGER_STATE_ENTERING || GetPassengerState() == PASSENGER_STATE_EXITING )
	{
		// The following code attempts to fix up a passenger being interrupted mid-transition
		Warning( "SelectSchedule() called on transitioning passenger!\n" );
		ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs( "%s(%d):  SelectSchedule() called on transitioning passenger!\n", GetOuter()->GetDebugName(), GetOuter()->entindex() ) );
		Assert( 0 );

		// Correct this issue immediately
		if ( GetPassengerState() == PASSENGER_STATE_EXITING )
		{
			// Force them out of the vehicle to where they want to be
			// The teleport function is overridden for passengers, meaning that they will clean up properly when called to do so
			GetOuter()->Teleport( &m_vecTargetPosition, &m_vecTargetAngles, NULL );
		}
		else if ( GetPassengerState() == PASSENGER_STATE_ENTERING )
		{
			// Force them into the proper position
			GetOuter()->SetLocalOrigin( m_vecTargetPosition );
			GetOuter()->SetLocalAngles( m_vecTargetAngles ); 
			FinishEnterVehicle();
		}

		// Stop playing our animation
		SetActivity( ACT_RESET );
	}

	return SCHED_NONE;
}

//-----------------------------------------------------------------------------
// Purpose: Overrides the schedule selection
// Output : int - Schedule to play
//-----------------------------------------------------------------------------
int CAI_PassengerBehavior::SelectSchedule( void )
{
	// Protect from this rare occurrence happening
	if ( m_hVehicle == NULL )
	{
		Assert( m_hVehicle != NULL );
		Warning( "Entity %s running passenger behavior without a valid vehicle!\n", GetName() );
		
		Disable();
		return BaseClass::SelectSchedule();
	}

	// See if we're transitioning in / out of the vehicle
	int nSchedule = SelectTransitionSchedule();
	if ( nSchedule != SCHED_NONE )
		return nSchedule;

	return SCHED_PASSENGER_IDLE;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CAI_PassengerBehavior::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
{
	switch( failedTask )
	{
	// For now, just sit back down
	case TASK_PASSENGER_DETACH_FROM_VEHICLE:
		return SCHED_PASSENGER_IDLE;
		break;
	}

	return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
}

//-----------------------------------------------------------------------------
// Purpose: Finds a ground position at a given location with some delta up and down to check
// Input  : &in - position to check at
//			delta - amount of distance up and down to check
//			*out - ground position
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::FindGroundAtPosition( const Vector &in, float flUpDelta, float flDownDelta, Vector *out )
{
	Vector startPos = in + Vector( 0, 0, flUpDelta );	// Look up by delta
	Vector endPos   = in - Vector( 0, 0, flDownDelta );	// Look down by delta
	Vector hullMin  = GetOuter()->GetHullMins();
	Vector hullMax  = GetOuter()->GetHullMaxs();

	// Ignore ourself and the vehicle we're referencing
	CTraceFilterVehicleTransition ignoreFilter( m_hVehicle, GetOuter(), COLLISION_GROUP_NONE );

	trace_t tr;
	UTIL_TraceHull( startPos, endPos, hullMin, hullMax, MASK_NPCSOLID, &ignoreFilter, &tr );

	// Must not have ended up in solid space
	if ( tr.allsolid )
	{
		// Debug
		if ( passenger_debug_transition.GetBool() )
		{
			NDebugOverlay::SweptBox( tr.startpos, tr.endpos, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 1.0f );
		}
		
		return false;
	}

	// Must have ended up with feet on the ground
	if ( tr.DidHitWorld() || ( tr.m_pEnt && tr.m_pEnt->IsStandable() ) )
	{
		// Debug
		if ( passenger_debug_transition.GetBool() )
		{
			NDebugOverlay::SweptBox( tr.startpos, tr.endpos, hullMin, hullMax, vec3_angle, 0, 255, 0, 255, 1.0f );
		}
		
		*out = tr.endpos;
		return true;
	}

	// Ended up in the air
	if ( passenger_debug_transition.GetBool() )
	{
		NDebugOverlay::SweptBox( tr.startpos, tr.endpos, hullMin, hullMax, vec3_angle, 255, 0, 0, 255, 1.0f );
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Attempt to verify that a test position is on the node graph
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::PointIsNavigable( const Vector &vecTargetPos )
{
	// Attempt to local move between this point and the nearest node, ignoring anything but world architecture
	AIMoveTrace_t moveTrace;
	int iNearestNode = GetOuter()->GetPathfinder()->NearestNodeToPoint( vecTargetPos );
	if ( iNearestNode != NO_NODE )
	{
		// Try a movement trace between the test position and the node
		GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND, 
			g_pBigAINet->GetNodePosition(GetOuter()->GetHullType(), iNearestNode ), 
			vecTargetPos, 
			MASK_SOLID_BRUSHONLY, 
			NULL, 
			0, 
			&moveTrace );
		
		// See if we got close enough to call it arrived
		if ( ( moveTrace.vEndPosition - vecTargetPos ).LengthSqr() < Square( GetHullWidth() ) &&
			GetOuter()->GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY ) )
		{
			// NDebugOverlay::HorzArrow( vecTargetPos, moveTrace.vEndPosition, 8.0f, 255, 0, 0, 16, true, 4.0f );
			// NDebugOverlay::HorzArrow( vecTargetPos, g_pBigAINet->GetNodePosition(GetOuter()->GetHullType(), iNearestNode ), 8.0f, 255, 255, 0, 16, true, 4.0f );
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Gets the exit point for the passenger (on the ground)
// Input  : &vecOut - position the entity should be at when finished exiting
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::GetExitPoint( int nSequence, Vector *vecExitPoint, QAngle *vecExitAngles )
{
	bool bSucceeded = true;

	// Get the delta to the final position as will be dictated by this animation's auto movement
	Vector vecDeltaPos;
	QAngle vecDeltaAngles;
	GetOuter()->GetSequenceMovement( nSequence, 0.0f, 1.0f, vecDeltaPos, vecDeltaAngles );

	// Rotate the delta position by our starting angles
	Vector vecRotPos = vecDeltaPos;
	VectorRotate( vecRotPos, GetOuter()->GetAbsAngles(), vecDeltaPos );

	if ( vecExitPoint != NULL )
	{
		float flDownDelta = 64.0f;
		float flUpDelta = 16.0f;
		Vector vecGroundPos;
		
		bool bFoundGround = FindGroundAtPosition( GetOuter()->GetAbsOrigin() + vecDeltaPos, flUpDelta, flDownDelta, &vecGroundPos );
		if ( bFoundGround )
		{
			if ( PointIsNavigable( vecGroundPos ) == false )
			{
				bSucceeded = false;
			}
		}
		else
		{
			bSucceeded = false;
		}

		*vecExitPoint = vecGroundPos;
	}

	if ( vecExitAngles != NULL )
	{
		QAngle newAngles = GetOuter()->GetAbsAngles() + vecDeltaAngles;
		newAngles.x = UTIL_AngleMod( newAngles.x );
		newAngles.y = UTIL_AngleMod( newAngles.y );
		newAngles.z = UTIL_AngleMod( newAngles.z );
		*vecExitAngles = newAngles;
	}

	return bSucceeded;
}

//-----------------------------------------------------------------------------
// Purpose: Reserve our entry point
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::ReserveEntryPoint( VehicleSeatQuery_e eSeatSearchType )
{
	// FIXME: Move all this logic into the NPC_EnterVehicle function?
	// Find any seat to get into
	int nSeatID = m_hVehicle->GetServerVehicle()->NPC_GetAvailableSeat( GetOuter(), GetRoleName(), eSeatSearchType );
	if ( nSeatID != VEHICLE_SEAT_INVALID )
		return m_hVehicle->NPC_AddPassenger( GetOuter(), GetRoleName(), nSeatID );

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Determines whether the NPC can move between a start and end position of a transition
// Input  : &vecStartPos - start position
//			&vecEndPos - end position
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::IsValidTransitionPoint( const Vector &vecStartPos, const Vector &vecEndPos )
{
	// Now sweep a hull through space to see if we can validly exit there
	Vector vecHullMins = GetOuter()->GetHullMins() + Vector( 0, 0, GetOuter()->StepHeight()*2.0f );
	Vector vecHullMaxs = GetOuter()->GetHullMaxs() - Vector( 0, 0, GetOuter()->StepHeight() );

	trace_t tr;
	CTraceFilterVehicleTransition skipFilter( GetOuter(), m_hVehicle, COLLISION_GROUP_NONE );
	UTIL_TraceHull( vecStartPos, vecEndPos, vecHullMins, vecHullMaxs, MASK_NPCSOLID, &skipFilter, &tr );

	// If we're blocked, we can't get out there
	if ( tr.fraction < 1.0f || tr.allsolid )
	{
		if ( passenger_debug_transition.GetBool() )
		{
			NDebugOverlay::SweptBox( vecStartPos, vecEndPos, vecHullMins, vecHullMaxs, vec3_angle, 255, 0, 0, 64, 2.0f );
		}
		return false;
	}
	
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Find the proper sequence to use (weighted by priority or distance from current position)
//			to enter the vehicle.
// Input  : bNearest - Use distance as the criteria for a "best" sequence.  Otherwise the order of the
//					   seats is their priority.
// Output : int - sequence index
//-----------------------------------------------------------------------------
int CAI_PassengerBehavior::FindEntrySequence( bool bNearest /*= false*/ )
{
	// Get a list of all our animations
	const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
	if ( pEntryAnims == NULL )
		return -1;

	// Get the ultimate position we'll end up at
	Vector	vecStartPos, vecEndPos;
	if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecEndPos, NULL ) == false )
		return -1;

	const CPassengerSeatTransition *pTransition;
	Vector	vecSeatDir;
	float	flNearestDist = FLT_MAX;
	float	flSeatDist;
	int		nNearestSequence = -1;
	int		nSequence;

	// Test each animation (sorted by priority) for the best match
	for ( int i = 0; i < pEntryAnims->Count(); i++ )
	{
		// Find the activity for this animation name
		pTransition = &pEntryAnims->Element(i);
		nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
		if ( nSequence == -1 )
			continue;

		// Test this entry for validity
		if ( GetEntryPoint( nSequence, &vecStartPos ) == false )
			continue;

		// Check to see if we can use this
		if ( IsValidTransitionPoint( vecStartPos, vecEndPos ) )
		{
			// If we're just looking for the first, we're done
			if ( bNearest == false )
				return nSequence;

			// Otherwise distance is the deciding factor
			vecSeatDir = ( vecStartPos - GetOuter()->GetAbsOrigin() );
			flSeatDist = VectorNormalize( vecSeatDir );

			// Closer, take it
			if ( flSeatDist < flNearestDist )
			{
				flNearestDist = flSeatDist;
				nNearestSequence = nSequence;
			}
		}
			
	}

	return nNearestSequence;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
int CAI_PassengerBehavior::FindExitSequence( void )
{
	// Get a list of all our animations
	const PassengerSeatAnims_t *pExitAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_EXIT );
	if ( pExitAnims == NULL )
		return -1;

	// Get the ultimate position we'll end up at
	Vector	vecStartPos, vecEndPos;
	if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecStartPos, NULL ) == false )
		return -1;

	// Test each animation (sorted by priority) for the best match
	for ( int i = 0; i < pExitAnims->Count(); i++ )
	{
		// Find the activity for this animation name
		int nSequence = GetOuter()->LookupSequence( STRING( pExitAnims->Element(i).GetAnimationName() ) );
		if ( nSequence == -1 )
			continue;

		// Test this entry for validity
		if ( GetExitPoint( nSequence, &vecEndPos ) == false )
			continue;

		// Check to see if we can use this
		if ( IsValidTransitionPoint( vecStartPos, vecEndPos ) )
			return nSequence;
	}

	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: Reserve our exit point so nothing moves into it while we're moving
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::ReserveExitPoint( void )
{
	// Cannot exit while we're upside down
	// FIXME: This is probably redundant!
	if ( m_hVehicle->IsOverturned() )
		return false;

	// Find the exit activity to use
	int nSequence = FindExitSequence();
	if ( nSequence == -1 )
		return false;

	// Get the exit position
	Vector vecGroundPos;
	if ( GetExitPoint( nSequence, &vecGroundPos, &m_vecTargetAngles ) == false )
		return false;

	// We have to do this specially because the activities are not named
	SetTransitionSequence( nSequence );

	// Reserve this space
	Vector hullMin = GetOuter()->GetHullMins();
	Vector hullMax = GetOuter()->GetHullMaxs();
	m_hBlocker = CEntityBlocker::Create( vecGroundPos, hullMin, hullMax, GetOuter(), true );

	// Save this destination position so we can interpolate towards it
	m_vecTargetPosition = vecGroundPos;
	
	// Pitch and roll must be zero when we finish!
	m_vecTargetAngles.x = m_vecTargetAngles.z = 0.0f;

	if ( passenger_debug_transition.GetBool() )
	{
		Vector vecForward;
		AngleVectors( m_vecTargetAngles, &vecForward, NULL, NULL );
		Vector vecArrowEnd = m_vecTargetPosition + ( vecForward * 64.0f );
		NDebugOverlay::HorzArrow( m_vecTargetPosition, vecArrowEnd, 8.0f, 255, 255, 0, 64, true, 4.0f );
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Find the exact point we'd like to start our animation from to enter
//			the vehicle.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::GetEntryPoint( int nSequence, Vector *vecEntryPoint, QAngle *vecEntryAngles )
{
	bool bSucceeded = true;

	// Get the delta to the final position as will be dictated by this animation's auto movement
	Vector vecDeltaPos;
	QAngle vecDeltaAngles;
	GetOuter()->GetSequenceMovement( nSequence, 1.0f, 0.0f, vecDeltaPos, vecDeltaAngles );

	// Get the final position we're trying to end up at
	Vector vecTargetPos;
	QAngle vecTargetAngles;
	GetEntryTarget( &vecTargetPos, &vecTargetAngles );

	// Rotate it to match
	Vector vecPreDelta = vecDeltaPos;
	VectorRotate( vecPreDelta, vecTargetAngles, vecDeltaPos );

	// Offset this into the proper worldspace position
	vecTargetPos = vecTargetPos + vecDeltaPos;

	// Output the position, if requested
	if ( vecEntryPoint != NULL )
	{
		m_hVehicle->EntityToWorldSpace( vecTargetPos, vecEntryPoint );

		// Trace down to the ground to see where we'll stand
		Vector vecGroundPos;
		if ( FindGroundAtPosition( (*vecEntryPoint), 16.0f, 64.0f, &vecGroundPos ) == false )
		{
			// We failed
			if ( passenger_debug_transition.GetBool() )
			{
				NDebugOverlay::SweptBox( (*vecEntryPoint), vecGroundPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 64, 2.0f );
			}

			// The floor could not be found
			bSucceeded = false;
		}

		// Take this position
		*vecEntryPoint = vecGroundPos;
	}

	// Output the angles, if requested
	if ( vecEntryAngles != NULL )
	{
		// Add our delta angles to find what angles to start at
		*vecEntryAngles = vecTargetAngles;
		vecEntryAngles->y = UTIL_AngleMod( vecTargetAngles.y + vecDeltaAngles.y );

		//Transform those angles to worldspace
		matrix3x4_t angToParent, angToWorld;
		AngleMatrix( (*vecEntryAngles), angToParent );
		ConcatTransforms( m_hVehicle->EntityToWorldTransform(), angToParent, angToWorld );
		MatrixAngles( angToWorld, (*vecEntryAngles) );
	}

	// Debug info
	if ( passenger_debug_transition.GetBool() && vecEntryPoint && vecEntryAngles )
	{
		NDebugOverlay::Axis( *vecEntryPoint, vecTargetAngles, 16, true, 4.0f );
		NDebugOverlay::Cross3D( *vecEntryPoint, 4, 255, 255, 0, true, 4.0f );
		
		if ( vecEntryAngles != NULL )
		{
			Vector vecForward;
			AngleVectors( (*vecEntryAngles), &vecForward, NULL, NULL );
			Vector vecArrowEnd = (*vecEntryPoint ) + ( vecForward * 64.0f );
			NDebugOverlay::HorzArrow( (*vecEntryPoint), vecArrowEnd, 8.0f, 0, 255, 0, 64, true, 4.0f );
		}
	}

	return bSucceeded;
}

//-----------------------------------------------------------------------------
// Purpose: Do the low-level work to detach us from our vehicle
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::DetachFromVehicle( void )
{
	// Detach from the parent
	GetOuter()->SetParent( NULL );
	GetOuter()->SetMoveType( MOVETYPE_STEP );
	GetOuter()->AddFlag( FL_FLY );
	GetOuter()->SetGroundEntity( NULL );
	GetOuter()->SetCollisionGroup( COLLISION_GROUP_NPC );
	m_hVehicle->RemovePhysicsChild( GetOuter() );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::AttachToVehicle( void )
{
	// Parent to the vehicle
	GetOuter()->ClearForceCrouch();
	GetOuter()->SetParent( m_hVehicle );
	GetOuter()->AddFlag( FL_FLY );
	GetOuter()->SetGroundEntity( NULL );
	GetOuter()->SetCollisionGroup( COLLISION_GROUP_IN_VEHICLE );

	// Turn off physical interactions while we're in the vehicle
	IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject();
	if ( pPhysObj != NULL )
	{
		pPhysObj->EnableCollisions( false );
	}

	// Set our destination target
	GetEntryTarget( &m_vecTargetPosition, &m_vecTargetAngles );

	// Get physics messages from our attached physics object
	m_hVehicle->AddPhysicsChild( GetOuter() );
}

//-----------------------------------------------------------------------------
// Purpose: Handle task starting
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::StartTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_PASSENGER_ENTER_VEHICLE:
		{
			// You must have set your entrance animation before this point!
			Assert( m_nTransitionSequence != -1 );

			// Start us playing the correct sequence
			GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
			SetPassengerState( PASSENGER_STATE_ENTERING );

			// Overlaying any gestures will mess us up, so don't allow it
			GetOuter()->RemoveAllGestures();
		}
		break;

	case TASK_PASSENGER_EXIT_VEHICLE:
		{
			// You must have set your entrance animation before this point!
			Assert( m_nTransitionSequence != -1 );

			// Start us playing the correct sequence
			GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
			
			// Overlaying any gestures will mess us up, so don't allow it
			GetOuter()->RemoveAllGestures();
		}
		break;

	case TASK_PASSENGER_ATTACH_TO_VEHICLE:
		{
			AttachToVehicle();
			TaskComplete();
		}
		break;

	case TASK_PASSENGER_DETACH_FROM_VEHICLE:
		{
			// Place an entity blocker where we're going to go
			if ( ReserveExitPoint() == false )
			{
				OnExitVehicleFailed();
				TaskFail("Failed to find valid exit point\n");
				return;
			}

			// Physically detach from the vehicle
			DetachFromVehicle();

			// Mark that we're now disembarking
			SetPassengerState( PASSENGER_STATE_EXITING );

			TaskComplete();
		}
		break;

	case TASK_PASSENGER_SET_IDEAL_ENTRY_YAW:
		{
			// Get the ideal facing to enter the vehicle
			QAngle vecEntryAngles;
			GetEntryPoint( m_nTransitionSequence, NULL, &vecEntryAngles );
			GetOuter()->GetMotor()->SetIdealYaw( vecEntryAngles.y );
			
			TaskComplete();
			return;
		}	
		break;

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

//-----------------------------------------------------------------------------
// Purpose: Handle task running
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_PASSENGER_ENTER_VEHICLE:
		{	
			// Correct for angular/spatial deviation
			Assert( GetSequence() == m_nTransitionSequence );
			if ( GetSequence() != m_nTransitionSequence )
			{
				Warning("Corrected entrance animation on vehicle enter!\n");
				GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
				GetOuter()->GetNavigator()->ClearGoal();
				SetTransitionSequence( m_nTransitionSequence );
			}
			
			bool corrected = DoTransitionMovement();

			// We must be done with the animation and in the correct position
			if ( corrected == false )
			{
				FinishEnterVehicle();
				TaskComplete();
			}
		}
		break;

	case TASK_PASSENGER_EXIT_VEHICLE:
		{
			// Correct for angular/spatial deviation
			Assert( GetSequence() == m_nTransitionSequence );
			if ( GetSequence() != m_nTransitionSequence )
			{
				Warning("Corrected exit animation on vehicle exit!\n");
				GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
				GetOuter()->GetNavigator()->ClearGoal();
				SetTransitionSequence( m_nTransitionSequence );
			}

			// Correct for angular/spatial deviation
			bool corrected = DoTransitionMovement();

			// We must be done with the animation and in the correct position
			if ( corrected == false )
			{
				FinishExitVehicle();
				TaskComplete();
			}
		}
		break;

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

//-----------------------------------------------------------------------------
// Purpose: Find the blend amounts for position and angles, given a point in 
//			time within a sequence
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::GetSequenceBlendAmount( float flCycle, float *posBlend, float *angBlend )
{
	// Find positional blend, if requested
	if ( posBlend != NULL )
	{
		float flFrac = RemapValClamped( flCycle, m_flOriginStartFrame, m_flOriginEndFrame, 0.0f, 1.0f );
		(*posBlend) = SimpleSpline( flFrac );
	}

	// Find angular blend, if requested
	if ( angBlend != NULL )
	{
		float flFrac = RemapValClamped( flCycle, m_flAnglesStartFrame, m_flAnglesEndFrame, 0.0f, 1.0f );
		(*angBlend) = SimpleSpline( flFrac );		
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the target destination for the entry animation
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::GetEntryTarget( Vector *vecOrigin, QAngle *vecAngles )
{
	// Get the ultimate position we'll end up at
	m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPositionLocal( GetOuter(), vecOrigin, vecAngles );
}

//-----------------------------------------------------------------------------
// Purpose: Returns the ideal position to be in to end up at the target at the
//			end of the animation.
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::GetTransitionAnimationIdeal( float flCycle, const Vector &vecTargetPos, const QAngle &vecTargetAngles, Vector *idealOrigin, QAngle *idealAngles )
{
	// Get the position in time working backwards from our goal
	Vector vecDeltaPos;
	QAngle vecDeltaAngles;
	GetOuter()->GetSequenceMovement( GetSequence(), 1.0f, flCycle, vecDeltaPos, vecDeltaAngles );

	// Rotate the delta by our local angles
	Vector vecPreDelta = vecDeltaPos;
	VectorRotate( vecPreDelta, vecTargetAngles, vecDeltaPos );

	// Ideal origin
	if ( idealOrigin != NULL )
	{
		*idealOrigin = ( vecTargetPos + vecDeltaPos );
	}

	// Ideal angles
	if ( idealAngles != NULL )
	{
		(*idealAngles).x = anglemod( vecTargetAngles.x + vecDeltaAngles.x );
		(*idealAngles).y = anglemod( vecTargetAngles.y + vecDeltaAngles.y );
		(*idealAngles).z = anglemod( vecTargetAngles.z + vecDeltaAngles.z );
	}
}

//-----------------------------------------------------------------------------
//	FIXME:	This is basically a complete duplication of GetIntervalMovement
//			which doesn't remove the x and z components of the angles.  This
//			should be consolidated to not replicate so much code! -- jdw
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::LocalIntervalMovement( float flInterval, bool &bMoveSeqFinished, Vector &newPosition, QAngle &newAngles )
{
	CStudioHdr *pstudiohdr = GetOuter()->GetModelPtr();
	if ( pstudiohdr == NULL )
		return false;

	// Get our next cycle point
	float flNextCycle = GetNextCycleForInterval( GetSequence(), flInterval );

	// Fix-up loops
	if ( ( GetOuter()->SequenceLoops() == false ) && flNextCycle > 1.0f )
	{
		flInterval = GetOuter()->GetCycle() / ( GetOuter()->GetSequenceCycleRate( GetSequence() ) * GetOuter()->GetPlaybackRate() );
		flNextCycle = 1.0f;
		bMoveSeqFinished = true;
	}
	else
	{
		bMoveSeqFinished = false;
	}

	Vector deltaPos;
	QAngle deltaAngles;

	// Find the delta position and delta angles for this sequence
	if ( Studio_SeqMovement( pstudiohdr, GetOuter()->GetSequence(), GetOuter()->GetCycle(), flNextCycle, GetOuter()->GetPoseParameterArray(), deltaPos, deltaAngles ))
	{
		Vector vecPreDelta = deltaPos;
		VectorRotate( vecPreDelta, GetOuter()->GetLocalAngles(), deltaPos );
		
		newPosition = GetLocalOrigin() + deltaPos;
		newAngles = GetLocalAngles() + deltaAngles;

		return true;
	}
	else
	{
		newPosition = GetLocalOrigin();
		newAngles = GetLocalAngles();

		return false;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Get the next cycle point in a sequence for a given interval
//-----------------------------------------------------------------------------
float CAI_PassengerBehavior::GetNextCycleForInterval( int nSequence, float flInterval )
{
	return GetOuter()->GetCycle() + flInterval * GetOuter()->GetSequenceCycleRate( GetSequence() ) * GetOuter()->GetPlaybackRate();
}

//-----------------------------------------------------------------------------
// Purpose: Draw debug information for the transitional movement
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::DrawDebugTransitionInfo( const Vector &vecIdealPos, const QAngle &vecIdealAngles, const Vector &vecAnimPos, const QAngle &vecAnimAngles )
{
	// Debug info
	if ( GetPassengerState() == PASSENGER_STATE_ENTERING )
	{	
		// Green - Ideal location
		Vector foo;
		m_hVehicle->EntityToWorldSpace( vecIdealPos, &foo );
		NDebugOverlay::Cross3D( foo, 2, 0, 255, 0, true, 0.1f );
		NDebugOverlay::Axis( foo, vecIdealAngles, 8, true, 0.1f );

		// Blue - Actual location
		m_hVehicle->EntityToWorldSpace( vecAnimPos, &foo );
		NDebugOverlay::Cross3D( foo, 2, 0, 0, 255, true, 0.1f );
		NDebugOverlay::Axis( foo, vecAnimAngles, 8, true, 0.1f );
	}
	else
	{
		// Green - Ideal location
		NDebugOverlay::Cross3D( vecIdealPos, 4, 0, 255, 0, true, 0.1f );
		NDebugOverlay::Axis( vecIdealPos, vecIdealAngles, 8, true, 0.1f );

		// Blue - Actual location
		NDebugOverlay::Cross3D( vecAnimPos, 2, 0, 0, 255, true, 0.1f );
		NDebugOverlay::Axis( vecAnimPos, vecAnimAngles, 8, true, 0.1f );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Local movement to enter or exit the vehicle
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::DoTransitionMovement( void )
{
	// Get our animation's extrapolated end position
	Vector	vecAnimPos;
	QAngle	vecAnimAngles;
	float	flInterval = GetOuter()->GetAnimTimeInterval();
	bool	bSequenceFinished;

	// Get the position we're moving to for this frame with our animation's motion
	if ( LocalIntervalMovement( flInterval, bSequenceFinished, vecAnimPos, vecAnimAngles ) )
	{	
		// Get the position we'd ideally be in
		Vector vecIdealPos;
		QAngle vecIdealAngles;
		float flNextCycle = GetNextCycleForInterval( GetOuter()->GetSequence(), flInterval );
		flNextCycle = clamp( flNextCycle, 0.0f, 1.0f );
		GetTransitionAnimationIdeal( flNextCycle, m_vecTargetPosition, m_vecTargetAngles, &vecIdealPos, &vecIdealAngles );

		// Get the amount of error to blend out
		float flPosBlend = 1.0f;
		float flAngBlend = 1.0f;
		GetSequenceBlendAmount( flNextCycle, &flPosBlend, &flAngBlend );

		// Find the error between our position and our ideal
		Vector vecDelta = ( vecIdealPos - vecAnimPos ) * flPosBlend;
		
		QAngle vecDeltaAngles;
		vecDeltaAngles.x = AngleDiff( vecIdealAngles.x, vecAnimAngles.x ) * flAngBlend;
		vecDeltaAngles.y = AngleDiff( vecIdealAngles.y, vecAnimAngles.y ) * flAngBlend;
		vecDeltaAngles.z = AngleDiff( vecIdealAngles.z, vecAnimAngles.z ) * flAngBlend;

		// Factor in the error
		GetOuter()->SetLocalOrigin( vecAnimPos + vecDelta );
		GetOuter()->SetLocalAngles( vecAnimAngles + vecDeltaAngles );

		// Draw our debug information
		if ( passenger_debug_transition.GetBool() )
		{
			DrawDebugTransitionInfo( vecIdealPos, vecIdealAngles, vecAnimPos, vecAnimAngles );
		}

		// We're done moving
		if ( bSequenceFinished )
			return false;

		// We're still correcting out the error
		return true;
	}

	// There was no movement in the animation
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Translate normal schedules into vehicle schedules
//-----------------------------------------------------------------------------
int CAI_PassengerBehavior::TranslateSchedule( int scheduleType )
{
	if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
	{
		// Always be seated when riding in the car!
		if ( scheduleType == SCHED_IDLE_STAND )
			return SCHED_PASSENGER_IDLE;
	}

	return BaseClass::TranslateSchedule( scheduleType );
}

//-----------------------------------------------------------------------------
// Purpose: Returns the velocity of the vehicle with respect to its orientation
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::GetLocalVehicleVelocity( Vector *pOut  )
{
	Vector velocity;
	m_hVehicle->GetVelocity( &velocity, NULL );
	m_hVehicle->WorldToEntitySpace( m_hVehicle->GetAbsOrigin() + velocity, pOut );
}

//-----------------------------------------------------------------------------
// Purpose: Gather conditions we can comment on or react to while riding in the vehicle
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::GatherVehicleStateConditions( void )
{
	// Must have a vehicle to bother with this
	if ( m_hVehicle == NULL )
		return;

	// Clear out transient conditions
	ClearCondition( COND_PASSENGER_HARD_IMPACT );
	ClearCondition( COND_PASSENGER_ERRATIC_DRIVING );
	ClearCondition( COND_PASSENGER_JOSTLE_SMALL );
	ClearCondition( COND_PASSENGER_VEHICLE_STARTED );
	ClearCondition( COND_PASSENGER_VEHICLE_STOPPED );
	ClearCondition( COND_PASSENGER_PLAYER_ENTERED_VEHICLE );
	ClearCondition( COND_PASSENGER_PLAYER_EXITED_VEHICLE );

	CBasePlayer *pPlayer = AI_GetSinglePlayer();
	if ( pPlayer )
	{
		if ( pPlayer->IsInAVehicle() && pPlayer->GetVehicle() == m_hVehicle->GetServerVehicle() )
		{
			if ( m_vehicleState.m_bPlayerInVehicle == false )
			{
				SetCondition( COND_PASSENGER_PLAYER_ENTERED_VEHICLE );
				m_vehicleState.m_bPlayerInVehicle = true;
			}
		}
		else
		{
			if ( m_vehicleState.m_bPlayerInVehicle )
			{
				SetCondition( COND_PASSENGER_PLAYER_EXITED_VEHICLE );
				m_vehicleState.m_bPlayerInVehicle = false;
			}
		}
	}

	// Get the vehicle's boost state
	if ( m_hVehicle->m_nBoostTimeLeft < 100.0f )
	{
		if ( m_vehicleState.m_bWasBoosting == false )
		{
			m_vehicleState.m_bWasBoosting = true;
		}
	}
	else
	{
		m_vehicleState.m_bWasBoosting = false;
	}

	// Detect being overturned
	if ( m_hVehicle->IsOverturned() )
	{
		SetCondition( COND_PASSENGER_OVERTURNED );

		if ( m_vehicleState.m_bWasOverturned == false )
		{
			m_vehicleState.m_bWasOverturned = true;
		}
	}
	else
	{
		ClearCondition( COND_PASSENGER_OVERTURNED ); 
		m_vehicleState.m_bWasOverturned = false;
	}

	// Get our local velocity
	Vector	localVelocity;
	GetLocalVehicleVelocity( &localVelocity );

	Vector	deltaVelocity = ( localVelocity - m_vehicleState.m_vecLastLocalVelocity );

	// Detect a sudden stop!
	if ( deltaVelocity.y < passenger_impact_response_threshold.GetFloat() )
	{
		SetCondition( COND_PASSENGER_HARD_IMPACT );
	}
	else if ( fabs( deltaVelocity.x ) > 200.0f || fabs( deltaVelocity.z ) > 75.0f )
	{
		// The X axis represents lateral movement and the Z axis represents vertical movement{
   		SetCondition( COND_PASSENGER_ERRATIC_DRIVING );
	} 
	else if ( fabs( deltaVelocity.x ) > 50.0f || fabs( deltaVelocity.z ) > 25.0f )
	{
		// Lightly jostled
      	SetCondition( COND_PASSENGER_JOSTLE_SMALL );
	}

	// Get our speed
	float flSpeedSqr = localVelocity.LengthSqr();

	// See if we've crossed over the threshold between movement to stillness
	if ( m_vehicleState.m_flLastSpeedSqr > STOPPED_VELOCITY_THRESHOLD_SQR && flSpeedSqr < STOPPED_VELOCITY_THRESHOLD_SQR )
	{
		SetCondition( COND_PASSENGER_VEHICLE_STOPPED );
	}
	else if ( m_vehicleState.m_flLastSpeedSqr < STARTED_VELOCITY_THRESHOLD_SQR && flSpeedSqr > STARTED_VELOCITY_THRESHOLD_SQR )
	{
		// See if we've crossed over the threshold between stillness to movement
		SetCondition( COND_PASSENGER_VEHICLE_STARTED );
	}

	// Save this as our last speed
	m_vehicleState.m_flLastSpeedSqr = flSpeedSqr;

	// Find our delta velocity from the last frame
	m_vehicleState.m_vecDeltaVelocity = ( localVelocity - m_vehicleState.m_vecLastLocalVelocity );
	m_vehicleState.m_vecLastLocalVelocity = localVelocity;
	
	// Get our angular velocity
	Vector vecVelocity;
	AngularImpulse angVelocty;
	m_hVehicle->GetVelocity( &vecVelocity, &angVelocty );
	QAngle angVel( angVelocty.x, angVelocty.y, angVelocty.z );
	
	// Blend this into the old values
	m_vehicleState.m_vecLastAngles = ( m_vehicleState.m_vecLastAngles * 0.2f ) + ( angVel * 0.8f );
}

//-----------------------------------------------------------------------------
// Purpose: Do some pre-schedule clean-up
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::PrescheduleThink( void )
{
	BaseClass::PrescheduleThink();

	// If we're outside the vehicle, we need to turn this behavior off immediately
	if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE && HasCondition( COND_PASSENGER_CANCEL_ENTER ) )
	{
		// Clear out our passenger intent
		m_PassengerIntent = PASSENGER_INTENT_NONE;
		ClearCondition( COND_PASSENGER_CANCEL_ENTER );

		// Stop pathfinding
		GetOuter()->GetNavigator()->ClearGoal();

		// We're outside and have no intent to enter, so we're done
		Disable();

		// This must be stomped to cause our behavior to relinquish control
		GetOuter()->ClearSchedule("Passenger enter canceled");
	}

#ifdef DEBUG
	if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
	{
		Vector vecSeatOrigin;
		QAngle vecSeatAngles;
		if ( m_hVehicle && m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPositionLocal( GetOuter(), &vecSeatOrigin, &vecSeatAngles ) )
		{
			if ( ( GetLocalOrigin() - vecSeatOrigin ).LengthSqr() > Square( 0.1f ) )
			{
				Warning( "Passenger has strayed from seat position!\n" );
				// GetOuter()->SetLocalOrigin( vecSeatOrigin );
				// GetOuter()->SetLocalAngles( vecSeatAngles );
			}
		}
		else
		{
			Warning( "Passenger is in vehicle without a valid seat position! -- EJECTED\n" );
			GetOuter()->SetParent( NULL );
			Disable();

			return;
		}
	}
#endif // DEBUG
}

//-----------------------------------------------------------------------------
// Purpose: Gather conditions for our use in making decisions
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::GatherConditions( void )
{
	if ( IsEnabled() == false )
		return BaseClass::GatherConditions();

	// Sense the state of the car
	GatherVehicleStateConditions();

	BaseClass::GatherConditions();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet )
{
	if ( m_hVehicle == NULL )
		return;

	// Mark whether we're overturned or not
	bool bOverturned = m_hVehicle->IsOverturned();
	criteriaSet.AppendCriteria( "vehicle_overturned", bOverturned ? "1" : "0" );

	// Denote whether we're in the vehicle or not
	bool bInsideVehicle = ( GetPassengerState() == PASSENGER_STATE_INSIDE );
	criteriaSet.AppendCriteria( "vehicle_inside", bInsideVehicle ? "1" : "0" );

	// Note what angle we're at (extreme or normal)
	Vector vecUp( 0.0f, 0.0f, 1.0f );
	Vector vecVehicleUp;
	m_hVehicle->GetVectors( NULL, NULL, &vecVehicleUp );

	float flVehicleUp = DotProduct( vecVehicleUp, vecUp );
	criteriaSet.AppendCriteria( "vehicle_tilt", UTIL_VarArgs( "%.2f", flVehicleUp ) );

	// Set the vehicle's speed (necessary for certain types of movement judgments)
	float flVehicleSpeed = sqrt( m_vehicleState.m_flLastSpeedSqr );
	criteriaSet.AppendCriteria( "vehicle_speed", UTIL_VarArgs( "%f", flVehicleSpeed  ) );

	// Whether or not the passenger is currently able to enter the vehicle (only accounts for locking really)
	bool bCanExitVehicle = ( m_hVehicle->NPC_CanExitVehicle( GetOuter(), true ) );
	criteriaSet.AppendCriteria( "vehicle_can_exit", bCanExitVehicle ? "1" : "0" );

	// Whether or not the passenger is currently able to exit the vehicle (only accounts for locking really)
	bool bCanEnterVehicle = ( m_hVehicle->NPC_CanEnterVehicle( GetOuter(), true ) );
	criteriaSet.AppendCriteria( "vehicle_can_enter", bCanEnterVehicle ? "1" : "0" );
}

//-----------------------------------------------------------------------------
// Purpose: Cache off our frame numbers from the sequence keyvalue blocks
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::CacheBlendTargets( void )
{
	// Get the keyvalues for this sequence
	KeyValues *seqValues = GetOuter()->GetSequenceKeyValues( m_nTransitionSequence );
	if ( seqValues == NULL )
	{
		Assert( 0 );
		return;
	}

	// Get the entry/exit subkeys
	KeyValues *blendValues = seqValues->FindKey( "entryexit_blend" );
	if ( blendValues == NULL )
	{
		Assert( 0 );
		return;
	}

	// Find our frame range on this sequence
	int nMaxFrames = Studio_MaxFrame( GetOuter()->GetModelPtr(), m_nTransitionSequence, GetOuter()->GetPoseParameterArray() );

	// Find a key by this name
	KeyValues *subKeys = blendValues->FindKey( ORIGIN_KEYNAME );
	if ( subKeys )
	{
		// Retrieve our frame numbers
		m_flOriginStartFrame = subKeys->GetFloat( "startframe", 0.0f );
		m_flOriginEndFrame = subKeys->GetFloat( "endframe", nMaxFrames );

		// Convert to normalized values
		m_flOriginStartFrame = RemapValClamped( m_flOriginStartFrame, 0, nMaxFrames, 0.0f, 1.0f );
		m_flOriginEndFrame = RemapValClamped( m_flOriginEndFrame, 0, nMaxFrames, 0.0f, 1.0f );
	}

	// Find a key by this name
	subKeys = blendValues->FindKey( ANGLES_KEYNAME );
	if ( subKeys )
	{
		// Retrieve our frame numbers
		m_flAnglesStartFrame = subKeys->GetFloat( "startframe", 0.0f );
		m_flAnglesEndFrame = subKeys->GetFloat( "endframe", nMaxFrames );

		// Convert to normalized values
		m_flAnglesStartFrame = RemapValClamped( m_flAnglesStartFrame, 0, nMaxFrames, 0.0f, 1.0f );
		m_flAnglesEndFrame = RemapValClamped( m_flAnglesEndFrame, 0, nMaxFrames, 0.0f, 1.0f );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::SetTransitionSequence( int nSequence )
{
	// We need to use the ACT_SCRIPT_CUSTOM_MOVE scenario for this type of custom anim
	m_nTransitionSequence = nSequence;
	GetOuter()->m_iszSceneCustomMoveSeq = AllocPooledString( GetOuter()->GetSequenceName( m_nTransitionSequence ) );

	// Cache off our blending information at this point
	CacheBlendTargets();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::SpeakIfAllowed( AIConcept_t concept, const char *modifiers /*= NULL*/, bool bRespondingToPlayer /*= false*/, char *pszOutResponseChosen /*= NULL*/, size_t bufsize /*= 0*/ )
{
	// FIXME: Store this cast off?
	CAI_PlayerAlly *pAlly = dynamic_cast<CAI_PlayerAlly *>(GetOuter());
	if ( pAlly != NULL )
		return pAlly->SpeakIfAllowed( concept, modifiers, bRespondingToPlayer, pszOutResponseChosen, bufsize );

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Forces us to begin a dynamic scripted scene
// Input  : *lpszInteractionName - Name of the sequence we'll play
//			*pOther - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::ForceVehicleInteraction( const char *lpszInteractionName, CBaseCombatCharacter *pOther )
{
	// Don't do this unless we're sitting in the cabin of the vehicle!
	if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
		return false;

	// Set a sequence and fire it off!
	GetOuter()->m_iszSceneCustomMoveSeq = AllocPooledString( lpszInteractionName );
	GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );

	// Slam our schedule (very unsafe!)
	GetOuter()->SetSchedule( SCHED_PASSENGER_PLAY_SCRIPTED_ANIM );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Fix up teleport event when in the vehicle
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
{
	//First, safely remove me from the vehicle
	if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE )
	{
		// Detach from the vehicle
		DetachFromVehicle();
		FinishExitVehicle();
		
		// Turn the behavior off
		GetOuter()->ClearSchedule( "ai_behavior_passenger: teleport while in vehicle" );
		Disable();
	}

	//Then allow the teleportation
	BaseClass::Teleport( newPosition, newAngles, newVelocity );
}

//-----------------------------------------------------------------------------
// Purpose: We override this function because it can completely wreak havoc if
//			we're in the middle of a transition
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::ClearSchedule( const char *szReason )
{
	// Cannot do this while we're transitioning, but it's also a bug because the code that called it was probably relying on this to work!
	if ( GetPassengerState() == PASSENGER_STATE_ENTERING || GetPassengerState() == PASSENGER_STATE_EXITING )
	{
		Warning("ClearSchedule rejected due to transitioning passenger: %s\n", szReason );
		return;
	}

	// TODO: Even this will probably need more crafting depending on what we're doing in the vehicle
	// Otherwise allow it
	GetOuter()->ClearSchedule( szReason );
}

//-----------------------------------------------------------------------------
// Purpose: Dictate the terms for being interrupted by scripted schedules or scenes
//-----------------------------------------------------------------------------
bool CAI_PassengerBehavior::IsInterruptable( void )
{
	// NOTE: We should never be interrupted this way when in a car. This would effectively makes us go comatose if we
	//		 start a FACETO, MOVETO, or SEQUENCE command from a VCD.
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehavior::CancelEnterVehicle( void )
{
	// Stop!
	if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
	{
		SetCondition( COND_PASSENGER_CANCEL_ENTER );
	}
}

// ----------------------------------------------
// Custom AI declarations
// ----------------------------------------------

AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehavior )
{
	DECLARE_ACTIVITY( ACT_PASSENGER_IDLE )
	DECLARE_ACTIVITY( ACT_PASSENGER_RANGE_ATTACK1 )

	DECLARE_CONDITION( COND_PASSENGER_HARD_IMPACT )
	DECLARE_CONDITION( COND_PASSENGER_ENTERING )
	DECLARE_CONDITION( COND_PASSENGER_EXITING )
	DECLARE_CONDITION( COND_PASSENGER_VEHICLE_STARTED )
	DECLARE_CONDITION( COND_PASSENGER_VEHICLE_STOPPED )
	DECLARE_CONDITION( COND_PASSENGER_OVERTURNED )
	DECLARE_CONDITION( COND_PASSENGER_CANCEL_ENTER )
	DECLARE_CONDITION( COND_PASSENGER_ERRATIC_DRIVING )
	DECLARE_CONDITION( COND_PASSENGER_PLAYER_ENTERED_VEHICLE )
	DECLARE_CONDITION( COND_PASSENGER_PLAYER_EXITED_VEHICLE )
	DECLARE_CONDITION( COND_PASSENGER_JOSTLE_SMALL )

	DECLARE_TASK( TASK_PASSENGER_ENTER_VEHICLE )
	DECLARE_TASK( TASK_PASSENGER_EXIT_VEHICLE )
	DECLARE_TASK( TASK_PASSENGER_ATTACH_TO_VEHICLE )
	DECLARE_TASK( TASK_PASSENGER_DETACH_FROM_VEHICLE )
	DECLARE_TASK( TASK_PASSENGER_SET_IDEAL_ENTRY_YAW )

	// FIXME: Move to companion
	DEFINE_SCHEDULE
	(
	SCHED_PASSENGER_ENTER_VEHICLE,

	"	Tasks"
	"		TASK_PASSENGER_SET_IDEAL_ENTRY_YAW	0"
	"		TASK_FACE_IDEAL						0"
	"		TASK_PASSENGER_ATTACH_TO_VEHICLE	0"
	"		TASK_PASSENGER_ENTER_VEHICLE		0"
	""
	"	Interrupts"
	"		COND_NO_CUSTOM_INTERRUPTS"
	)

	DEFINE_SCHEDULE
	(
	SCHED_PASSENGER_EXIT_VEHICLE,

	"	Tasks"
	"		TASK_SET_FAIL_SCHEDULE		SCHEDULE:SCHED_PASSENGER_IDLE"
	"		TASK_PASSENGER_DETACH_FROM_VEHICLE	0"
	"		TASK_WAIT 0.1"  // We must wait one tick for us to start being updated
	"		TASK_PASSENGER_EXIT_VEHICLE	0"
	""
	"	Interrupts"
	"		COND_NO_CUSTOM_INTERRUPTS"
	"		COND_TASK_FAILED"
	)

	DEFINE_SCHEDULE
	( 
	SCHED_PASSENGER_IDLE,

	"	Tasks"
	"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE"
	"		TASK_WAIT					2"
	""
	"	Interrupts"
	"		COND_PROVOKED"
	"		COND_NEW_ENEMY"
	"		COND_CAN_RANGE_ATTACK1"
	"		COND_CAN_MELEE_ATTACK1"
	"		COND_PASSENGER_EXITING"
	"		COND_HEAR_DANGER"
	)

	DEFINE_SCHEDULE
	( 
	SCHED_PASSENGER_PLAY_SCRIPTED_ANIM,
	
	"	Tasks"
	"		TASK_PLAY_SEQUENCE			ACTIVITY:ACT_SCRIPT_CUSTOM_MOVE"
	""
	"	Interrupts"
	"		COND_PASSENGER_HARD_IMPACT"
	)

	AI_END_CUSTOM_SCHEDULE_PROVIDER()
}
