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

#include "cbase.h"
#include "npcevent.h"
#include "ai_motor.h"
#include "ai_senses.h"
#include "vehicle_jeep_episodic.h"
#include "npc_alyx_episodic.h"
#include "ai_behavior_passenger_zombie.h"

#define JUMP_ATTACH_DIST_THRESHOLD			1000
#define JUMP_ATTACH_FACING_THRESHOLD		DOT_45DEGREE

#define ATTACH_PREDICTION_INTERVAL			0.2f
#define ATTACH_PREDICTION_FACING_THRESHOLD	0.75f
#define	ATTACH_PREDICTION_DIST_THRESHOLD	128

int ACT_PASSENGER_MELEE_ATTACK1;
int ACT_PASSENGER_THREATEN;
int ACT_PASSENGER_FLINCH;
int ACT_PASSENGER_ZOMBIE_LEAP_LOOP;

BEGIN_DATADESC( CAI_PassengerBehaviorZombie )

	DEFINE_FIELD( m_flLastVerticalLean, FIELD_FLOAT ),
	DEFINE_FIELD( m_flLastLateralLean,	FIELD_FLOAT ),
	DEFINE_FIELD( m_flNextLeapTime,		FIELD_TIME ),

END_DATADESC();

extern int AE_PASSENGER_PHYSICS_PUSH;

//==============================================================================================
// Passenger damage table
//==============================================================================================
static impactentry_t zombieLinearTable[] =
{
	{ 200*200, 100 },
};

static impactentry_t zombieAngularTable[] =
{
	{ 100*100, 100 },
};

impactdamagetable_t gZombiePassengerImpactDamageTable =
{
		zombieLinearTable,
		zombieAngularTable,

		ARRAYSIZE(zombieLinearTable),
		ARRAYSIZE(zombieAngularTable),

		24*24,		// minimum linear speed squared
		360*360,	// minimum angular speed squared (360 deg/s to cause spin/slice damage)
		2,			// can't take damage from anything under 2kg

		5,			// anything less than 5kg is "small"
		5,			// never take more than 5 pts of damage from anything under 5kg
		36*36,		// <5kg objects must go faster than 36 in/s to do damage

		VPHYSICS_LARGE_OBJECT_MASS,		// large mass in kg 
		4,			// large mass scale (anything over 500kg does 4X as much energy to read from damage table)
		5,			// large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
		0.0f,		// min vel
};

