diff options
Diffstat (limited to 'game/server/episodic/ai_behavior_passenger_companion.cpp')
| -rw-r--r-- | game/server/episodic/ai_behavior_passenger_companion.cpp | 2053 |
1 files changed, 2053 insertions, 0 deletions
diff --git a/game/server/episodic/ai_behavior_passenger_companion.cpp b/game/server/episodic/ai_behavior_passenger_companion.cpp new file mode 100644 index 0000000..f96c2a5 --- /dev/null +++ b/game/server/episodic/ai_behavior_passenger_companion.cpp @@ -0,0 +1,2053 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Companion NPCs riding in cars +// +//============================================================================= + +#include "cbase.h" +#include "ai_speech.h" +#include "ai_pathfinder.h" +#include "ai_waypoint.h" +#include "ai_navigator.h" +#include "ai_navgoaltype.h" +#include "ai_memory.h" +#include "ai_behavior_passenger_companion.h" +#include "ai_squadslot.h" +#include "npc_playercompanion.h" +#include "ai_route.h" +#include "saverestore_utlvector.h" +#include "cplane.h" +#include "util_shared.h" +#include "sceneentity.h" + +bool SphereWithinPlayerFOV( CBasePlayer *pPlayer, const Vector &vecCenter, float flRadius ); + +#define PASSENGER_NEAR_VEHICLE_THRESHOLD 64.0f + +#define MIN_OVERTURNED_DURATION 1.0f // seconds +#define MIN_FAILED_EXIT_ATTEMPTS 4 +#define MIN_OVERTURNED_WARN_DURATION 4.0f // seconds + +ConVar passenger_collision_response_threshold( "passenger_collision_response_threshold", "250.0" ); +ConVar passenger_debug_entry( "passenger_debug_entry", "0" ); +ConVar passenger_use_leaning("passenger_use_leaning", "1" ); +extern ConVar passenger_debug_transition; + +// Custom activities +Activity ACT_PASSENGER_IDLE_AIM; +Activity ACT_PASSENGER_RELOAD; +Activity ACT_PASSENGER_OVERTURNED; +Activity ACT_PASSENGER_IMPACT; +Activity ACT_PASSENGER_IMPACT_WEAPON; +Activity ACT_PASSENGER_POINT; +Activity ACT_PASSENGER_POINT_BEHIND; +Activity ACT_PASSENGER_IDLE_READY; +Activity ACT_PASSENGER_GESTURE_JOSTLE_LARGE; +Activity ACT_PASSENGER_GESTURE_JOSTLE_SMALL; +Activity ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED; +Activity ACT_PASSENGER_GESTURE_JOSTLE_SMALL_STIMULATED; +Activity ACT_PASSENGER_COWER_IN; +Activity ACT_PASSENGER_COWER_LOOP; +Activity ACT_PASSENGER_COWER_OUT; +Activity ACT_PASSENGER_IDLE_FIDGET; + +BEGIN_DATADESC( CAI_PassengerBehaviorCompanion ) + + DEFINE_EMBEDDED( m_VehicleMonitor ), + + DEFINE_UTLVECTOR( m_FailedEntryPositions, FIELD_EMBEDDED ), + + DEFINE_FIELD( m_flOverturnedDuration, FIELD_FLOAT ), + DEFINE_FIELD( m_flUnseenDuration, FIELD_FLOAT ), + DEFINE_FIELD( m_nExitAttempts, FIELD_INTEGER ), + DEFINE_FIELD( m_flNextOverturnWarning, FIELD_TIME ), + DEFINE_FIELD( m_flEnterBeginTime, FIELD_TIME ), + DEFINE_FIELD( m_hCompanion, FIELD_EHANDLE ), + DEFINE_FIELD( m_flNextJostleTime, FIELD_TIME ), + DEFINE_FIELD( m_nVisibleEnemies, FIELD_INTEGER ), + DEFINE_FIELD( m_flLastLateralLean, FIELD_FLOAT ), + DEFINE_FIELD( m_flEntraceUpdateTime, FIELD_TIME ), + DEFINE_FIELD( m_flNextEnterAttempt, FIELD_TIME ), + DEFINE_FIELD( m_flNextFidgetTime, FIELD_TIME ), + +END_DATADESC(); + +BEGIN_SIMPLE_DATADESC( FailPosition_t ) + + DEFINE_FIELD( vecPosition, FIELD_VECTOR ), + DEFINE_FIELD( flTime, FIELD_TIME ), + +END_DATADESC(); + +CAI_PassengerBehaviorCompanion::CAI_PassengerBehaviorCompanion( void ) : +m_flUnseenDuration( 0.0f ), +m_flNextOverturnWarning( 0.0f ), +m_flOverturnedDuration( 0.0f ), +m_nExitAttempts( 0 ), +m_flNextEnterAttempt( 0.0f ), +m_flLastLateralLean( 0.0f ), +m_flNextJostleTime( 0.0f ) +{ + memset( &m_vehicleState, 0, sizeof( m_vehicleState ) ); + m_VehicleMonitor.ClearMark(); +} + +void CAI_PassengerBehaviorCompanion::Enable( CPropJeepEpisodic *pVehicle, bool bImmediateEnter /*= false*/ ) +{ + BaseClass::Enable( pVehicle ); + + // Store this up for quick reference later on + m_hCompanion = dynamic_cast<CNPC_PlayerCompanion *>(GetOuter()); + + // See if we want to sit in the vehicle immediately + if ( bImmediateEnter ) + { + // Find the seat and sit in it + if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) ) + { + // Attach + AttachToVehicle(); + + // This will slam us into the right position and clean up + FinishEnterVehicle(); + GetOuter()->IncrementInterpolationFrame(); + + // Start our schedule immediately + ClearSchedule( "Immediate entry to vehicle" ); + } + } +} + +//----------------------------------------------------------------------------- +// Set up the shot regulator based on the equipped weapon +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::OnUpdateShotRegulator( void ) +{ + if ( GetVehicleSpeed() > 250 ) + { + // Default values + GetOuter()->GetShotRegulator()->SetBurstInterval( 0.1f, 0.5f ); + GetOuter()->GetShotRegulator()->SetBurstShotCountRange( 1, 4 ); + GetOuter()->GetShotRegulator()->SetRestInterval( 0.25f, 1.0f ); + } + else + { + BaseClass::OnUpdateShotRegulator(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::IsValidEnemy( CBaseEntity *pEntity ) +{ + // The target must be much closer in the vehicle + float flDistSqr = ( pEntity->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); + if ( flDistSqr > Square( (40*12) ) && pEntity->Classify() != CLASS_BULLSEYE ) + return false; + + // Determine if the target is going to move past us? + return BaseClass::IsValidEnemy( pEntity ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the speed the vehicle is moving at +// Output : units per second +//----------------------------------------------------------------------------- +float CAI_PassengerBehaviorCompanion::GetVehicleSpeed( void ) +{ + if ( m_hVehicle == NULL ) + { + Assert(0); + return -1.0f; + } + + Vector vecVelocity; + m_hVehicle->GetVelocity( &vecVelocity, NULL ); + + // Get our speed + return vecVelocity.Length(); +} + +//----------------------------------------------------------------------------- +// Purpose: Detect oncoming collisions +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::GatherVehicleCollisionConditions( const Vector &localVelocity ) +{ + // Look for walls in front of us + if ( localVelocity.y > passenger_collision_response_threshold.GetFloat() ) + { + // Detect an upcoming collision + Vector vForward; + m_hVehicle->GetVectors( &vForward, NULL, NULL ); + + // Use a smaller bounding box to make it detect mostly head-on impacts + Vector mins, maxs; + mins.Init( -24, -24, 32 ); + maxs.Init( 24, 24, 64 ); + + float dt = 0.6f; // Seconds + float distance = localVelocity.y * dt; + + // Find our angular velocity as a vector + Vector vecAngularVelocity; + vecAngularVelocity.z = 0.0f; + SinCos( DEG2RAD( m_vehicleState.m_vecLastAngles.z * dt ), &vecAngularVelocity.y, &vecAngularVelocity.x ); + + Vector vecOffset; + VectorRotate( vecAngularVelocity, m_hVehicle->GetAbsAngles() + QAngle( 0, 90, 0 ), vecOffset ); + + vForward += vecOffset; + VectorNormalize( vForward ); + + // Trace ahead of us to see what's there + CTraceFilterNoNPCsOrPlayer filter( m_hVehicle, COLLISION_GROUP_NONE ); // We don't care about NPCs or the player (certainly if they're in the vehicle!) + + trace_t tr; + UTIL_TraceHull( m_hVehicle->GetAbsOrigin(), m_hVehicle->GetAbsOrigin() + ( vForward * distance ), mins, maxs, MASK_SOLID, &filter, &tr ); + + bool bWarnCollision = true; + if ( tr.DidHit() ) + { + // We need to see how "head-on" to the surface we are + float impactDot = DotProduct( tr.plane.normal, vForward ); + + // Don't warn over grazing blows or slopes + if ( impactDot < -0.9f && tr.plane.normal.z < 0.75f ) + { + // Make sure this is a worthwhile thing to warn about + if ( tr.m_pEnt ) + { + // If it's physical and moveable, then ignore it because we'll probably smash or move it + IPhysicsObject *pObject = tr.m_pEnt->VPhysicsGetObject(); + if ( pObject && pObject->IsMoveable() ) + { + bWarnCollision = false; + } + } + + // Note that we should say something to the player about it + if ( bWarnCollision ) + { + SetCondition( COND_PASSENGER_WARN_COLLISION ); + } + } + } + } + + if ( passenger_use_leaning.GetBool() ) + { + // Calculate how our body is leaning + CalculateBodyLean(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Speak various lines about the state of the vehicle +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::SpeakVehicleConditions( void ) +{ + Assert( m_hVehicle != NULL ); + if ( m_hVehicle == NULL ) + return; + + // Speak if we just hit something + if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) ) + { + SpeakIfAllowed( TLK_PASSENGER_IMPACT ); + } + + // Speak if we're overturned + if ( HasCondition( COND_PASSENGER_OVERTURNED ) ) + { + SpeakIfAllowed( TLK_PASSENGER_OVERTURNED ); + } + + // Speak if we're about to hit something + if ( HasCondition( COND_PASSENGER_WARN_COLLISION ) ) + { + // Make Alyx look at the impending impact + Vector vecForward; + m_hVehicle->GetVectors( &vecForward, NULL, NULL ); + Vector vecLookPos = m_hVehicle->WorldSpaceCenter() + ( vecForward * 64.0f ); + GetOuter()->AddLookTarget( vecLookPos, 1.0f, 1.0f ); + + SpeakIfAllowed( TLK_PASSENGER_WARN_COLLISION ); + ClearCondition( COND_PASSENGER_WARN_COLLISION ); + } + + // Speak if the player is driving like a madman + if ( HasCondition( COND_PASSENGER_ERRATIC_DRIVING ) ) + { + SpeakIfAllowed( TLK_PASSENGER_ERRATIC_DRIVING ); + } + + // The vehicle has come to a halt + if ( HasCondition( COND_PASSENGER_VEHICLE_STOPPED ) ) + { + float flDist = ( WorldSpaceCenter() - m_hVehicle->WorldSpaceCenter() ).Length(); + CFmtStrN<128> modifiers( "vehicle_distance:%f", flDist ); + SpeakIfAllowed( TLK_PASSENGER_VEHICLE_STOPPED, modifiers ); + } + + // The vehicle has started to move + if ( HasCondition( COND_PASSENGER_VEHICLE_STARTED ) ) + { + float flDist = ( WorldSpaceCenter() - m_hVehicle->WorldSpaceCenter() ).Length(); + CFmtStrN<128> modifiers( "vehicle_distance:%f", flDist ); + SpeakIfAllowed( TLK_PASSENGER_VEHICLE_STARTED, modifiers ); + } + + // Player got in + if ( HasCondition( COND_PASSENGER_PLAYER_EXITED_VEHICLE ) ) + { + CPropJeepEpisodic *pJalopy = dynamic_cast<CPropJeepEpisodic*>(m_hVehicle.Get()); + if( pJalopy != NULL && pJalopy->NumRadarContacts() > 0 ) + { + SpeakIfAllowed( TLK_PASSENGER_PLAYER_EXITED, "radar_has_targets" ); + } + else + { + SpeakIfAllowed( TLK_PASSENGER_PLAYER_EXITED ); + } + } + + // Player got out + if ( HasCondition( COND_PASSENGER_PLAYER_ENTERED_VEHICLE ) ) + { + SpeakIfAllowed( TLK_PASSENGER_PLAYER_ENTERED ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Whether or not we should jostle at this moment +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::CanPlayJostle( bool bLargeJostle ) +{ + // We've been told to suppress the jostle + if ( m_flNextJostleTime > gpGlobals->curtime ) + return false; + + // Can't do this if we're at a high readiness level + if ( m_hCompanion && m_hCompanion->ShouldBeAiming() ) + return false; + + // Can't do this when we're upside-down + if ( HasCondition( COND_PASSENGER_OVERTURNED ) ) + return false; + + // Allow our normal impact code to handle this one instead + if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) || IsCurSchedule( SCHED_PASSENGER_IMPACT ) ) + return false; + + if ( bLargeJostle ) + { + // Don't bother under certain circumstances + if ( IsCurSchedule( SCHED_PASSENGER_COWER ) || + IsCurSchedule( SCHED_PASSENGER_FIDGET ) ) + return false; + } + else + { + // Don't interrupt a larger gesture + if ( GetOuter()->IsPlayingGesture( ACT_PASSENGER_GESTURE_JOSTLE_LARGE ) || GetOuter()->IsPlayingGesture( ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED ) ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Gather conditions we can comment on or react to while riding in the vehicle +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::GatherVehicleStateConditions( void ) +{ + // Gather the base class + BaseClass::GatherVehicleStateConditions(); + + // See if we're going to collide with anything soon + GatherVehicleCollisionConditions( m_vehicleState.m_vecLastLocalVelocity ); + + // Say anything we're meant to through the response rules + SpeakVehicleConditions(); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles exit failure notifications +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::OnExitVehicleFailed( void ) +{ + m_nExitAttempts++; +} + +//----------------------------------------------------------------------------- +// Purpose: Track how long we've been overturned +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::UpdateStuckStatus( void ) +{ + if ( m_hVehicle == NULL ) + return; + + // Always clear this to start out with + ClearCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ); + + // If we can't exit the vehicle, then don't bother with these checks + if ( m_hVehicle->NPC_CanExitVehicle( GetOuter(), true ) == false ) + return; + + bool bVisibleToPlayer = false; + bool bPlayerInVehicle = false; + + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if ( pPlayer ) + { + bVisibleToPlayer = pPlayer->FInViewCone( GetOuter()->GetAbsOrigin() ); + bPlayerInVehicle = pPlayer->IsInAVehicle(); + } + + // If we're not overturned, just reset our counter + if ( m_vehicleState.m_bWasOverturned == false ) + { + m_flOverturnedDuration = 0.0f; + m_flUnseenDuration = 0.0f; + } + else + { + // Add up the time since we last checked + m_flOverturnedDuration += ( gpGlobals->curtime - GetLastThink() ); + } + + // Warn about being stuck upside-down if it's been long enough + if ( m_flOverturnedDuration > MIN_OVERTURNED_WARN_DURATION && m_flNextOverturnWarning < gpGlobals->curtime ) + { + SetCondition( COND_PASSENGER_WARN_OVERTURNED ); + } + + // If the player can see us or is still in the vehicle, we never exit + if ( bVisibleToPlayer || bPlayerInVehicle ) + { + // Reset our timer + m_flUnseenDuration = 0.0f; + return; + } + + // Add up the time since we last checked + m_flUnseenDuration += ( gpGlobals->curtime - GetLastThink() ); + + // If we've been overturned for long enough or tried to exit one too many times + if ( m_vehicleState.m_bWasOverturned ) + { + if ( m_flUnseenDuration > MIN_OVERTURNED_DURATION ) + { + SetCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ); + } + } + else if ( m_nExitAttempts >= MIN_FAILED_EXIT_ATTEMPTS ) + { + // The player can't be looking at us + SetCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gather conditions for our use in making decisions +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::GatherConditions( void ) +{ + // Code below relies on these conditions being set first! + BaseClass::GatherConditions(); + + // We're not enabled + if ( IsEnabled() == false ) + return; + + // In-car conditions + if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) + { + // If we're jostling, then note that + if ( HasCondition( COND_PASSENGER_ERRATIC_DRIVING ) ) + { + if ( CanPlayJostle( true ) ) + { + // Add the gesture to be played. If it's already playing, the underlying function will simply opt-out + int nSequence = GetOuter()->AddGesture( GetOuter()->NPC_TranslateActivity( ACT_PASSENGER_GESTURE_JOSTLE_LARGE ), true ); + + GetOuter()->SetNextAttack( gpGlobals->curtime + ( GetOuter()->SequenceDuration( nSequence ) * 2.0f ) ); + GetOuter()->GetShotRegulator()->FireNoEarlierThan( GetOuter()->GetNextAttack() ); + + // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain + ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) ); + } + } + else if ( HasCondition( COND_PASSENGER_JOSTLE_SMALL ) ) + { + if ( CanPlayJostle( false ) ) + { + // Add the gesture to be played. If it's already playing, the underlying function will simply opt-out + GetOuter()->AddGesture( GetOuter()->NPC_TranslateActivity( ACT_PASSENGER_GESTURE_JOSTLE_SMALL ), true ); + + // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain + ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) ); + } + } + + // See if we're upside-down + UpdateStuckStatus(); + + // See if we're able to fidget + if ( CanFidget() ) + { + SetCondition( COND_PASSENGER_CAN_FIDGET ); + } + } + + // Clear this out + ClearCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY ); + + // Make sure a vehicle doesn't stray from its mark + if ( IsCurSchedule( SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE ) ) + { + if ( m_VehicleMonitor.TargetMoved( m_hVehicle ) ) + { + SetCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK ); + } + + // If we can get in the car right away, set us up to do so + int nNearestSequence; + if ( CanEnterVehicleImmediately( &nNearestSequence, &m_vecTargetPosition, &m_vecTargetAngles ) ) + { + SetTransitionSequence( nNearestSequence ); + SetCondition( COND_PASSENGER_ENTERING ); + SetCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY ); + } + } + + // Clear the number for now + m_nVisibleEnemies = 0; + + AIEnemiesIter_t iter; + for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) + { + if( GetOuter()->IRelationType( pEMemory->hEnemy ) == D_HT ) + { + if( pEMemory->timeLastSeen == gpGlobals->curtime ) + { + m_nVisibleEnemies++; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::AimGun( void ) +{ + // If there is no aiming target, return to center + if ( GetEnemy() == NULL ) + { + GetOuter()->RelaxAim(); + return; + } + + // Otherwise try and shoot down the barrel + Vector vecForward, vecRight, vecUp; + GetOuter()->GetVectors( &vecForward, &vecRight, &vecUp ); + Vector vecTorso = GetAbsOrigin() + ( vecUp * 48.0f ); + + Vector vecShootDir = GetOuter()->GetShootEnemyDir( vecTorso, false ); + + Vector vecDirToEnemy = GetEnemy()->GetAbsOrigin() - vecTorso; + VectorNormalize( vecDirToEnemy ); + + bool bRightSide = ( DotProduct( vecDirToEnemy, vecRight ) > 0.0f ); + float flTargetDot = ( bRightSide ) ? -0.7f : 0.0f; + + if ( DotProduct( vecForward, vecDirToEnemy ) <= flTargetDot ) + { + // Don't aim at something that's outside our reach + GetOuter()->RelaxAim(); + } + else + { + // Aim at it + GetOuter()->SetAim( vecShootDir ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Allow us to deny selecting a schedule if we're not in a state to do so +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::CanSelectSchedule( void ) +{ + if ( BaseClass::CanSelectSchedule() == false ) + return false; + + // We're in a period where we're allowing our base class to override us + if ( m_flNextEnterAttempt > gpGlobals->curtime ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Deal with enter/exit of the vehicle +//----------------------------------------------------------------------------- +int CAI_PassengerBehaviorCompanion::SelectTransitionSchedule( void ) +{ + // Attempt to instantly enter the vehicle + if ( HasCondition( COND_PASSENGER_CAN_ENTER_IMMEDIATELY ) ) + { + // Snap to position and begin to animate into the seat + EnterVehicleImmediately(); + return SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY; + } + + // Entering schedule + if ( HasCondition( COND_PASSENGER_ENTERING ) || m_PassengerIntent == PASSENGER_INTENT_ENTER ) + { + if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) + { + ClearCondition( COND_PASSENGER_ENTERING ); + m_PassengerIntent = PASSENGER_INTENT_NONE; + return SCHED_NONE; + } + + // Don't attempt to enter for a period of time + if ( m_flNextEnterAttempt > gpGlobals->curtime ) + return SCHED_NONE; + + ClearCondition( COND_PASSENGER_ENTERING ); + + // Failing that, run to the right place + return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE; + } + + return BaseClass::SelectTransitionSchedule(); +} + +//----------------------------------------------------------------------------- +// Purpose: Select schedules when we're riding in the car +//----------------------------------------------------------------------------- +int CAI_PassengerBehaviorCompanion::SelectScheduleInsideVehicle( void ) +{ + // Overturned + if ( HasCondition( COND_PASSENGER_OVERTURNED ) ) + return SCHED_PASSENGER_OVERTURNED; + + if ( HasCondition( COND_PASSENGER_HARD_IMPACT ) ) + { + // Push out our fidget into the future so that we don't act unnaturally over bumpy terrain + ExtendFidgetDelay( random->RandomFloat( 1.5f, 3.0f ) ); + m_flNextJostleTime = gpGlobals->curtime + random->RandomFloat( 2.5f, 4.0f ); + return SCHED_PASSENGER_IMPACT; + } + + // Look for exiting the vehicle + if ( HasCondition( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ) ) + return SCHED_PASSENGER_EXIT_STUCK_VEHICLE; + + // Cower if we're about to get nailed + if ( HasCondition( COND_HEAR_DANGER ) && IsCurSchedule( SCHED_PASSENGER_COWER ) == false ) + { + SpeakIfAllowed( TLK_DANGER ); + return SCHED_PASSENGER_COWER; + } + + // Fire on targets + if ( GetEnemy() ) + { + // Limit how long we'll keep an enemy if there are many on screen + if ( HasCondition( COND_NEW_ENEMY ) && m_nVisibleEnemies > 1 ) + { + GetEnemies()->SetTimeValidEnemy( GetEnemy(), random->RandomFloat( 0.5f, 1.0f ) ); + } + + // Always face + GetOuter()->AddLookTarget( GetEnemy(), 1.0f, 2.0f ); + + if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && ( GetOuter()->GetShotRegulator()->IsInRestInterval() == false ) ) + return SCHED_PASSENGER_RANGE_ATTACK1; + } + + // Reload when we have the chance + if ( HasCondition( COND_LOW_PRIMARY_AMMO ) && HasCondition( COND_SEE_ENEMY ) == false ) + return SCHED_PASSENGER_RELOAD; + + // Say an overturned line + if ( HasCondition( COND_PASSENGER_WARN_OVERTURNED ) ) + { + SpeakIfAllowed( TLK_PASSENGER_REQUEST_UPRIGHT ); + m_flNextOverturnWarning = gpGlobals->curtime + random->RandomFloat( 5.0f, 10.0f ); + ClearCondition( COND_PASSENGER_WARN_OVERTURNED ); + } + + // Should we fidget? + if ( HasCondition( COND_PASSENGER_CAN_FIDGET ) ) + { + ExtendFidgetDelay( random->RandomFloat( 6.0f, 12.0f ) ); + return SCHED_PASSENGER_FIDGET; + } + + return SCHED_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: Select schedules while we're outside the car +//----------------------------------------------------------------------------- +int CAI_PassengerBehaviorCompanion::SelectScheduleOutsideVehicle( void ) +{ + // FIXME: How can we get in here? + Assert( m_hVehicle ); + if ( m_hVehicle == NULL ) + return SCHED_NONE; + + // Handle our mark moving + if ( HasCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK ) ) + { + // Reset our mark + m_VehicleMonitor.SetMark( m_hVehicle, 36.0f ); + ClearCondition( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK ); + } + + // If we want to get in, the try to do so + if ( m_PassengerIntent == PASSENGER_INTENT_ENTER ) + { + // If we're not attempting to enter the vehicle again, just fall to the base class + if ( m_flNextEnterAttempt > gpGlobals->curtime ) + return BaseClass::SelectSchedule(); + + // Otherwise try and enter thec car + return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE; + } + + // This means that we're outside the vehicle with no intent to enter, which should have disabled us! + Disable(); + Assert( 0 ); + + return SCHED_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +// &vecCenter - +// flRadius - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool SphereWithinPlayerFOV( CBasePlayer *pPlayer, const Vector &vecCenter, float flRadius ) +{ + // TODO: For safety sake, we might want to do a more fully qualified test against the frustum using the bbox + + // If the player can see us, then we can't enter immediately anyway + if ( pPlayer == NULL ) + return false; + + // Find the length to the point + Vector los = ( vecCenter - pPlayer->EyePosition() ); + float flLength = VectorNormalize( los ); + + // Get the player's forward direction + Vector vecPlayerForward; + pPlayer->EyeVectors( &vecPlayerForward, NULL, NULL ); + + // This is the additional number of degrees to add to account for our distance + float flArcAddition = atan2( flRadius, flLength ); + + // Find if the sphere is within our FOV + float flDot = DotProduct( los, vecPlayerForward ); + float flPlayerFOV = cos( DEG2RAD( pPlayer->GetFOV() / 2.0f ) ); + + return ( flDot > (flPlayerFOV-flArcAddition) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::CanEnterVehicleImmediately( int *pResultSequence, Vector *pResultPos, QAngle *pResultAngles ) +{ + // Must wait a short time before trying to do this (otherwise we stack up on the player!) + if ( ( gpGlobals->curtime - m_flEnterBeginTime ) < 0.5f ) + return false; + + // Vehicle can't be moving too quickly + if ( GetVehicleSpeed() > 150 ) + return false; + + // If the player can see us, then we can't enter immediately anyway + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if ( pPlayer == NULL ) + return false; + + Vector vecPosition = GetOuter()->WorldSpaceCenter(); + float flRadius = GetOuter()->CollisionProp()->BoundingRadius2D(); + if ( SphereWithinPlayerFOV( pPlayer, vecPosition, flRadius ) ) + return false; + + // Reserve an entry point + if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false ) + return 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; + QAngle vecStartAngles; + if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecEndPos, NULL ) == false ) + return -1; + + // Categorize the passenger in terms of being on the left or right side of the vehicle + Vector vecRight; + m_hVehicle->GetVectors( NULL, &vecRight, NULL ); + + CPlane lateralPlane; + lateralPlane.InitializePlane( vecRight, m_hVehicle->WorldSpaceCenter() ); + + bool bPlaneSide = lateralPlane.PointInFront( GetOuter()->GetAbsOrigin() ); + + Vector vecPassengerOffset = ( GetOuter()->WorldSpaceCenter() - GetOuter()->GetAbsOrigin() ); + + const CPassengerSeatTransition *pTransition; + float flNearestDistSqr = FLT_MAX; + float flSeatDistSqr; + int nNearestSequence = -1; + int nSequence; + Vector vecNearestPos; + QAngle vecNearestAngles; + + // 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, &vecStartAngles ) == false ) + continue; + + // See if the passenger would be visible if standing at this position + if ( SphereWithinPlayerFOV( pPlayer, (vecStartPos+vecPassengerOffset), flRadius ) ) + continue; + + // Otherwise distance is the deciding factor + flSeatDistSqr = ( vecStartPos - GetOuter()->GetAbsOrigin() ).LengthSqr(); + + // We must be within a certain distance to the vehicle + if ( flSeatDistSqr > Square( 25*12 ) ) + continue; + + // We cannot cross between the plane which splits the vehicle laterally in half down the middle + // This avoids cases where the character magically ends up on one side of the vehicle after they were + // clearly just on the other side. + if ( lateralPlane.PointInFront( vecStartPos ) != bPlaneSide ) + continue; + + // Closer, take it + if ( flSeatDistSqr < flNearestDistSqr ) + { + flNearestDistSqr = flSeatDistSqr; + nNearestSequence = nSequence; + vecNearestPos = vecStartPos; + vecNearestAngles = vecStartAngles; + } + } + + // Fail if we didn't find anything + if ( nNearestSequence == -1 ) + return false; + + // Return the results + if ( pResultSequence ) + { + *pResultSequence = nNearestSequence; + } + + if ( pResultPos ) + { + *pResultPos = vecNearestPos; + } + + if ( pResultAngles ) + { + *pResultAngles = vecNearestAngles; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Put us into the vehicle immediately +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::EnterVehicleImmediately( void ) +{ + // Now play the animation + GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE ); + GetOuter()->GetNavigator()->ClearGoal(); + + // Put us there and get going (no interpolation!) + GetOuter()->Teleport( &m_vecTargetPosition, &m_vecTargetAngles, &vec3_origin ); + GetOuter()->IncrementInterpolationFrame(); +} + +//----------------------------------------------------------------------------- +// Purpose: Overrides the schedule selection +// Output : int - Schedule to play +//----------------------------------------------------------------------------- +int CAI_PassengerBehaviorCompanion::SelectSchedule( void ) +{ + // First, keep track of our transition state (enter/exit) + int nSched = SelectTransitionSchedule(); + if ( nSched != SCHED_NONE ) + return nSched; + + // Handle schedules based on our passenger state + if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE ) + { + nSched = SelectScheduleOutsideVehicle(); + if ( nSched != SCHED_NONE ) + return nSched; + } + else if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) + { + nSched = SelectScheduleInsideVehicle(); + if ( nSched != SCHED_NONE ) + return nSched; + } + + return BaseClass::SelectSchedule(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CAI_PassengerBehaviorCompanion::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) +{ + switch( failedTask ) + { + case TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT: + { + // This is not allowed! + if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE ) + { + Assert( 0 ); + return SCHED_FAIL; + } + + // If we're not close enough, then get nearer the target + if ( UTIL_DistApprox( m_hVehicle->GetAbsOrigin(), GetOuter()->GetAbsOrigin() ) > PASSENGER_NEAR_VEHICLE_THRESHOLD ) + return SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED; + } + + // Fall through + + case TASK_GET_PATH_TO_NEAR_VEHICLE: + m_flNextEnterAttempt = gpGlobals->curtime + 3.0f; + break; + } + + return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode ); +} + +//----------------------------------------------------------------------------- +// Purpose: Start to enter the vehicle +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::EnterVehicle( void ) +{ + BaseClass::EnterVehicle(); + + m_nExitAttempts = 0; + m_VehicleMonitor.SetMark( m_hVehicle, 8.0f ); + m_flEnterBeginTime = gpGlobals->curtime; + + // Remove this flag because we're sitting so close we always think we're going to hit the player + // FIXME: We need to store this state so we don't incorrectly restore it later + GetOuter()->CapabilitiesRemove( bits_CAP_NO_HIT_PLAYER ); + + // Discard enemies quickly + GetOuter()->GetEnemies()->SetEnemyDiscardTime( 2.0f ); + + SpeakIfAllowed( TLK_PASSENGER_BEGIN_ENTRANCE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::FinishEnterVehicle( void ) +{ + BaseClass::FinishEnterVehicle(); + + // We succeeded + ResetVehicleEntryFailedState(); + + // Push this out into the future so we don't always fidget immediately in the vehicle + ExtendFidgetDelay( random->RandomFloat( 4.0, 15.0f ) ); + + SpeakIfAllowed( TLK_PASSENGER_FINISH_ENTRANCE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::ExitVehicle( void ) +{ + BaseClass::ExitVehicle(); + + SpeakIfAllowed( TLK_PASSENGER_BEGIN_EXIT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Vehicle has been completely exited +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::FinishExitVehicle( void ) +{ + BaseClass::FinishExitVehicle(); + + m_nExitAttempts = 0; + m_VehicleMonitor.ClearMark(); + + // FIXME: We need to store this state so we don't incorrectly restore it later + GetOuter()->CapabilitiesAdd( bits_CAP_NO_HIT_PLAYER ); + + // FIXME: Restore this properly + GetOuter()->GetEnemies()->SetEnemyDiscardTime( AI_DEF_ENEMY_DISCARD_TIME ); + + SpeakIfAllowed( TLK_PASSENGER_FINISH_EXIT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Tries to build a route to the entry point of the target vehicle. +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::FindPathToVehicleEntryPoint( void ) +{ + // Set our custom move name + // bool bFindNearest = ( GetOuter()->m_NPCState == NPC_STATE_COMBAT || GetOuter()->m_NPCState == NPC_STATE_ALERT ); + bool bFindNearest = true; // For the sake of quick gameplay, just make Alyx move directly! + int nSequence = FindEntrySequence( bFindNearest ); + if ( nSequence == -1 ) + return false; + + // We have to do this specially because the activities are not named + SetTransitionSequence( nSequence ); + + // Get the entry position + Vector vecEntryPoint; + QAngle vecEntryAngles; + if ( GetEntryPoint( m_nTransitionSequence, &vecEntryPoint, &vecEntryAngles ) == false ) + { + MarkVehicleEntryFailed( vecEntryPoint ); + return false; + } + + // If we're already close enough, just succeed + float flDistToGoalSqr = ( GetOuter()->GetAbsOrigin() - vecEntryPoint ).LengthSqr(); + if ( flDistToGoalSqr < Square(3*12) ) + return true; + + // Setup our goal + AI_NavGoal_t goal( GOALTYPE_LOCATION ); + // goal.arrivalActivity = ACT_SCRIPT_CUSTOM_MOVE; + goal.dest = vecEntryPoint; + + // See if we need a radial route around the car, to our goal + if ( UseRadialRouteToEntryPoint( vecEntryPoint ) ) + { + // Find the bounding radius of the vehicle + Vector vecCenterPoint = m_hVehicle->WorldSpaceCenter(); + vecCenterPoint.z = vecEntryPoint.z; + bool bClockwise; + float flArc = GetArcToEntryPoint( vecCenterPoint, vecEntryPoint, bClockwise ); + float flRadius = m_hVehicle->CollisionProp()->BoundingRadius2D(); + + // Try and set a radial route + if ( GetOuter()->GetNavigator()->SetRadialGoal( vecEntryPoint, vecCenterPoint, flRadius, flArc, 64.0f, bClockwise ) == false ) + { + // Try the opposite way + flArc = 360.0f - flArc; + + // Try the opposite way around + if ( GetOuter()->GetNavigator()->SetRadialGoal( vecEntryPoint, vecCenterPoint, flRadius, flArc, 64.0f, !bClockwise ) == false ) + { + // Try and set a direct route as a last resort + if ( GetOuter()->GetNavigator()->SetGoal( goal ) == false ) + return false; + } + } + + // We found a goal + GetOuter()->GetNavigator()->SetArrivalDirection( vecEntryAngles ); + GetOuter()->GetNavigator()->SetArrivalSpeed( 64.0f ); + return true; + } + else + { + // Try and set a direct route + if ( GetOuter()->GetNavigator()->SetGoal( goal ) ) + { + GetOuter()->GetNavigator()->SetArrivalDirection( vecEntryAngles ); + GetOuter()->GetNavigator()->SetArrivalSpeed( 64.0f ); + return true; + } + } + + // We failed, so remember it + MarkVehicleEntryFailed( vecEntryPoint ); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Tests the route and position to see if it's valid +// Input : &vecTestPos - position to test +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::CanExitAtPosition( const Vector &vecTestPos ) +{ + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if ( pPlayer == NULL ) + return false; + + // Can't be in our potential view + if ( pPlayer->FInViewCone( vecTestPos ) ) + return false; + + // NOTE: There's no reason to do this since this is only called from a node's reported position + // Find the exact ground at this position + //Vector vecGroundPos; + //if ( FindGroundAtPosition( vecTestPos, 16.0f, 64.0f, &vecGroundPos ) == false ) + // return false; + + // Get the ultimate position we'll end up at + Vector vecStartPos; + if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecStartPos, NULL ) == false ) + return false; + + // See if we can move from where we are to that position in space + if ( IsValidTransitionPoint( vecStartPos, vecTestPos ) == false ) + return false; + + // Trace down to the ground + // FIXME: This piece of code is redundant and happening in IsValidTransitionPoint() as well + /* + Vector vecGroundPos; + if ( FindGroundAtPosition( vecTestPos, GetOuter()->StepHeight(), 64.0f, &vecGroundPos ) == false ) + return false; + */ + + // Try and sweep a box through space and make sure it's clear of obstructions + /* + trace_t tr; + CTraceFilterVehicleTransition skipFilter( GetOuter(), m_hVehicle, COLLISION_GROUP_NONE ); + + // These are very approximated (and magical) numbers to allow passengers greater head room and leg room when transitioning + Vector vecMins = GetOuter()->GetHullMins() + Vector( 0, 0, GetOuter()->StepHeight()*2.0f ); // FIXME: + Vector vecMaxs = GetOuter()->GetHullMaxs() - Vector( 0, 0, GetOuter()->StepHeight() ); + + UTIL_TraceHull( GetOuter()->GetAbsOrigin(), vecGroundPos, vecMins, vecMaxs, MASK_NPCSOLID, &skipFilter, &tr ); + + // If we're blocked, we can't get out there + if ( tr.fraction < 1.0f || tr.allsolid || tr.startsolid ) + { + if ( passenger_debug_transition.GetBool() ) + { + NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), vecGroundPos, vecMins, GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 64, 2.0f ); + } + return false; + } + */ + + return true; +} + +#define NUM_EXIT_ITERATIONS 8 + +//----------------------------------------------------------------------------- +// Purpose: Find a position we can use to exit the vehicle via teleportation +// Input : *vecResult - safe place to exit to +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::GetStuckExitPos( Vector *vecResult ) +{ + // Get our right direction + Vector vecVehicleRight; + m_hVehicle->GetVectors( NULL, &vecVehicleRight, NULL ); + + // Get the vehicle's rough horizontal bounds + float flVehicleRadius = m_hVehicle->CollisionProp()->BoundingRadius2D(); + + // Use the vehicle's center as our hub + Vector vecCenter = m_hVehicle->WorldSpaceCenter(); + + // Angle whose tan is: y/x + float flCurAngle = atan2f( vecVehicleRight.y, vecVehicleRight.x ); + float flAngleIncr = (M_PI*2.0f)/(float)NUM_EXIT_ITERATIONS; + Vector vecTestPos; + + // Test a number of discrete exit routes + for ( int i = 0; i <= NUM_EXIT_ITERATIONS-1; i++ ) + { + // Get our position + SinCos( flCurAngle, &vecTestPos.y, &vecTestPos.x ); + vecTestPos.z = 0.0f; + vecTestPos *= flVehicleRadius; + vecTestPos += vecCenter; + + // Now find the nearest node and use that + int nNearNode = GetOuter()->GetPathfinder()->NearestNodeToPoint( vecTestPos ); + if ( nNearNode != NO_NODE ) + { + Vector vecNodePos = g_pBigAINet->GetNodePosition( GetOuter()->GetHullType(), nNearNode ); + + // Test the position + if ( CanExitAtPosition( vecNodePos ) ) + { + // Take the result + *vecResult = vecNodePos; + return true; + } + + // Move to the next iteration + flCurAngle += flAngleIncr; + } + } + + // None found + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Attempt to get out of an overturned vehicle when the player isn't looking +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::ExitStuckVehicle( void ) +{ + // Try and find an exit position + Vector vecExitPos; + if ( GetStuckExitPos( &vecExitPos ) == false ) + return false; + + // Detach from the parent + GetOuter()->SetParent( NULL ); + + // Do all necessary clean-up + FinishExitVehicle(); + + // Teleport to the destination + // TODO: Make sure that the player can't see this! + GetOuter()->Teleport( &vecExitPos, &vec3_angle, &vec3_origin ); + GetOuter()->IncrementInterpolationFrame(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::StartTask( const Task_t *pTask ) +{ + // We need to override these so we never face + if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) + { + if ( pTask->iTask == TASK_FACE_TARGET || + pTask->iTask == TASK_FACE_ENEMY || + pTask->iTask == TASK_FACE_IDEAL || + pTask->iTask == TASK_FACE_HINTNODE || + pTask->iTask == TASK_FACE_LASTPOSITION || + pTask->iTask == TASK_FACE_PATH || + pTask->iTask == TASK_FACE_PLAYER || + pTask->iTask == TASK_FACE_REASONABLE || + pTask->iTask == TASK_FACE_SAVEPOSITION || + pTask->iTask == TASK_FACE_SCRIPT ) + { + return TaskComplete(); + } + } + + switch ( pTask->iTask ) + { + case TASK_RUN_TO_VEHICLE_ENTRANCE: + { + // Get a move on! + GetOuter()->GetNavigator()->SetMovementActivity( ACT_RUN ); + } + break; + + case TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT: + { + if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE ) + { + Assert( 0 ); + TaskFail( "Trying to run while inside a vehicle!\n"); + return; + } + + // Reserve an entry point + if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false ) + { + TaskFail( "No valid entry point!\n" ); + return; + } + + // Find where we're going + if ( FindPathToVehicleEntryPoint() ) + { + TaskComplete(); + return; + } + + // We didn't find a path + TaskFail( "TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT: Unable to run to entry point" ); + } + break; + + case TASK_GET_PATH_TO_TARGET: + { + GetOuter()->SetTarget( m_hVehicle ); + BaseClass::StartTask( pTask ); + } + break; + + case TASK_GET_PATH_TO_NEAR_VEHICLE: + { + if ( m_hVehicle == NULL ) + { + TaskFail("Lost vehicle pointer\n"); + return; + } + + // Find the passenger offset we're going for + Vector vecRight; + m_hVehicle->GetVectors( NULL, &vecRight, NULL ); + Vector vecTargetOffset = vecRight * 64.0f; + + // Try and find a path near there + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vecTargetOffset, AIN_DEF_ACTIVITY, 64.0f, AIN_UPDATE_TARGET_POS, m_hVehicle ); + GetOuter()->SetTarget( m_hVehicle ); + if ( GetOuter()->GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + + TaskFail( "Unable to find path to get closer to vehicle!\n" ); + return; + } + + break; + + case TASK_PASSENGER_RELOAD: + { + GetOuter()->SetIdealActivity( ACT_PASSENGER_RELOAD ); + return; + } + break; + + case TASK_PASSENGER_EXIT_STUCK_VEHICLE: + { + if ( ExitStuckVehicle() ) + { + TaskComplete(); + return; + } + + TaskFail("Unable to exit overturned vehicle!\n"); + } + break; + + case TASK_PASSENGER_OVERTURNED: + { + // Go into our overturned animation + if ( GetOuter()->GetActivity() != ACT_PASSENGER_OVERTURNED ) + { + GetOuter()->SetActivity( ACT_RESET ); + GetOuter()->SetActivity( ACT_PASSENGER_OVERTURNED ); + } + + TaskComplete(); + } + break; + + case TASK_PASSENGER_IMPACT: + { + // Stomp anything currently playing on top of us, this has to take priority + GetOuter()->RemoveAllGestures(); + + // Go into our impact animation + GetOuter()->ResetIdealActivity( ACT_PASSENGER_IMPACT ); + + // Delay for twice the duration of our impact animation + int nSequence = GetOuter()->SelectWeightedSequence( ACT_PASSENGER_IMPACT ); + float flSeqDuration = GetOuter()->SequenceDuration( nSequence ); + float flStunTime = flSeqDuration + random->RandomFloat( 1.0f, 2.0f ); + GetOuter()->SetNextAttack( gpGlobals->curtime + flStunTime ); + ExtendFidgetDelay( flStunTime ); + } + break; + + default: + BaseClass::StartTask( pTask ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::IsCurTaskContinuousMove( void ) +{ + const Task_t *pCurTask = GetCurTask(); + if ( pCurTask && pCurTask->iTask == TASK_RUN_TO_VEHICLE_ENTRANCE ) + return true; + + return BaseClass::IsCurTaskContinuousMove(); +} + +//----------------------------------------------------------------------------- +// Purpose: Update our path if we're running towards the vehicle (since it can move) +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::UpdateVehicleEntrancePath( void ) +{ + // If it's too soon to check again, don't bother + if ( m_flEntraceUpdateTime > gpGlobals->curtime ) + return true; + + // Find out if we need to update + if ( m_VehicleMonitor.TargetMoved2D( m_hVehicle ) == false ) + { + m_flEntraceUpdateTime = gpGlobals->curtime + 0.5f; + return true; + } + + // Don't attempt again for some amount of time + m_flEntraceUpdateTime = gpGlobals->curtime + 1.0f; + + int nSequence = FindEntrySequence( true ); + if ( nSequence == -1 ) + return false; + + SetTransitionSequence( nSequence ); + + // Get the entry position + Vector vecEntryPoint; + QAngle vecEntryAngles; + if ( GetEntryPoint( m_nTransitionSequence, &vecEntryPoint, &vecEntryAngles ) == false ) + return false; + + // Move the entry point forward in time a bit to predict where it'll be + Vector vecVehicleSpeed = m_hVehicle->GetSmoothedVelocity(); + + // Tack on the smoothed velocity + vecEntryPoint += vecVehicleSpeed; // one second + + // Update our entry point + if ( GetOuter()->GetNavigator()->UpdateGoalPos( vecEntryPoint ) == false ) + return false; + + // Reset the goal angles + GetNavigator()->SetArrivalDirection( vecEntryAngles ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::RunTask( const Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_PASSENGER_RELOAD: + { + if ( GetOuter()->IsSequenceFinished() ) + { + TaskComplete(); + } + } + break; + + case TASK_PASSENGER_IMPACT: + { + if ( GetOuter()->IsSequenceFinished() ) + { + TaskComplete(); + return; + } + } + break; + + case TASK_RUN_TO_VEHICLE_ENTRANCE: + { + // Update our entrance point if we can + if ( UpdateVehicleEntrancePath() == false ) + { + TaskFail("Unable to find entrance to vehicle"); + break; + } + + // See if we're close enough to our goal + if ( GetOuter ()->GetNavigator()->IsGoalActive() == false ) + { + // See if we're close enough now to enter the vehicle + Vector vecEntryPoint; + GetEntryPoint( m_nTransitionSequence, &vecEntryPoint ); + if ( ( vecEntryPoint - GetAbsOrigin() ).Length2DSqr() < Square( 36.0f ) ) + { + if ( GetNavigator()->GetArrivalActivity() != ACT_INVALID ) + { + SetActivity( GetNavigator()->GetArrivalActivity() ); + } + + TaskComplete(); + } + else + { + TaskFail( "Unable to navigate to vehicle" ); + } + } + + // Keep merrily going! + } + break; + + default: + BaseClass::RunTask( pTask ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add custom interrupt conditions +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::BuildScheduleTestBits( void ) +{ + // Always break on being able to exit + if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) + { + GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ) ); + GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_HARD_IMPACT) ); + + if ( IsCurSchedule( SCHED_PASSENGER_OVERTURNED ) == false ) + { + GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_OVERTURNED ) ); + } + + // Append the ability to break on fidgeting + if ( IsCurSchedule( SCHED_PASSENGER_IDLE ) ) + { + GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_CAN_FIDGET ) ); + } + + // Add this so we're prompt about exiting the vehicle when able to + if ( m_PassengerIntent == PASSENGER_INTENT_EXIT ) + { + GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_VEHICLE_STOPPED ) ); + } + } + + BaseClass::BuildScheduleTestBits(); +} +//----------------------------------------------------------------------------- +// Purpose: Determines if the passenger should take a radial route to the goal +// Input : &vecEntryPoint - Point of entry +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::UseRadialRouteToEntryPoint( const Vector &vecEntryPoint ) +{ + // Get the center position of the vehicle we'll radiate around + Vector vecCenterPos = m_hVehicle->WorldSpaceCenter(); + vecCenterPos.z = vecEntryPoint.z; + + // Find out if we need to go around the vehicle + float flDistToVehicleCenter = ( vecCenterPos - GetOuter()->GetAbsOrigin() ).Length(); + float flDistToGoal = ( vecEntryPoint - GetOuter()->GetAbsOrigin() ).Length(); + if ( flDistToGoal > flDistToVehicleCenter ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Find the arc in degrees to reach our goal position +// Input : &vecCenterPoint - Point around which the arc rotates +// &vecEntryPoint - Point we're trying to reach +// &bClockwise - If we should move clockwise or not to get there +// Output : float - degrees around arc to follow +//----------------------------------------------------------------------------- +float CAI_PassengerBehaviorCompanion::GetArcToEntryPoint( const Vector &vecCenterPoint, const Vector &vecEntryPoint, bool &bClockwise ) +{ + // We want the entry point to be at the same level as the center to make this a two dimensional problem + Vector vecEntryPointAdjusted = vecEntryPoint; + vecEntryPointAdjusted.z = vecCenterPoint.z; + + // Direction from vehicle center to passenger + Vector vecVehicleToPassenger = ( GetOuter()->GetAbsOrigin() - vecCenterPoint ); + VectorNormalize( vecVehicleToPassenger ); + + // Direction from vehicle center to entry point + Vector vecVehicleToEntry = ( vecEntryPointAdjusted - vecCenterPoint ); + VectorNormalize( vecVehicleToEntry ); + + float flVehicleToPassengerYaw = UTIL_VecToYaw( vecVehicleToPassenger ); + float flVehicleToEntryYaw = UTIL_VecToYaw( vecVehicleToEntry ); + float flArcDist = UTIL_AngleDistance( flVehicleToEntryYaw, flVehicleToPassengerYaw ); + + bClockwise = ( flArcDist < 0.0f ); + return fabs( flArcDist ); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes all failed points +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::ResetVehicleEntryFailedState( void ) +{ + m_FailedEntryPositions.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a failed position to the list and marks when it occurred +// Input : &vecPosition - Position that failed +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::MarkVehicleEntryFailed( const Vector &vecPosition ) +{ + FailPosition_t failPos; + failPos.flTime = gpGlobals->curtime; + failPos.vecPosition = vecPosition; + m_FailedEntryPositions.AddToTail( failPos ); + + // Show this as failed + if ( passenger_debug_entry.GetBool() ) + { + NDebugOverlay::Box( vecPosition, -Vector(8,8,8), Vector(8,8,8), 255, 0, 0, 0, 2.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: See if a vector is near enough to a previously failed position +// Input : &vecPosition - position to test +// Output : Returns true if the point is near enough another to be considered equivalent +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::PointIsWithinEntryFailureRadius( const Vector &vecPosition ) +{ + // Test this point against our known failed points and reject it if it's too near + for ( int i = 0; i < m_FailedEntryPositions.Count(); i++ ) + { + // If our time has expired, kill the position + if ( ( gpGlobals->curtime - m_FailedEntryPositions[i].flTime ) > 3.0f ) + { + // Show that we've cleared it + if ( passenger_debug_entry.GetBool() ) + { + NDebugOverlay::Box( m_FailedEntryPositions[i].vecPosition, -Vector(12,12,12), Vector(12,12,12), 255, 255, 0, 0, 2.0f ); + } + + m_FailedEntryPositions.Remove( i ); + continue; + } + + // See if this position is too near our last failed attempt + if ( ( vecPosition - m_FailedEntryPositions[i].vecPosition ).LengthSqr() < Square(3*12) ) + { + // Show that this was denied + if ( passenger_debug_entry.GetBool() ) + { + NDebugOverlay::Box( m_FailedEntryPositions[i].vecPosition, -Vector(12,12,12), Vector(12,12,12), 255, 0, 0, 128, 2.0f ); + } + + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// 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_PassengerBehaviorCompanion::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; + float flNearestDistSqr = FLT_MAX; + float flSeatDistSqr; + 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; + + // See if this entry position is in our list of known unreachable places + if ( PointIsWithinEntryFailureRadius( vecStartPos ) ) + 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 + flSeatDistSqr = ( vecStartPos - GetOuter()->GetAbsOrigin() ).LengthSqr(); + + // Closer, take it + if ( flSeatDistSqr < flNearestDistSqr ) + { + flNearestDistSqr = flSeatDistSqr; + nNearestSequence = nSequence; + } + } + + } + + return nNearestSequence; +} + +//----------------------------------------------------------------------------- +// Purpose: Override certain animations +//----------------------------------------------------------------------------- +Activity CAI_PassengerBehaviorCompanion::NPC_TranslateActivity( Activity activity ) +{ + Activity newActivity = BaseClass::NPC_TranslateActivity( activity ); + + // Handle animations from inside the vehicle + if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) + { + // Alter idle depending on the vehicle's state + if ( newActivity == ACT_IDLE ) + { + // Always play the overturned animation + if ( m_vehicleState.m_bWasOverturned ) + return ACT_PASSENGER_OVERTURNED; + } + } + + return newActivity; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::CanExitVehicle( void ) +{ + if ( BaseClass::CanExitVehicle() == false ) + return false; + + // If we're tipped too much, we can't exit + Vector vecUp; + GetOuter()->GetVectors( NULL, NULL, &vecUp ); + if ( DotProduct( vecUp, Vector(0,0,1) ) < DOT_45DEGREE ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: NPC needs to get to their marks, so do so with urgent navigation +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::IsNavigationUrgent( void ) +{ + // If we're running to the vehicle, do so urgently + if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE && m_PassengerIntent == PASSENGER_INTENT_ENTER ) + return true; + + return BaseClass::IsNavigationUrgent(); +} + +//----------------------------------------------------------------------------- +// Purpose: Calculate our body lean based on our delta velocity +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::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 dependent! + m_flLastLateralLean = ( m_flLastLateralLean * 0.2f ) + ( flLateralDisp * 0.8f ); + + // Here we can make Alyx do something different on an "extreme" lean condition + if ( fabs( m_flLastLateralLean ) > 0.75f ) + { + // Large lean, make us react? + } + + // Set these parameters + GetOuter()->SetPoseParameter( "vehicle_lean", m_flLastLateralLean ); +} + +//----------------------------------------------------------------------------- +// Purpose: Whether or not we're allowed to fidget +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::CanFidget( void ) +{ + // Can't fidget again too quickly + if ( m_flNextFidgetTime > gpGlobals->curtime ) + return false; + + // FIXME: Really we want to check our readiness level at this point + if ( GetOuter()->GetEnemy() != NULL ) + return false; + + // Don't fidget unless we're at low readiness + if ( m_hCompanion && ( m_hCompanion->GetReadinessLevel() > AIRL_RELAXED ) ) + return false; + + // Don't fidget while we're in a script + if ( GetOuter()->IsInAScript() || GetOuter()->GetIdealState() == NPC_STATE_SCRIPT || IsRunningScriptedScene( GetOuter() ) ) + return false; + + // If we're upside down, don't bother + if ( HasCondition( COND_PASSENGER_OVERTURNED ) ) + return false; + + // Must be visible to the player + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if ( pPlayer && pPlayer->FInViewCone( GetOuter()->EyePosition() ) == false ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Extends the fidget delay by the time specified +// Input : flDuration - in seconds +//----------------------------------------------------------------------------- +void CAI_PassengerBehaviorCompanion::ExtendFidgetDelay( float flDuration ) +{ + // If we're already expired, just set this as the next time + if ( m_flNextFidgetTime < gpGlobals->curtime ) + { + m_flNextFidgetTime = gpGlobals->curtime + flDuration; + } + else + { + // Otherwise bump the delay farther into the future + m_flNextFidgetTime += flDuration; + } +} + +//----------------------------------------------------------------------------- +// Purpose: We never want to be marked as crouching when inside a vehicle +//----------------------------------------------------------------------------- +bool CAI_PassengerBehaviorCompanion::IsCrouching( void ) +{ + return false; +} + +AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehaviorCompanion ) +{ + DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_AIM ) + DECLARE_ACTIVITY( ACT_PASSENGER_RELOAD ) + DECLARE_ACTIVITY( ACT_PASSENGER_OVERTURNED ) + DECLARE_ACTIVITY( ACT_PASSENGER_IMPACT ) + DECLARE_ACTIVITY( ACT_PASSENGER_IMPACT_WEAPON ) + DECLARE_ACTIVITY( ACT_PASSENGER_POINT ) + DECLARE_ACTIVITY( ACT_PASSENGER_POINT_BEHIND ) + DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_READY ) + DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_LARGE ) + DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_SMALL ) + DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_LARGE_STIMULATED ) + DECLARE_ACTIVITY( ACT_PASSENGER_GESTURE_JOSTLE_SMALL_STIMULATED ) + DECLARE_ACTIVITY( ACT_PASSENGER_COWER_IN ) + DECLARE_ACTIVITY( ACT_PASSENGER_COWER_LOOP ) + DECLARE_ACTIVITY( ACT_PASSENGER_COWER_OUT ) + DECLARE_ACTIVITY( ACT_PASSENGER_IDLE_FIDGET ) + + DECLARE_TASK( TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT ) + DECLARE_TASK( TASK_GET_PATH_TO_NEAR_VEHICLE ) + DECLARE_TASK( TASK_PASSENGER_RELOAD ) + DECLARE_TASK( TASK_PASSENGER_EXIT_STUCK_VEHICLE ) + DECLARE_TASK( TASK_PASSENGER_OVERTURNED ) + DECLARE_TASK( TASK_PASSENGER_IMPACT ) + DECLARE_TASK( TASK_RUN_TO_VEHICLE_ENTRANCE ) + + DECLARE_CONDITION( COND_PASSENGER_VEHICLE_MOVED_FROM_MARK ) + DECLARE_CONDITION( COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE ) + DECLARE_CONDITION( COND_PASSENGER_WARN_OVERTURNED ) + DECLARE_CONDITION( COND_PASSENGER_WARN_COLLISION ) + DECLARE_CONDITION( COND_PASSENGER_CAN_FIDGET ) + DECLARE_CONDITION( COND_PASSENGER_CAN_ENTER_IMMEDIATELY ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED" + " TASK_STOP_MOVING 0" + " TASK_SET_TOLERANCE_DISTANCE 36" // 3 ft + " TASK_SET_ROUTE_SEARCH_TIME 5" + " TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT 0" + " TASK_RUN_TO_VEHICLE_ENTRANCE 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_PASSENGER_ENTER_VEHICLE" + "" + " Interrupts" + " COND_PASSENGER_CAN_ENTER_IMMEDIATELY" + " COND_PASSENGER_CANCEL_ENTER" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_ENTER_VEHICLE_PAUSE" + " TASK_STOP_MOVING 0" + " TASK_SET_TOLERANCE_DISTANCE 36" + " TASK_SET_ROUTE_SEARCH_TIME 3" + " TASK_GET_PATH_TO_NEAR_VEHICLE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_PASSENGER_CANCEL_ENTER" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_ENTER_VEHICLE_PAUSE, + + " Tasks" + " TASK_STOP_MOVING 1" + " TASK_FACE_TARGET 0" + " TASK_WAIT 2" + "" + " Interrupts" + " COND_LIGHT_DAMAGE" + " COND_NEW_ENEMY" + " COND_PASSENGER_CANCEL_ENTER" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_RANGE_ATTACK1, + + " Tasks" + " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack + " TASK_RANGE_ATTACK1 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_ENEMY_OCCLUDED" + " COND_NO_PRIMARY_AMMO" + " COND_HEAR_DANGER" + " COND_WEAPON_BLOCKED_BY_FRIEND" + " COND_WEAPON_SIGHT_OCCLUDED" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_EXIT_STUCK_VEHICLE, + + " Tasks" + " TASK_PASSENGER_EXIT_STUCK_VEHICLE 0" + "" + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_RELOAD, + + " Tasks" + " TASK_PASSENGER_RELOAD 0" + "" + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_OVERTURNED, + + " Tasks" + " TASK_PASSENGER_OVERTURNED 0" + "" + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_IMPACT, + + " Tasks" + " TASK_PASSENGER_IMPACT 0" + "" + " Interrupts" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY, + + " Tasks" + " TASK_PASSENGER_ATTACH_TO_VEHICLE 0" + " TASK_PASSENGER_ENTER_VEHICLE 0" + "" + " Interrupts" + " COND_NO_CUSTOM_INTERRUPTS" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_COWER, + + " Tasks" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_IN" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_LOOP" + " TASK_WAIT_UNTIL_NO_DANGER_SOUND 0" + " TASK_WAIT 2" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_COWER_OUT" + "" + " Interrupts" + " COND_NO_CUSTOM_INTERRUPTS" + ) + + DEFINE_SCHEDULE + ( + SCHED_PASSENGER_FIDGET, + + " Tasks" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_IDLE_FIDGET" + "" + " Interrupts" + " COND_NO_CUSTOM_INTERRUPTS" + ) + + AI_END_CUSTOM_SCHEDULE_PROVIDER() +} |