//========= 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() }