summaryrefslogtreecommitdiff
path: root/game/server/episodic/ai_behavior_passenger_zombie.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/episodic/ai_behavior_passenger_zombie.cpp')
-rw-r--r--game/server/episodic/ai_behavior_passenger_zombie.cpp878
1 files changed, 878 insertions, 0 deletions
diff --git a/game/server/episodic/ai_behavior_passenger_zombie.cpp b/game/server/episodic/ai_behavior_passenger_zombie.cpp
new file mode 100644
index 0000000..0667530
--- /dev/null
+++ b/game/server/episodic/ai_behavior_passenger_zombie.cpp
@@ -0,0 +1,878 @@
+//========= Copyright 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_flLastVerticalLean( 0.0f ),
+m_flLastLateralLean( 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()
+}