//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CAI_PassengerBehaviorZombie::CAI_PassengerBehaviorZombie( void ) : 
m_flLastLateralLean( 0.0f ),
m_flLastVerticalLean( 0.0f ),
m_flNextLeapTime( 0.0f )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehaviorZombie::CanEnterVehicle( void )
{
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Translate into vehicle passengers
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::TranslateSchedule( int scheduleType )
{
	// We do different animations when inside the vehicle
	if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
	{
		if ( scheduleType == SCHED_MELEE_ATTACK1 )
			return SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1;

		if ( scheduleType == SCHED_RANGE_ATTACK1 )
			return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
	}

	return BaseClass::TranslateSchedule( scheduleType );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : activity - 
// Output : Activity
//-----------------------------------------------------------------------------
Activity CAI_PassengerBehaviorZombie::NPC_TranslateActivity( Activity activity )
{
	Activity nNewActivity = BaseClass::NPC_TranslateActivity( activity );
	if ( activity == ACT_IDLE )
		return (Activity) ACT_PASSENGER_IDLE;

	return nNewActivity;
}

//-----------------------------------------------------------------------------
// Purpose: Suppress melee attacks against enemies for the given duration
// Input  : flDuration - Amount of time to suppress the attacks
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::SuppressAttack( float flDuration )
{
	GetOuter()->SetNextAttack( gpGlobals->curtime + flDuration );
}

//-----------------------------------------------------------------------------
// Purpose: Determines if an enemy is inside a vehicle or not
// Output : Returns true if the enemy is outside the vehicle.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehaviorZombie::EnemyInVehicle( void )
{
	// Obviously they're not...
	if ( GetOuter()->GetEnemy() == NULL )
		return false;

	// See if they're in a vehicle, currently
	CBaseCombatCharacter *pCCEnemy = GetOuter()->GetEnemy()->MyCombatCharacterPointer();
	if ( pCCEnemy && pCCEnemy->IsInAVehicle() )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Select a schedule when we're outside of the vehicle
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::SelectOutsideSchedule( void )
{
	// Attaching to target
	if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
		return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;

	// Attack the player if we're able
	if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
		return SCHED_MELEE_ATTACK1;

	// Attach to the vehicle
	if ( HasCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE ) )
		return SCHED_PASSENGER_ZOMBIE_ATTACH;

	// Otherwise chase after him
	return SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE;
}

//-----------------------------------------------------------------------------
// Purpose: Pick a schedule for being "inside" the vehicle
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::SelectInsideSchedule( void )
{
	// Attacking target
	if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
		return SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1;

	return SCHED_IDLE_STAND;
}

//-----------------------------------------------------------------------------
// Purpose: Move the zombie to the vehicle
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::SelectSchedule( void )
{
	// See if our enemy got out
	if ( GetOuter()->GetEnemy() != NULL && EnemyInVehicle() == false  )
	{
		if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
		{
			// Exit the vehicle
			SetCondition( COND_PASSENGER_EXITING );
		}
		else if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
		{
			// Our target has left the vehicle and we're outside as well, so give up
			Disable();
			return BaseClass::SelectSchedule();
		}
	}

	// Entering schedule
	if ( HasCondition( COND_PASSENGER_ENTERING ) )
	{
		ClearCondition( COND_PASSENGER_ENTERING );
		return SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE;
	}

	// Exiting schedule
	if ( HasCondition( COND_PASSENGER_EXITING ) )
	{
		ClearCondition( COND_PASSENGER_EXITING );
		return SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE;
	}

	// Select different schedules based on our state
	PassengerState_e nState = GetPassengerState();
	int nNewSchedule = SCHED_NONE;

	if ( nState == PASSENGER_STATE_INSIDE )
	{
		nNewSchedule = SelectInsideSchedule();
		if ( nNewSchedule != SCHED_NONE )
			return nNewSchedule;
	}
	else if ( nState == PASSENGER_STATE_OUTSIDE )
	{
		nNewSchedule = SelectOutsideSchedule();
		if ( nNewSchedule != SCHED_NONE )
			return nNewSchedule;
	}

	// Worst case he just stands here
	Assert(0);
	return SCHED_IDLE_STAND;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehaviorZombie::CanJumpToAttachToVehicle( void )
{
	// FIXME: Probably move this up one level and out of this function
	if ( m_flNextLeapTime > gpGlobals->curtime )
		return false;

	// Predict an attachment jump
	CBaseEntity *pEnemy = GetOuter()->GetEnemy();

	Vector	vecPredictedPosition;
	UTIL_PredictedPosition( pEnemy, 1.0f, &vecPredictedPosition );

	float flDist = UTIL_DistApprox( vecPredictedPosition, GetOuter()->GetAbsOrigin() );

	// If we're facing them enough, allow the jump
	if ( ( flDist < JUMP_ATTACH_DIST_THRESHOLD ) && UTIL_IsFacingWithinTolerance( GetOuter(), pEnemy, JUMP_ATTACH_FACING_THRESHOLD ) )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Determine if we can jump to be on the enemy's vehicle
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
inline bool CAI_PassengerBehaviorZombie::CanBeOnEnemyVehicle( void )
{
	CBaseCombatCharacter *pEnemy = ToBaseCombatCharacter( GetOuter()->GetEnemy() );
	if ( pEnemy != NULL )
	{
		IServerVehicle *pVehicle = pEnemy->GetVehicle();
		if ( pVehicle && pVehicle->NPC_HasAvailableSeat( GetRoleName() ) )
			return true;
	}

	return false;
}

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

	// Always clear the base conditions
	ClearCondition( COND_CAN_MELEE_ATTACK1 );

	// Behavior when outside the vehicle
	if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
	{
		if ( CanBeOnEnemyVehicle() && CanJumpToAttachToVehicle() )
		{
			SetCondition( COND_CAN_RANGE_ATTACK1 );
		}
		
		// Determine if we can latch on to the vehicle (out of sight)
		ClearCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE );
		CBasePlayer *pPlayer = AI_GetSinglePlayer();
		
		if ( pPlayer != NULL && 
			 GetOuter()->GetEnemy() == pPlayer && 
			 pPlayer->GetVehicleEntity() == m_hVehicle )
		{
			// Can't be visible to the player and must be close enough
			bool bNotVisibleToPlayer = ( pPlayer->FInViewCone( GetOuter() ) == false );
			float flDistSqr = ( pPlayer->GetAbsOrigin() - GetOuter()->GetAbsOrigin() ).LengthSqr();
			bool bInRange = ( flDistSqr < Square(250.0f) );
			if ( bNotVisibleToPlayer && bInRange )
			{
				// We can latch on and "enter" the vehicle
				SetCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE );
			}
			else if ( bNotVisibleToPlayer == false && flDistSqr < Square(128.0f) )
			{
				// Otherwise just hit the vehicle in anger
				SetCondition( COND_CAN_MELEE_ATTACK1 );
			}
		}
	}

	// Behavior when on the car
	if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
	{
		// Check for melee attack
		if ( GetOuter()->GetNextAttack() < gpGlobals->curtime )
		{
			SetCondition( COND_CAN_MELEE_ATTACK1 );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handle death case
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::Event_Killed( const CTakeDamageInfo &info )
{
	if ( m_hVehicle )
	{
		// Stop taking messages from the vehicle
		m_hVehicle->RemovePhysicsChild( GetOuter() );
		m_hVehicle->NPC_RemovePassenger( GetOuter() );
		m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
	}

	BaseClass::Event_Killed( info );
}

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

	BaseClass::BuildScheduleTestBits();
}

//-----------------------------------------------------------------------------
// Purpose: Get the absolute position of the desired attachment point
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::GetAttachmentPoint( Vector *vecPoint )
{
	Vector vecEntryOffset, vecFinalOffset;
	GetEntryTarget( &vecEntryOffset, NULL );
	VectorRotate( vecEntryOffset, m_hVehicle->GetAbsAngles(), vecFinalOffset );
	*vecPoint = ( m_hVehicle->GetAbsOrigin() + vecFinalOffset );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::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;

	// 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() ) );
		Assert( nSequence != -1 );
		if ( nSequence == -1 )
			continue;

		return nSequence;
	}

	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::StartDismount( void )
{
	// Leap off the vehicle
	int nSequence = FindExitSequence();
	Assert( nSequence != -1 );

	SetTransitionSequence( nSequence );
	GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );

	// This removes the NPC from the vehicle's handling and fires all necessary outputs
	m_hVehicle->RemovePhysicsChild( GetOuter() );
	m_hVehicle->NPC_RemovePassenger( GetOuter() );
	m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), (IsPassengerHostile()==false) );

	// Detach from the parent
	GetOuter()->SetParent( NULL );
	GetOuter()->SetMoveType( MOVETYPE_STEP );
	GetMotor()->SetYawLocked( false );

	QAngle vecAngles = GetAbsAngles();
	vecAngles.z = 0.0f;
	GetOuter()->SetAbsAngles( vecAngles );

	// HACK: Will this work?
	IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject();
	if ( pPhysObj != NULL )
	{
		pPhysObj->EnableCollisions( true );
	}

	// Clear this
	m_PassengerIntent = PASSENGER_INTENT_NONE;
	SetPassengerState( PASSENGER_STATE_EXITING );

	// Get the velocity
	Vector vecUp, vecJumpDir;
	GetOuter()->GetVectors( &vecJumpDir, NULL, &vecUp );

	// Move back and up
	vecJumpDir *= random->RandomFloat( -400.0f, -500.0f );
	vecJumpDir += vecUp * 150.0f;
	GetOuter()->SetAbsVelocity( vecJumpDir );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::FinishDismount( void )
{
	SetPassengerState( PASSENGER_STATE_OUTSIDE );
	Disable();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::StartTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_FACE_HINTNODE:
	case TASK_FACE_LASTPOSITION:
	case TASK_FACE_SAVEPOSITION:
	case TASK_FACE_TARGET:
	case TASK_FACE_IDEAL:
	case TASK_FACE_SCRIPT:
	case TASK_FACE_PATH:
		TaskComplete();
		break;

	case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
		break;

	case TASK_MELEE_ATTACK1:
		{
			// Only override this if we're "in" the vehicle
			if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
			{
				BaseClass::StartTask( pTask );
				break;
			}

			// Swipe
			GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_MELEE_ATTACK1 );
			
			// Randomly attack again in the future
			float flWait = random->RandomFloat( 0.0f, 1.0f );
			SuppressAttack( flWait );
		}
		break;

	case TASK_PASSENGER_ZOMBIE_DISMOUNT:
		{
			// Start the process of dismounting from the vehicle
			StartDismount();
		}
		break;

	case TASK_PASSENGER_ZOMBIE_ATTACH:
		{
			if ( AttachToVehicle() )
			{
				TaskComplete();
				return;
			}

			TaskFail( "Unable to attach to vehicle!" );
		}
		break;

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

//-----------------------------------------------------------------------------
// Purpose: Handle task running
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
		{
			// Face the entry point
			Vector vecAttachPoint;
			GetAttachmentPoint( &vecAttachPoint );
			GetOuter()->GetMotor()->SetIdealYawToTarget( vecAttachPoint );

			// All done when you touch the ground
			if ( GetOuter()->GetFlags() & FL_ONGROUND )
			{
				m_flNextLeapTime = gpGlobals->curtime + 2.0f;
				TaskComplete();
				return;
			}
		}
		break;

	case TASK_MELEE_ATTACK1:

		if ( GetOuter()->IsSequenceFinished() )
		{
			TaskComplete();
		}

		break;

	case TASK_PASSENGER_ZOMBIE_DISMOUNT:
		{
			if ( GetOuter()->IsSequenceFinished() )
			{
				// Completely separate from the vehicle
				FinishDismount();
				TaskComplete();
			}

			break;
		}

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

//-----------------------------------------------------------------------------
// Purpose: Find the relative cost of an entry point based on facing
// Input  : &vecEntryPos - Position we're evaluating
// Output : Returns the cost as a modified distance value
//-----------------------------------------------------------------------------
float CAI_PassengerBehaviorZombie::GetEntryPointCost( const Vector &vecEntryPos )
{
	// FIXME: We don't care about cost any longer!
	return 1.0f;

	// Find the direction from us to the entry point
	Vector vecEntryDir = ( vecEntryPos - GetAbsOrigin() );
	float flCost = VectorNormalize( vecEntryDir );
	
	// Get our current facing
	Vector vecDir;
	GetOuter()->GetVectors( &vecDir, NULL, NULL );

	// Scale our cost by how closely it matches our facing
	float flDot = DotProduct( vecEntryDir, vecDir );
	if ( flDot < 0.0f )
		return FLT_MAX;

	flCost *= RemapValClamped( flDot, 1.0f, 0.0f, 1.0f, 2.0f );

	return flCost;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bNearest - 
// Output : int
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::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;

	Vector vecStartPos;
	const CPassengerSeatTransition *pTransition;
	float flBestCost = FLT_MAX;
	float flCost;
	int nBestSequence = -1;
	int nSequence = -1;

	// 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() ) );

		Assert( nSequence != -1 );
		if ( nSequence == -1 )
			continue;

		// Test this entry for validity
		GetEntryPoint( nSequence, &vecStartPos );

		// Evaluate the cost
		flCost = GetEntryPointCost( vecStartPos );
		if ( flCost < flBestCost )
		{
			nBestSequence = nSequence;
			flBestCost = flCost;
			continue;
		}
	}

	return nBestSequence;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::ExitVehicle( void )
{
	BaseClass::ExitVehicle();

	// Remove us as a passenger
	m_hVehicle->NPC_RemovePassenger( GetOuter() );
	m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );

}

