summaryrefslogtreecommitdiff
path: root/game/server/episodic/ai_behavior_passenger_companion.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/episodic/ai_behavior_passenger_companion.cpp')
-rw-r--r--game/server/episodic/ai_behavior_passenger_companion.cpp2053
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()
+}