//-----------------------------------------------------------------------------
// Purpose: Calculate our body lean based on our delta velocity
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::CalculateBodyLean( void )
{
	// Calculate our lateral displacement from a perfectly centered start
	float flLateralDisp = SimpleSplineRemapVal( m_vehicleState.m_vecLastAngles.z, 100.0f, -100.0f, -1.0f, 1.0f );
	flLateralDisp = clamp( flLateralDisp, -1.0f, 1.0f );

	// FIXME: Framerate dependant!
	m_flLastLateralLean = ( m_flLastLateralLean * 0.2f ) + ( flLateralDisp * 0.8f );

	// Factor in a "stun" if the zombie was moved too far off course
	if ( fabs( m_flLastLateralLean ) > 0.75f )
	{
		SuppressAttack( 0.5f );
	}

	// Calc our vertical displacement
	float flVerticalDisp = SimpleSplineRemapVal( m_vehicleState.m_vecDeltaVelocity.z, -50.0f, 50.0f, -1.0f, 1.0f );
	flVerticalDisp = clamp( flVerticalDisp, -1.0f, 1.0f );

	// FIXME: Framerate dependant!
	m_flLastVerticalLean = ( m_flLastVerticalLean * 0.75f ) + ( flVerticalDisp * 0.25f );
	
	// Set these parameters
	GetOuter()->SetPoseParameter( "lean_lateral", m_flLastLateralLean );
	GetOuter()->SetPoseParameter( "lean_vertical", m_flLastVerticalLean );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::GatherVehicleStateConditions( void )
{
	// Call to the base
	BaseClass::GatherVehicleStateConditions();

	// Only do this if we're on the vehicle
	if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
		return;

	// Calculate how our body is leaning
	CalculateBodyLean();

	// The forward delta of the vehicle
	float flLateralDelta = ( m_vehicleState.m_vecDeltaVelocity.x + m_vehicleState.m_vecDeltaVelocity.y );

	// Detect a sudden stop
	if ( flLateralDelta < -350.0f )
	{
		if ( m_hVehicle )
		{
			Vector vecDamageForce;
			m_hVehicle->GetVelocity( &vecDamageForce, NULL );
			VectorNormalize( vecDamageForce );
			vecDamageForce *= random->RandomFloat( 50000.0f, 60000.0f );
			
			//NDebugOverlay::HorzArrow( GetAbsOrigin(), GetAbsOrigin() + ( vecDamageForce * 256.0f ), 16.0f, 255, 0, 0, 16, true, 2.0f );

			// Fake it!
			CTakeDamageInfo info( m_hVehicle, m_hVehicle, vecDamageForce, GetOuter()->WorldSpaceCenter(), 200, (DMG_CRUSH|DMG_VEHICLE) );
			GetOuter()->TakeDamage( info );
		}
	}
	else if ( flLateralDelta < -150.0f )
	{
		// FIXME: Realistically this should interrupt and play a schedule to do it
		GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_FLINCH );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEvent - 
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::HandleAnimEvent( animevent_t *pEvent )
{
	if ( pEvent->event == AE_PASSENGER_PHYSICS_PUSH )
	{
		// Add a push into the vehicle
		float flForce = (float) atof( pEvent->options );
		AddPhysicsPush( flForce * 0.75f );
		return;
	}

	BaseClass::HandleAnimEvent( pEvent );
}

//-----------------------------------------------------------------------------
// Purpose: Attach to the vehicle if we're able
//-----------------------------------------------------------------------------
bool CAI_PassengerBehaviorZombie::AttachToVehicle( void )
{
	// Must be able to enter the vehicle
	if ( m_hVehicle->NPC_CanEnterVehicle( GetOuter(), false ) == false )
		return false;

	// Reserve the seat
	if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
		return false;

	// Use the best one we've found
	int nSequence = FindEntrySequence();
	if ( nSequence == -1 )
		return false;

	// Take the transition sequence
	SetTransitionSequence( nSequence );

	// Get in the vehicle
	EnterVehicle();

	// Start our scripted sequence with any other passengers
	// Find Alyx
	// TODO: Iterate through the list of passengers in the vehicle and find one we can interact with
	CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
	if ( pAlyx )
	{
		// Tell Alyx to play along!
		pAlyx->ForceVehicleInteraction( GetOuter()->GetSequenceName( nSequence ), GetOuter() );
	}

	return true;
}

AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehaviorZombie )
{
	DECLARE_ACTIVITY( ACT_PASSENGER_MELEE_ATTACK1 )
	DECLARE_ACTIVITY( ACT_PASSENGER_THREATEN )
	DECLARE_ACTIVITY( ACT_PASSENGER_FLINCH )
	DECLARE_ACTIVITY( ACT_PASSENGER_ZOMBIE_LEAP_LOOP )

	DECLARE_TASK( TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 )
	DECLARE_TASK( TASK_PASSENGER_ZOMBIE_DISMOUNT )
	DECLARE_TASK( TASK_PASSENGER_ZOMBIE_ATTACH )

	DECLARE_CONDITION( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE )

	DEFINE_SCHEDULE
		(
		SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE,

		"	Tasks"
		"		TASK_PASSENGER_ATTACH_TO_VEHICLE	0"
		"		TASK_PASSENGER_ENTER_VEHICLE		0"
		""
		"	Interrupts"
		)

	DEFINE_SCHEDULE
		(
		SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE		SCHEDULE:SCHED_PASSENGER_IDLE"
		"		TASK_STOP_MOVING			0"
		"		TASK_PASSENGER_ZOMBIE_DISMOUNT	0"
		""
		"	Interrupts"
		"		COND_TASK_FAILED"
		)

	DEFINE_SCHEDULE
		(
		SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1,

		"	Tasks"
		"		TASK_ANNOUNCE_ATTACK	1"
		"		TASK_MELEE_ATTACK1		0"
		""
		"	Interrupts"
		)
	
	DEFINE_SCHEDULE
		(
		SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1,

		"	Tasks"
		"		TASK_PLAY_SEQUENCE				ACTIVITY:ACT_PASSENGER_RANGE_ATTACK1"
		"		TASK_SET_ACTIVITY				ACTIVITY:ACT_PASSENGER_ZOMBIE_LEAP_LOOP"
		"		TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1	0"
		"	"
		"	Interrupts"
		)

	DEFINE_SCHEDULE
		(
		SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
		"		TASK_GET_CHASE_PATH_TO_ENEMY	2400"
		"		TASK_RUN_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_ENEMY_UNREACHABLE"
		"		COND_TASK_FAILED"
		"		COND_LOST_ENEMY"
		"		COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE"
		)

	DEFINE_SCHEDULE
		(
		SCHED_PASSENGER_ZOMBIE_ATTACH,

		"	Tasks"
		"		TASK_PASSENGER_ZOMBIE_ATTACH	0"
		""
		"	Interrupts"
		)

		AI_END_CUSTOM_SCHEDULE_PROVIDER()
}
