You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1736 lines
58 KiB
1736 lines
58 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Behavior for NPCs riding in cars (with boys) |
|
// |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
#include "ai_playerally.h" |
|
#include "ai_motor.h" |
|
#include "bone_setup.h" |
|
#include "vehicle_base.h" |
|
#include "entityblocker.h" |
|
#include "ai_behavior_passenger.h" |
|
#include "ai_pathfinder.h" |
|
#include "ai_network.h" |
|
#include "ai_node.h" |
|
#include "ai_moveprobe.h" |
|
#include "env_debughistory.h" |
|
|
|
// Custom activities |
|
int ACT_PASSENGER_IDLE; |
|
int ACT_PASSENGER_RANGE_ATTACK1; |
|
|
|
ConVar passenger_debug_transition( "passenger_debug_transition", "0" ); |
|
ConVar passenger_impact_response_threshold( "passenger_impact_response_threshold", "-350.0" ); |
|
|
|
#define ORIGIN_KEYNAME "origin" |
|
#define ANGLES_KEYNAME "angles" |
|
|
|
BEGIN_DATADESC( CAI_PassengerBehavior ) |
|
|
|
DEFINE_EMBEDDED( m_vehicleState ), |
|
DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_PassengerIntent, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_PassengerState, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_hVehicle, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hBlocker, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_vecTargetPosition, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_vecTargetAngles, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_flOriginStartFrame, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flOriginEndFrame, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flAnglesStartFrame, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flAnglesEndFrame, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_nTransitionSequence,FIELD_INTEGER ), |
|
|
|
END_DATADESC(); |
|
|
|
BEGIN_SIMPLE_DATADESC( passengerVehicleState_t ) |
|
|
|
DEFINE_FIELD( m_bWasBoosting, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bWasOverturned, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_vecLastLocalVelocity, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecDeltaVelocity, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecLastAngles, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_flNextWarningTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flLastSpeedSqr, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_bPlayerInVehicle, FIELD_BOOLEAN ), |
|
|
|
END_DATADESC(); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CAI_PassengerBehavior::CAI_PassengerBehavior( void ) : |
|
m_bEnabled( false ), |
|
m_hVehicle( NULL ), |
|
m_PassengerState( PASSENGER_STATE_OUTSIDE ), |
|
m_PassengerIntent( PASSENGER_INTENT_NONE ), |
|
m_nTransitionSequence( -1 ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Enables the behavior to run |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::Enable( CPropJeepEpisodic *pVehicle, bool bImmediateEnter /*= false*/ ) |
|
{ |
|
if ( m_bEnabled && m_hVehicle.Get() ) |
|
return; |
|
|
|
m_bEnabled = true; |
|
m_hVehicle = pVehicle; |
|
SetPassengerState( PASSENGER_STATE_OUTSIDE ); |
|
|
|
// Init our starting information about the vehicle |
|
InitVehicleState(); |
|
} |
|
|
|
void CAI_PassengerBehavior::OnRestore() |
|
{ |
|
if ( m_bEnabled && !m_hVehicle.Get() ) |
|
{ |
|
Disable(); |
|
} |
|
|
|
BaseClass::OnRestore(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Stops the behavior from being run |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::Disable( void ) |
|
{ |
|
m_bEnabled = false; |
|
m_hVehicle = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Starts the process of entering a vehicle |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::EnterVehicle( void ) |
|
{ |
|
// If we're already in the vehicle, simply cancel out our intents |
|
if ( GetPassengerState() == PASSENGER_STATE_INSIDE || GetPassengerState() == PASSENGER_STATE_ENTERING ) |
|
{ |
|
// Clear out an exit |
|
if ( m_PassengerIntent == PASSENGER_INTENT_EXIT ) |
|
{ |
|
m_PassengerIntent = PASSENGER_INTENT_NONE; |
|
ClearCondition( COND_PASSENGER_ENTERING ); |
|
ClearCondition( COND_PASSENGER_EXITING ); |
|
} |
|
|
|
return; |
|
} |
|
|
|
// Update our internal state |
|
m_PassengerIntent = PASSENGER_INTENT_ENTER; |
|
|
|
// Otherwise set this condition and go! |
|
SetCondition( COND_PASSENGER_ENTERING ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Starts the process of exiting a vehicle |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::ExitVehicle( void ) |
|
{ |
|
// Must be in the seat |
|
if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE || GetPassengerState() == PASSENGER_STATE_EXITING ) |
|
{ |
|
// Clear out an entrance |
|
if ( m_PassengerIntent == PASSENGER_INTENT_ENTER ) |
|
{ |
|
m_PassengerIntent = PASSENGER_INTENT_NONE; |
|
SetCondition( COND_PASSENGER_CANCEL_ENTER ); |
|
ClearCondition( COND_PASSENGER_ENTERING ); |
|
ClearCondition( COND_PASSENGER_EXITING ); |
|
} |
|
return; |
|
} |
|
|
|
// Update our internal state |
|
m_PassengerIntent = PASSENGER_INTENT_EXIT; |
|
|
|
// |
|
// Everything below this point will still attempt to exit the vehicle, once able |
|
// |
|
|
|
// Must have a valid vehicle |
|
if ( m_hVehicle == NULL ) |
|
return; |
|
|
|
// Cannot exit while we're upside down |
|
if ( m_hVehicle->IsOverturned() ) |
|
return; |
|
|
|
// Interrupt what we're doing |
|
SetCondition( COND_PASSENGER_EXITING ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: FIXME - This should move into something a bit more flexible |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::AddPhysicsPush( float force ) |
|
{ |
|
/* |
|
// Kick the vehicle so the player knows we've arrived |
|
Vector impulse = m_hVehicle->GetAbsOrigin() - GetOuter()->GetAbsOrigin(); |
|
VectorNormalize( impulse ); |
|
impulse.z = -0.75; |
|
VectorNormalize( impulse ); |
|
Vector vecForce = impulse * force; |
|
|
|
m_hVehicle->VPhysicsGetObject()->ApplyForceOffset( vecForce, GetOuter()->GetAbsOrigin() ); |
|
*/ |
|
|
|
Vector vecDir; |
|
|
|
IPhysicsObject *pObject = GetOuter()->VPhysicsGetObject(); |
|
Vector vecVelocity; |
|
pObject->GetVelocity( &vecVelocity, NULL ); |
|
GetOuter()->GetVectors( NULL, NULL, &vecDir ); |
|
vecDir.Negate(); |
|
|
|
Vector vecForce = vecDir * force; |
|
|
|
m_hVehicle->VPhysicsGetObject()->ApplyForceOffset( vecForce, GetOuter()->GetAbsOrigin() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::IsPassengerHostile( void ) |
|
{ |
|
CBaseEntity *pPlayer = AI_GetSinglePlayer(); |
|
|
|
// If the player hates or fears the passenger, they're hostile |
|
if ( GetOuter()->IRelationType( pPlayer ) == D_HT || GetOuter()->IRelationType( pPlayer ) == D_FR ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::InitVehicleState( void ) |
|
{ |
|
// Set the player's state |
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
m_vehicleState.m_bPlayerInVehicle = ( pPlayer && pPlayer->IsInAVehicle() && pPlayer->GetServerVehicle() == m_hVehicle->GetServerVehicle() ); |
|
|
|
// Update our vehicle state so we don't confuse our previous velocity on the first frame! |
|
m_vehicleState.m_bWasBoosting = false; |
|
m_vehicleState.m_bWasOverturned = false; |
|
m_vehicleState.m_flNextWarningTime = 0.0f; |
|
m_vehicleState.m_vecDeltaVelocity = vec3_origin; |
|
m_vehicleState.m_flNextWarningTime = gpGlobals->curtime; |
|
m_vehicleState.m_vecLastAngles = m_hVehicle->GetAbsAngles(); |
|
|
|
Vector localVelocity; |
|
GetLocalVehicleVelocity( &m_vehicleState.m_vecLastLocalVelocity ); |
|
|
|
m_vehicleState.m_flLastSpeedSqr = localVelocity.LengthSqr(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Puts the NPC in hierarchy with the vehicle and makes them intangible |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::FinishEnterVehicle( void ) |
|
{ |
|
if ( m_hVehicle == NULL ) |
|
return; |
|
|
|
// Get the ultimate position we want to be in |
|
Vector vecFinalPos; |
|
QAngle vecFinalAngles; |
|
GetEntryTarget( &vecFinalPos, &vecFinalAngles ); |
|
|
|
// Make sure we're exactly where we need to be |
|
GetOuter()->SetLocalOrigin( vecFinalPos ); |
|
GetOuter()->SetLocalAngles( vecFinalAngles ); |
|
GetOuter()->SetMoveType( MOVETYPE_NONE ); |
|
GetOuter()->GetMotor()->SetYawLocked( true ); |
|
|
|
// We're now riding inside the vehicle |
|
SetPassengerState( PASSENGER_STATE_INSIDE ); |
|
|
|
// If we've not been told to leave immediately, we're done |
|
if ( m_PassengerIntent == PASSENGER_INTENT_ENTER ) |
|
{ |
|
m_PassengerIntent = PASSENGER_INTENT_NONE; |
|
} |
|
|
|
// Tell the vehicle we've succeeded |
|
m_hVehicle->NPC_FinishedEnterVehicle( GetOuter(), (IsPassengerHostile()==false) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes the NPC from the car |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::FinishExitVehicle( void ) |
|
{ |
|
if ( m_hVehicle == NULL ) |
|
return; |
|
|
|
// Destroy the blocker |
|
if ( m_hBlocker != NULL ) |
|
{ |
|
UTIL_Remove( m_hBlocker ); |
|
m_hBlocker = NULL; |
|
} |
|
|
|
// To do this, we need to be very sure we're in a good spot |
|
GetOuter()->SetCondition( COND_PROVOKED ); |
|
GetOuter()->SetMoveType( MOVETYPE_STEP ); |
|
GetOuter()->RemoveFlag( FL_FLY ); |
|
GetOuter()->GetMotor()->SetYawLocked( false ); |
|
|
|
// Re-enable the physical collisions for this NPC |
|
IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject(); |
|
if ( pPhysObj != NULL ) |
|
{ |
|
pPhysObj->EnableCollisions( true ); |
|
} |
|
|
|
m_hVehicle->NPC_RemovePassenger( GetOuter() ); |
|
m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), (IsPassengerHostile()==false) ); |
|
|
|
SetPassengerState( PASSENGER_STATE_OUTSIDE ); |
|
|
|
// Stop our custom move sequence |
|
GetOuter()->m_iszSceneCustomMoveSeq = NULL_STRING; |
|
|
|
// If we've not been told to enter immediately, we're done |
|
if ( m_PassengerIntent == PASSENGER_INTENT_EXIT ) |
|
{ |
|
m_PassengerIntent = PASSENGER_INTENT_NONE; |
|
Disable(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Build our custom interrupt cases for the behavior |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::BuildScheduleTestBits( void ) |
|
{ |
|
// Always interrupt when we need to get in or out |
|
if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE || GetPassengerState() == PASSENGER_STATE_INSIDE ) |
|
{ |
|
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_ENTERING ) ); |
|
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_EXITING ) ); |
|
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_CANCEL_ENTER ) ); |
|
} |
|
|
|
BaseClass::BuildScheduleTestBits(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Dictates whether or not the behavior is active and working |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::CanSelectSchedule( void ) |
|
{ |
|
return m_bEnabled; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::CanExitVehicle( void ) |
|
{ |
|
// Vehicle must not be overturned |
|
if ( m_hVehicle->IsOverturned() ) |
|
return false; |
|
|
|
// Vehicle must be at rest |
|
Vector vecVelocity; |
|
m_hVehicle->GetVelocity( &vecVelocity, NULL ); |
|
if ( vecVelocity.LengthSqr() > Square( 8.0f ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles passengers deciding to enter or exit the vehicle |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CAI_PassengerBehavior::SelectTransitionSchedule( void ) |
|
{ |
|
// Handle our various states |
|
if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) |
|
{ |
|
// Exiting schedule |
|
if ( HasCondition( COND_PASSENGER_EXITING ) || m_PassengerIntent == PASSENGER_INTENT_EXIT ) |
|
{ |
|
if ( CanExitVehicle( ) ) |
|
{ |
|
ClearCondition( COND_PASSENGER_EXITING ); |
|
return SCHED_PASSENGER_EXIT_VEHICLE; |
|
} |
|
} |
|
} |
|
else if ( GetPassengerState() == PASSENGER_STATE_ENTERING || GetPassengerState() == PASSENGER_STATE_EXITING ) |
|
{ |
|
// The following code attempts to fix up a passenger being interrupted mid-transition |
|
Warning( "SelectSchedule() called on transitioning passenger!\n" ); |
|
ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs( "%s(%d): SelectSchedule() called on transitioning passenger!\n", GetOuter()->GetDebugName(), GetOuter()->entindex() ) ); |
|
Assert( 0 ); |
|
|
|
// Correct this issue immediately |
|
if ( GetPassengerState() == PASSENGER_STATE_EXITING ) |
|
{ |
|
// Force them out of the vehicle to where they want to be |
|
// The teleport function is overridden for passengers, meaning that they will clean up properly when called to do so |
|
GetOuter()->Teleport( &m_vecTargetPosition, &m_vecTargetAngles, NULL ); |
|
} |
|
else if ( GetPassengerState() == PASSENGER_STATE_ENTERING ) |
|
{ |
|
// Force them into the proper position |
|
GetOuter()->SetLocalOrigin( m_vecTargetPosition ); |
|
GetOuter()->SetLocalAngles( m_vecTargetAngles ); |
|
FinishEnterVehicle(); |
|
} |
|
|
|
// Stop playing our animation |
|
SetActivity( ACT_RESET ); |
|
} |
|
|
|
return SCHED_NONE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Overrides the schedule selection |
|
// Output : int - Schedule to play |
|
//----------------------------------------------------------------------------- |
|
int CAI_PassengerBehavior::SelectSchedule( void ) |
|
{ |
|
// Protect from this rare occurrence happening |
|
if ( m_hVehicle == NULL ) |
|
{ |
|
Assert( m_hVehicle != NULL ); |
|
Warning( "Entity %s running passenger behavior without a valid vehicle!\n", GetName() ); |
|
|
|
Disable(); |
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
// See if we're transitioning in / out of the vehicle |
|
int nSchedule = SelectTransitionSchedule(); |
|
if ( nSchedule != SCHED_NONE ) |
|
return nSchedule; |
|
|
|
return SCHED_PASSENGER_IDLE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CAI_PassengerBehavior::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) |
|
{ |
|
switch( failedTask ) |
|
{ |
|
// For now, just sit back down |
|
case TASK_PASSENGER_DETACH_FROM_VEHICLE: |
|
return SCHED_PASSENGER_IDLE; |
|
break; |
|
} |
|
|
|
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds a ground position at a given location with some delta up and down to check |
|
// Input : &in - position to check at |
|
// delta - amount of distance up and down to check |
|
// *out - ground position |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::FindGroundAtPosition( const Vector &in, float flUpDelta, float flDownDelta, Vector *out ) |
|
{ |
|
Vector startPos = in + Vector( 0, 0, flUpDelta ); // Look up by delta |
|
Vector endPos = in - Vector( 0, 0, flDownDelta ); // Look down by delta |
|
Vector hullMin = GetOuter()->GetHullMins(); |
|
Vector hullMax = GetOuter()->GetHullMaxs(); |
|
|
|
// Ignore ourself and the vehicle we're referencing |
|
CTraceFilterVehicleTransition ignoreFilter( m_hVehicle, GetOuter(), COLLISION_GROUP_NONE ); |
|
|
|
trace_t tr; |
|
UTIL_TraceHull( startPos, endPos, hullMin, hullMax, MASK_NPCSOLID, &ignoreFilter, &tr ); |
|
|
|
// Must not have ended up in solid space |
|
if ( tr.allsolid ) |
|
{ |
|
// Debug |
|
if ( passenger_debug_transition.GetBool() ) |
|
{ |
|
NDebugOverlay::SweptBox( tr.startpos, tr.endpos, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 1.0f ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// Must have ended up with feet on the ground |
|
if ( tr.DidHitWorld() || ( tr.m_pEnt && tr.m_pEnt->IsStandable() ) ) |
|
{ |
|
// Debug |
|
if ( passenger_debug_transition.GetBool() ) |
|
{ |
|
NDebugOverlay::SweptBox( tr.startpos, tr.endpos, hullMin, hullMax, vec3_angle, 0, 255, 0, 255, 1.0f ); |
|
} |
|
|
|
*out = tr.endpos; |
|
return true; |
|
} |
|
|
|
// Ended up in the air |
|
if ( passenger_debug_transition.GetBool() ) |
|
{ |
|
NDebugOverlay::SweptBox( tr.startpos, tr.endpos, hullMin, hullMax, vec3_angle, 255, 0, 0, 255, 1.0f ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Attempt to verify that a test position is on the node graph |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::PointIsNavigable( const Vector &vecTargetPos ) |
|
{ |
|
// Attempt to local move between this point and the nearest node, ignoring anything but world architecture |
|
AIMoveTrace_t moveTrace; |
|
int iNearestNode = GetOuter()->GetPathfinder()->NearestNodeToPoint( vecTargetPos ); |
|
if ( iNearestNode != NO_NODE ) |
|
{ |
|
// Try a movement trace between the test position and the node |
|
GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND, |
|
g_pBigAINet->GetNodePosition(GetOuter()->GetHullType(), iNearestNode ), |
|
vecTargetPos, |
|
MASK_SOLID_BRUSHONLY, |
|
NULL, |
|
0, |
|
&moveTrace ); |
|
|
|
// See if we got close enough to call it arrived |
|
if ( ( moveTrace.vEndPosition - vecTargetPos ).LengthSqr() < Square( GetHullWidth() ) && |
|
GetOuter()->GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY ) ) |
|
{ |
|
// NDebugOverlay::HorzArrow( vecTargetPos, moveTrace.vEndPosition, 8.0f, 255, 0, 0, 16, true, 4.0f ); |
|
// NDebugOverlay::HorzArrow( vecTargetPos, g_pBigAINet->GetNodePosition(GetOuter()->GetHullType(), iNearestNode ), 8.0f, 255, 255, 0, 16, true, 4.0f ); |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gets the exit point for the passenger (on the ground) |
|
// Input : &vecOut - position the entity should be at when finished exiting |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::GetExitPoint( int nSequence, Vector *vecExitPoint, QAngle *vecExitAngles ) |
|
{ |
|
bool bSucceeded = true; |
|
|
|
// Get the delta to the final position as will be dictated by this animation's auto movement |
|
Vector vecDeltaPos; |
|
QAngle vecDeltaAngles; |
|
GetOuter()->GetSequenceMovement( nSequence, 0.0f, 1.0f, vecDeltaPos, vecDeltaAngles ); |
|
|
|
// Rotate the delta position by our starting angles |
|
Vector vecRotPos = vecDeltaPos; |
|
VectorRotate( vecRotPos, GetOuter()->GetAbsAngles(), vecDeltaPos ); |
|
|
|
if ( vecExitPoint != NULL ) |
|
{ |
|
float flDownDelta = 64.0f; |
|
float flUpDelta = 16.0f; |
|
Vector vecGroundPos; |
|
|
|
bool bFoundGround = FindGroundAtPosition( GetOuter()->GetAbsOrigin() + vecDeltaPos, flUpDelta, flDownDelta, &vecGroundPos ); |
|
if ( bFoundGround ) |
|
{ |
|
if ( PointIsNavigable( vecGroundPos ) == false ) |
|
{ |
|
bSucceeded = false; |
|
} |
|
} |
|
else |
|
{ |
|
bSucceeded = false; |
|
} |
|
|
|
*vecExitPoint = vecGroundPos; |
|
} |
|
|
|
if ( vecExitAngles != NULL ) |
|
{ |
|
QAngle newAngles = GetOuter()->GetAbsAngles() + vecDeltaAngles; |
|
newAngles.x = UTIL_AngleMod( newAngles.x ); |
|
newAngles.y = UTIL_AngleMod( newAngles.y ); |
|
newAngles.z = UTIL_AngleMod( newAngles.z ); |
|
*vecExitAngles = newAngles; |
|
} |
|
|
|
return bSucceeded; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Reserve our entry point |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::ReserveEntryPoint( VehicleSeatQuery_e eSeatSearchType ) |
|
{ |
|
// FIXME: Move all this logic into the NPC_EnterVehicle function? |
|
// Find any seat to get into |
|
int nSeatID = m_hVehicle->GetServerVehicle()->NPC_GetAvailableSeat( GetOuter(), GetRoleName(), eSeatSearchType ); |
|
if ( nSeatID != VEHICLE_SEAT_INVALID ) |
|
return m_hVehicle->NPC_AddPassenger( GetOuter(), GetRoleName(), nSeatID ); |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determines whether the NPC can move between a start and end position of a transition |
|
// Input : &vecStartPos - start position |
|
// &vecEndPos - end position |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::IsValidTransitionPoint( const Vector &vecStartPos, const Vector &vecEndPos ) |
|
{ |
|
// Now sweep a hull through space to see if we can validly exit there |
|
Vector vecHullMins = GetOuter()->GetHullMins() + Vector( 0, 0, GetOuter()->StepHeight()*2.0f ); |
|
Vector vecHullMaxs = GetOuter()->GetHullMaxs() - Vector( 0, 0, GetOuter()->StepHeight() ); |
|
|
|
trace_t tr; |
|
CTraceFilterVehicleTransition skipFilter( GetOuter(), m_hVehicle, COLLISION_GROUP_NONE ); |
|
UTIL_TraceHull( vecStartPos, vecEndPos, vecHullMins, vecHullMaxs, MASK_NPCSOLID, &skipFilter, &tr ); |
|
|
|
// If we're blocked, we can't get out there |
|
if ( tr.fraction < 1.0f || tr.allsolid ) |
|
{ |
|
if ( passenger_debug_transition.GetBool() ) |
|
{ |
|
NDebugOverlay::SweptBox( vecStartPos, vecEndPos, vecHullMins, vecHullMaxs, vec3_angle, 255, 0, 0, 64, 2.0f ); |
|
} |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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_PassengerBehavior::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; |
|
Vector vecSeatDir; |
|
float flNearestDist = FLT_MAX; |
|
float flSeatDist; |
|
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; |
|
|
|
// 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 |
|
vecSeatDir = ( vecStartPos - GetOuter()->GetAbsOrigin() ); |
|
flSeatDist = VectorNormalize( vecSeatDir ); |
|
|
|
// Closer, take it |
|
if ( flSeatDist < flNearestDist ) |
|
{ |
|
flNearestDist = flSeatDist; |
|
nNearestSequence = nSequence; |
|
} |
|
} |
|
|
|
} |
|
|
|
return nNearestSequence; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
int CAI_PassengerBehavior::FindExitSequence( void ) |
|
{ |
|
// Get a list of all our animations |
|
const PassengerSeatAnims_t *pExitAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_EXIT ); |
|
if ( pExitAnims == NULL ) |
|
return -1; |
|
|
|
// Get the ultimate position we'll end up at |
|
Vector vecStartPos, vecEndPos; |
|
if ( m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPosition( GetOuter(), &vecStartPos, NULL ) == false ) |
|
return -1; |
|
|
|
// Test each animation (sorted by priority) for the best match |
|
for ( int i = 0; i < pExitAnims->Count(); i++ ) |
|
{ |
|
// Find the activity for this animation name |
|
int nSequence = GetOuter()->LookupSequence( STRING( pExitAnims->Element(i).GetAnimationName() ) ); |
|
if ( nSequence == -1 ) |
|
continue; |
|
|
|
// Test this entry for validity |
|
if ( GetExitPoint( nSequence, &vecEndPos ) == false ) |
|
continue; |
|
|
|
// Check to see if we can use this |
|
if ( IsValidTransitionPoint( vecStartPos, vecEndPos ) ) |
|
return nSequence; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Reserve our exit point so nothing moves into it while we're moving |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::ReserveExitPoint( void ) |
|
{ |
|
// Cannot exit while we're upside down |
|
// FIXME: This is probably redundant! |
|
if ( m_hVehicle->IsOverturned() ) |
|
return false; |
|
|
|
// Find the exit activity to use |
|
int nSequence = FindExitSequence(); |
|
if ( nSequence == -1 ) |
|
return false; |
|
|
|
// Get the exit position |
|
Vector vecGroundPos; |
|
if ( GetExitPoint( nSequence, &vecGroundPos, &m_vecTargetAngles ) == false ) |
|
return false; |
|
|
|
// We have to do this specially because the activities are not named |
|
SetTransitionSequence( nSequence ); |
|
|
|
// Reserve this space |
|
Vector hullMin = GetOuter()->GetHullMins(); |
|
Vector hullMax = GetOuter()->GetHullMaxs(); |
|
m_hBlocker = CEntityBlocker::Create( vecGroundPos, hullMin, hullMax, GetOuter(), true ); |
|
|
|
// Save this destination position so we can interpolate towards it |
|
m_vecTargetPosition = vecGroundPos; |
|
|
|
// Pitch and roll must be zero when we finish! |
|
m_vecTargetAngles.x = m_vecTargetAngles.z = 0.0f; |
|
|
|
if ( passenger_debug_transition.GetBool() ) |
|
{ |
|
Vector vecForward; |
|
AngleVectors( m_vecTargetAngles, &vecForward, NULL, NULL ); |
|
Vector vecArrowEnd = m_vecTargetPosition + ( vecForward * 64.0f ); |
|
NDebugOverlay::HorzArrow( m_vecTargetPosition, vecArrowEnd, 8.0f, 255, 255, 0, 64, true, 4.0f ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the exact point we'd like to start our animation from to enter |
|
// the vehicle. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::GetEntryPoint( int nSequence, Vector *vecEntryPoint, QAngle *vecEntryAngles ) |
|
{ |
|
bool bSucceeded = true; |
|
|
|
// Get the delta to the final position as will be dictated by this animation's auto movement |
|
Vector vecDeltaPos; |
|
QAngle vecDeltaAngles; |
|
GetOuter()->GetSequenceMovement( nSequence, 1.0f, 0.0f, vecDeltaPos, vecDeltaAngles ); |
|
|
|
// Get the final position we're trying to end up at |
|
Vector vecTargetPos; |
|
QAngle vecTargetAngles; |
|
GetEntryTarget( &vecTargetPos, &vecTargetAngles ); |
|
|
|
// Rotate it to match |
|
Vector vecPreDelta = vecDeltaPos; |
|
VectorRotate( vecPreDelta, vecTargetAngles, vecDeltaPos ); |
|
|
|
// Offset this into the proper worldspace position |
|
vecTargetPos = vecTargetPos + vecDeltaPos; |
|
|
|
// Output the position, if requested |
|
if ( vecEntryPoint != NULL ) |
|
{ |
|
m_hVehicle->EntityToWorldSpace( vecTargetPos, vecEntryPoint ); |
|
|
|
// Trace down to the ground to see where we'll stand |
|
Vector vecGroundPos; |
|
if ( FindGroundAtPosition( (*vecEntryPoint), 16.0f, 64.0f, &vecGroundPos ) == false ) |
|
{ |
|
// We failed |
|
if ( passenger_debug_transition.GetBool() ) |
|
{ |
|
NDebugOverlay::SweptBox( (*vecEntryPoint), vecGroundPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 64, 2.0f ); |
|
} |
|
|
|
// The floor could not be found |
|
bSucceeded = false; |
|
} |
|
|
|
// Take this position |
|
*vecEntryPoint = vecGroundPos; |
|
} |
|
|
|
// Output the angles, if requested |
|
if ( vecEntryAngles != NULL ) |
|
{ |
|
// Add our delta angles to find what angles to start at |
|
*vecEntryAngles = vecTargetAngles; |
|
vecEntryAngles->y = UTIL_AngleMod( vecTargetAngles.y + vecDeltaAngles.y ); |
|
|
|
//Transform those angles to worldspace |
|
matrix3x4_t angToParent, angToWorld; |
|
AngleMatrix( (*vecEntryAngles), angToParent ); |
|
ConcatTransforms( m_hVehicle->EntityToWorldTransform(), angToParent, angToWorld ); |
|
MatrixAngles( angToWorld, (*vecEntryAngles) ); |
|
} |
|
|
|
// Debug info |
|
if ( passenger_debug_transition.GetBool() && vecEntryPoint && vecEntryAngles ) |
|
{ |
|
NDebugOverlay::Axis( *vecEntryPoint, vecTargetAngles, 16, true, 4.0f ); |
|
NDebugOverlay::Cross3D( *vecEntryPoint, 4, 255, 255, 0, true, 4.0f ); |
|
|
|
if ( vecEntryAngles != NULL ) |
|
{ |
|
Vector vecForward; |
|
AngleVectors( (*vecEntryAngles), &vecForward, NULL, NULL ); |
|
Vector vecArrowEnd = (*vecEntryPoint ) + ( vecForward * 64.0f ); |
|
NDebugOverlay::HorzArrow( (*vecEntryPoint), vecArrowEnd, 8.0f, 0, 255, 0, 64, true, 4.0f ); |
|
} |
|
} |
|
|
|
return bSucceeded; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Do the low-level work to detach us from our vehicle |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::DetachFromVehicle( void ) |
|
{ |
|
// Detach from the parent |
|
GetOuter()->SetParent( NULL ); |
|
GetOuter()->SetMoveType( MOVETYPE_STEP ); |
|
GetOuter()->AddFlag( FL_FLY ); |
|
GetOuter()->SetGroundEntity( NULL ); |
|
GetOuter()->SetCollisionGroup( COLLISION_GROUP_NPC ); |
|
m_hVehicle->RemovePhysicsChild( GetOuter() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::AttachToVehicle( void ) |
|
{ |
|
// Parent to the vehicle |
|
GetOuter()->ClearForceCrouch(); |
|
GetOuter()->SetParent( m_hVehicle ); |
|
GetOuter()->AddFlag( FL_FLY ); |
|
GetOuter()->SetGroundEntity( NULL ); |
|
GetOuter()->SetCollisionGroup( COLLISION_GROUP_IN_VEHICLE ); |
|
|
|
// Turn off physical interactions while we're in the vehicle |
|
IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject(); |
|
if ( pPhysObj != NULL ) |
|
{ |
|
pPhysObj->EnableCollisions( false ); |
|
} |
|
|
|
// Set our destination target |
|
GetEntryTarget( &m_vecTargetPosition, &m_vecTargetAngles ); |
|
|
|
// Get physics messages from our attached physics object |
|
m_hVehicle->AddPhysicsChild( GetOuter() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle task starting |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::StartTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_PASSENGER_ENTER_VEHICLE: |
|
{ |
|
// You must have set your entrance animation before this point! |
|
Assert( m_nTransitionSequence != -1 ); |
|
|
|
// Start us playing the correct sequence |
|
GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE ); |
|
SetPassengerState( PASSENGER_STATE_ENTERING ); |
|
|
|
// Overlaying any gestures will mess us up, so don't allow it |
|
GetOuter()->RemoveAllGestures(); |
|
} |
|
break; |
|
|
|
case TASK_PASSENGER_EXIT_VEHICLE: |
|
{ |
|
// You must have set your entrance animation before this point! |
|
Assert( m_nTransitionSequence != -1 ); |
|
|
|
// Start us playing the correct sequence |
|
GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE ); |
|
|
|
// Overlaying any gestures will mess us up, so don't allow it |
|
GetOuter()->RemoveAllGestures(); |
|
} |
|
break; |
|
|
|
case TASK_PASSENGER_ATTACH_TO_VEHICLE: |
|
{ |
|
AttachToVehicle(); |
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_PASSENGER_DETACH_FROM_VEHICLE: |
|
{ |
|
// Place an entity blocker where we're going to go |
|
if ( ReserveExitPoint() == false ) |
|
{ |
|
OnExitVehicleFailed(); |
|
TaskFail("Failed to find valid exit point\n"); |
|
return; |
|
} |
|
|
|
// Physically detach from the vehicle |
|
DetachFromVehicle(); |
|
|
|
// Mark that we're now disembarking |
|
SetPassengerState( PASSENGER_STATE_EXITING ); |
|
|
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_PASSENGER_SET_IDEAL_ENTRY_YAW: |
|
{ |
|
// Get the ideal facing to enter the vehicle |
|
QAngle vecEntryAngles; |
|
GetEntryPoint( m_nTransitionSequence, NULL, &vecEntryAngles ); |
|
GetOuter()->GetMotor()->SetIdealYaw( vecEntryAngles.y ); |
|
|
|
TaskComplete(); |
|
return; |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::StartTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle task running |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::RunTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_PASSENGER_ENTER_VEHICLE: |
|
{ |
|
// Correct for angular/spatial deviation |
|
Assert( GetSequence() == m_nTransitionSequence ); |
|
if ( GetSequence() != m_nTransitionSequence ) |
|
{ |
|
Warning("Corrected entrance animation on vehicle enter!\n"); |
|
GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE ); |
|
GetOuter()->GetNavigator()->ClearGoal(); |
|
SetTransitionSequence( m_nTransitionSequence ); |
|
} |
|
|
|
bool corrected = DoTransitionMovement(); |
|
|
|
// We must be done with the animation and in the correct position |
|
if ( corrected == false ) |
|
{ |
|
FinishEnterVehicle(); |
|
TaskComplete(); |
|
} |
|
} |
|
break; |
|
|
|
case TASK_PASSENGER_EXIT_VEHICLE: |
|
{ |
|
// Correct for angular/spatial deviation |
|
Assert( GetSequence() == m_nTransitionSequence ); |
|
if ( GetSequence() != m_nTransitionSequence ) |
|
{ |
|
Warning("Corrected exit animation on vehicle exit!\n"); |
|
GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE ); |
|
GetOuter()->GetNavigator()->ClearGoal(); |
|
SetTransitionSequence( m_nTransitionSequence ); |
|
} |
|
|
|
// Correct for angular/spatial deviation |
|
bool corrected = DoTransitionMovement(); |
|
|
|
// We must be done with the animation and in the correct position |
|
if ( corrected == false ) |
|
{ |
|
FinishExitVehicle(); |
|
TaskComplete(); |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::RunTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the blend amounts for position and angles, given a point in |
|
// time within a sequence |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::GetSequenceBlendAmount( float flCycle, float *posBlend, float *angBlend ) |
|
{ |
|
// Find positional blend, if requested |
|
if ( posBlend != NULL ) |
|
{ |
|
float flFrac = RemapValClamped( flCycle, m_flOriginStartFrame, m_flOriginEndFrame, 0.0f, 1.0f ); |
|
(*posBlend) = SimpleSpline( flFrac ); |
|
} |
|
|
|
// Find angular blend, if requested |
|
if ( angBlend != NULL ) |
|
{ |
|
float flFrac = RemapValClamped( flCycle, m_flAnglesStartFrame, m_flAnglesEndFrame, 0.0f, 1.0f ); |
|
(*angBlend) = SimpleSpline( flFrac ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the target destination for the entry animation |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::GetEntryTarget( Vector *vecOrigin, QAngle *vecAngles ) |
|
{ |
|
// Get the ultimate position we'll end up at |
|
m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPositionLocal( GetOuter(), vecOrigin, vecAngles ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the ideal position to be in to end up at the target at the |
|
// end of the animation. |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::GetTransitionAnimationIdeal( float flCycle, const Vector &vecTargetPos, const QAngle &vecTargetAngles, Vector *idealOrigin, QAngle *idealAngles ) |
|
{ |
|
// Get the position in time working backwards from our goal |
|
Vector vecDeltaPos; |
|
QAngle vecDeltaAngles; |
|
GetOuter()->GetSequenceMovement( GetSequence(), 1.0f, flCycle, vecDeltaPos, vecDeltaAngles ); |
|
|
|
// Rotate the delta by our local angles |
|
Vector vecPreDelta = vecDeltaPos; |
|
VectorRotate( vecPreDelta, vecTargetAngles, vecDeltaPos ); |
|
|
|
// Ideal origin |
|
if ( idealOrigin != NULL ) |
|
{ |
|
*idealOrigin = ( vecTargetPos + vecDeltaPos ); |
|
} |
|
|
|
// Ideal angles |
|
if ( idealAngles != NULL ) |
|
{ |
|
(*idealAngles).x = anglemod( vecTargetAngles.x + vecDeltaAngles.x ); |
|
(*idealAngles).y = anglemod( vecTargetAngles.y + vecDeltaAngles.y ); |
|
(*idealAngles).z = anglemod( vecTargetAngles.z + vecDeltaAngles.z ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// FIXME: This is basically a complete duplication of GetIntervalMovement |
|
// which doesn't remove the x and z components of the angles. This |
|
// should be consolidated to not replicate so much code! -- jdw |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::LocalIntervalMovement( float flInterval, bool &bMoveSeqFinished, Vector &newPosition, QAngle &newAngles ) |
|
{ |
|
CStudioHdr *pstudiohdr = GetOuter()->GetModelPtr(); |
|
if ( pstudiohdr == NULL ) |
|
return false; |
|
|
|
// Get our next cycle point |
|
float flNextCycle = GetNextCycleForInterval( GetSequence(), flInterval ); |
|
|
|
// Fix-up loops |
|
if ( ( GetOuter()->SequenceLoops() == false ) && flNextCycle > 1.0f ) |
|
{ |
|
flInterval = GetOuter()->GetCycle() / ( GetOuter()->GetSequenceCycleRate( GetSequence() ) * GetOuter()->GetPlaybackRate() ); |
|
flNextCycle = 1.0f; |
|
bMoveSeqFinished = true; |
|
} |
|
else |
|
{ |
|
bMoveSeqFinished = false; |
|
} |
|
|
|
Vector deltaPos; |
|
QAngle deltaAngles; |
|
|
|
// Find the delta position and delta angles for this sequence |
|
if ( Studio_SeqMovement( pstudiohdr, GetOuter()->GetSequence(), GetOuter()->GetCycle(), flNextCycle, GetOuter()->GetPoseParameterArray(), deltaPos, deltaAngles )) |
|
{ |
|
Vector vecPreDelta = deltaPos; |
|
VectorRotate( vecPreDelta, GetOuter()->GetLocalAngles(), deltaPos ); |
|
|
|
newPosition = GetLocalOrigin() + deltaPos; |
|
newAngles = GetLocalAngles() + deltaAngles; |
|
|
|
return true; |
|
} |
|
else |
|
{ |
|
newPosition = GetLocalOrigin(); |
|
newAngles = GetLocalAngles(); |
|
|
|
return false; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the next cycle point in a sequence for a given interval |
|
//----------------------------------------------------------------------------- |
|
float CAI_PassengerBehavior::GetNextCycleForInterval( int nSequence, float flInterval ) |
|
{ |
|
return GetOuter()->GetCycle() + flInterval * GetOuter()->GetSequenceCycleRate( GetSequence() ) * GetOuter()->GetPlaybackRate(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Draw debug information for the transitional movement |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::DrawDebugTransitionInfo( const Vector &vecIdealPos, const QAngle &vecIdealAngles, const Vector &vecAnimPos, const QAngle &vecAnimAngles ) |
|
{ |
|
// Debug info |
|
if ( GetPassengerState() == PASSENGER_STATE_ENTERING ) |
|
{ |
|
// Green - Ideal location |
|
Vector foo; |
|
m_hVehicle->EntityToWorldSpace( vecIdealPos, &foo ); |
|
NDebugOverlay::Cross3D( foo, 2, 0, 255, 0, true, 0.1f ); |
|
NDebugOverlay::Axis( foo, vecIdealAngles, 8, true, 0.1f ); |
|
|
|
// Blue - Actual location |
|
m_hVehicle->EntityToWorldSpace( vecAnimPos, &foo ); |
|
NDebugOverlay::Cross3D( foo, 2, 0, 0, 255, true, 0.1f ); |
|
NDebugOverlay::Axis( foo, vecAnimAngles, 8, true, 0.1f ); |
|
} |
|
else |
|
{ |
|
// Green - Ideal location |
|
NDebugOverlay::Cross3D( vecIdealPos, 4, 0, 255, 0, true, 0.1f ); |
|
NDebugOverlay::Axis( vecIdealPos, vecIdealAngles, 8, true, 0.1f ); |
|
|
|
// Blue - Actual location |
|
NDebugOverlay::Cross3D( vecAnimPos, 2, 0, 0, 255, true, 0.1f ); |
|
NDebugOverlay::Axis( vecAnimPos, vecAnimAngles, 8, true, 0.1f ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Local movement to enter or exit the vehicle |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::DoTransitionMovement( void ) |
|
{ |
|
// Get our animation's extrapolated end position |
|
Vector vecAnimPos; |
|
QAngle vecAnimAngles; |
|
float flInterval = GetOuter()->GetAnimTimeInterval(); |
|
bool bSequenceFinished; |
|
|
|
// Get the position we're moving to for this frame with our animation's motion |
|
if ( LocalIntervalMovement( flInterval, bSequenceFinished, vecAnimPos, vecAnimAngles ) ) |
|
{ |
|
// Get the position we'd ideally be in |
|
Vector vecIdealPos; |
|
QAngle vecIdealAngles; |
|
float flNextCycle = GetNextCycleForInterval( GetOuter()->GetSequence(), flInterval ); |
|
flNextCycle = clamp( flNextCycle, 0.0f, 1.0f ); |
|
GetTransitionAnimationIdeal( flNextCycle, m_vecTargetPosition, m_vecTargetAngles, &vecIdealPos, &vecIdealAngles ); |
|
|
|
// Get the amount of error to blend out |
|
float flPosBlend = 1.0f; |
|
float flAngBlend = 1.0f; |
|
GetSequenceBlendAmount( flNextCycle, &flPosBlend, &flAngBlend ); |
|
|
|
// Find the error between our position and our ideal |
|
Vector vecDelta = ( vecIdealPos - vecAnimPos ) * flPosBlend; |
|
|
|
QAngle vecDeltaAngles; |
|
vecDeltaAngles.x = AngleDiff( vecIdealAngles.x, vecAnimAngles.x ) * flAngBlend; |
|
vecDeltaAngles.y = AngleDiff( vecIdealAngles.y, vecAnimAngles.y ) * flAngBlend; |
|
vecDeltaAngles.z = AngleDiff( vecIdealAngles.z, vecAnimAngles.z ) * flAngBlend; |
|
|
|
// Factor in the error |
|
GetOuter()->SetLocalOrigin( vecAnimPos + vecDelta ); |
|
GetOuter()->SetLocalAngles( vecAnimAngles + vecDeltaAngles ); |
|
|
|
// Draw our debug information |
|
if ( passenger_debug_transition.GetBool() ) |
|
{ |
|
DrawDebugTransitionInfo( vecIdealPos, vecIdealAngles, vecAnimPos, vecAnimAngles ); |
|
} |
|
|
|
// We're done moving |
|
if ( bSequenceFinished ) |
|
return false; |
|
|
|
// We're still correcting out the error |
|
return true; |
|
} |
|
|
|
// There was no movement in the animation |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Translate normal schedules into vehicle schedules |
|
//----------------------------------------------------------------------------- |
|
int CAI_PassengerBehavior::TranslateSchedule( int scheduleType ) |
|
{ |
|
if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) |
|
{ |
|
// Always be seated when riding in the car! |
|
if ( scheduleType == SCHED_IDLE_STAND ) |
|
return SCHED_PASSENGER_IDLE; |
|
} |
|
|
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the velocity of the vehicle with respect to its orientation |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::GetLocalVehicleVelocity( Vector *pOut ) |
|
{ |
|
Vector velocity; |
|
m_hVehicle->GetVelocity( &velocity, NULL ); |
|
m_hVehicle->WorldToEntitySpace( m_hVehicle->GetAbsOrigin() + velocity, pOut ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gather conditions we can comment on or react to while riding in the vehicle |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::GatherVehicleStateConditions( void ) |
|
{ |
|
// Must have a vehicle to bother with this |
|
if ( m_hVehicle == NULL ) |
|
return; |
|
|
|
// Clear out transient conditions |
|
ClearCondition( COND_PASSENGER_HARD_IMPACT ); |
|
ClearCondition( COND_PASSENGER_ERRATIC_DRIVING ); |
|
ClearCondition( COND_PASSENGER_JOSTLE_SMALL ); |
|
ClearCondition( COND_PASSENGER_VEHICLE_STARTED ); |
|
ClearCondition( COND_PASSENGER_VEHICLE_STOPPED ); |
|
ClearCondition( COND_PASSENGER_PLAYER_ENTERED_VEHICLE ); |
|
ClearCondition( COND_PASSENGER_PLAYER_EXITED_VEHICLE ); |
|
|
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
if ( pPlayer ) |
|
{ |
|
if ( pPlayer->IsInAVehicle() && pPlayer->GetVehicle() == m_hVehicle->GetServerVehicle() ) |
|
{ |
|
if ( m_vehicleState.m_bPlayerInVehicle == false ) |
|
{ |
|
SetCondition( COND_PASSENGER_PLAYER_ENTERED_VEHICLE ); |
|
m_vehicleState.m_bPlayerInVehicle = true; |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_vehicleState.m_bPlayerInVehicle ) |
|
{ |
|
SetCondition( COND_PASSENGER_PLAYER_EXITED_VEHICLE ); |
|
m_vehicleState.m_bPlayerInVehicle = false; |
|
} |
|
} |
|
} |
|
|
|
// Get the vehicle's boost state |
|
if ( m_hVehicle->m_nBoostTimeLeft < 100.0f ) |
|
{ |
|
if ( m_vehicleState.m_bWasBoosting == false ) |
|
{ |
|
m_vehicleState.m_bWasBoosting = true; |
|
} |
|
} |
|
else |
|
{ |
|
m_vehicleState.m_bWasBoosting = false; |
|
} |
|
|
|
// Detect being overturned |
|
if ( m_hVehicle->IsOverturned() ) |
|
{ |
|
SetCondition( COND_PASSENGER_OVERTURNED ); |
|
|
|
if ( m_vehicleState.m_bWasOverturned == false ) |
|
{ |
|
m_vehicleState.m_bWasOverturned = true; |
|
} |
|
} |
|
else |
|
{ |
|
ClearCondition( COND_PASSENGER_OVERTURNED ); |
|
m_vehicleState.m_bWasOverturned = false; |
|
} |
|
|
|
// Get our local velocity |
|
Vector localVelocity; |
|
GetLocalVehicleVelocity( &localVelocity ); |
|
|
|
Vector deltaVelocity = ( localVelocity - m_vehicleState.m_vecLastLocalVelocity ); |
|
|
|
// Detect a sudden stop! |
|
if ( deltaVelocity.y < passenger_impact_response_threshold.GetFloat() ) |
|
{ |
|
SetCondition( COND_PASSENGER_HARD_IMPACT ); |
|
} |
|
else if ( fabs( deltaVelocity.x ) > 200.0f || fabs( deltaVelocity.z ) > 75.0f ) |
|
{ |
|
// The X axis represents lateral movement and the Z axis represents vertical movement{ |
|
SetCondition( COND_PASSENGER_ERRATIC_DRIVING ); |
|
} |
|
else if ( fabs( deltaVelocity.x ) > 50.0f || fabs( deltaVelocity.z ) > 25.0f ) |
|
{ |
|
// Lightly jostled |
|
SetCondition( COND_PASSENGER_JOSTLE_SMALL ); |
|
} |
|
|
|
// Get our speed |
|
float flSpeedSqr = localVelocity.LengthSqr(); |
|
|
|
// See if we've crossed over the threshold between movement to stillness |
|
if ( m_vehicleState.m_flLastSpeedSqr > STOPPED_VELOCITY_THRESHOLD_SQR && flSpeedSqr < STOPPED_VELOCITY_THRESHOLD_SQR ) |
|
{ |
|
SetCondition( COND_PASSENGER_VEHICLE_STOPPED ); |
|
} |
|
else if ( m_vehicleState.m_flLastSpeedSqr < STARTED_VELOCITY_THRESHOLD_SQR && flSpeedSqr > STARTED_VELOCITY_THRESHOLD_SQR ) |
|
{ |
|
// See if we've crossed over the threshold between stillness to movement |
|
SetCondition( COND_PASSENGER_VEHICLE_STARTED ); |
|
} |
|
|
|
// Save this as our last speed |
|
m_vehicleState.m_flLastSpeedSqr = flSpeedSqr; |
|
|
|
// Find our delta velocity from the last frame |
|
m_vehicleState.m_vecDeltaVelocity = ( localVelocity - m_vehicleState.m_vecLastLocalVelocity ); |
|
m_vehicleState.m_vecLastLocalVelocity = localVelocity; |
|
|
|
// Get our angular velocity |
|
Vector vecVelocity; |
|
AngularImpulse angVelocty; |
|
m_hVehicle->GetVelocity( &vecVelocity, &angVelocty ); |
|
QAngle angVel( angVelocty.x, angVelocty.y, angVelocty.z ); |
|
|
|
// Blend this into the old values |
|
m_vehicleState.m_vecLastAngles = ( m_vehicleState.m_vecLastAngles * 0.2f ) + ( angVel * 0.8f ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Do some pre-schedule clean-up |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::PrescheduleThink( void ) |
|
{ |
|
BaseClass::PrescheduleThink(); |
|
|
|
// If we're outside the vehicle, we need to turn this behavior off immediately |
|
if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE && HasCondition( COND_PASSENGER_CANCEL_ENTER ) ) |
|
{ |
|
// Clear out our passenger intent |
|
m_PassengerIntent = PASSENGER_INTENT_NONE; |
|
ClearCondition( COND_PASSENGER_CANCEL_ENTER ); |
|
|
|
// Stop pathfinding |
|
GetOuter()->GetNavigator()->ClearGoal(); |
|
|
|
// We're outside and have no intent to enter, so we're done |
|
Disable(); |
|
|
|
// This must be stomped to cause our behavior to relinquish control |
|
GetOuter()->ClearSchedule("Passenger enter canceled"); |
|
} |
|
|
|
#ifdef DEBUG |
|
if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) |
|
{ |
|
Vector vecSeatOrigin; |
|
QAngle vecSeatAngles; |
|
if ( m_hVehicle && m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatPositionLocal( GetOuter(), &vecSeatOrigin, &vecSeatAngles ) ) |
|
{ |
|
if ( ( GetLocalOrigin() - vecSeatOrigin ).LengthSqr() > Square( 0.1f ) ) |
|
{ |
|
Warning( "Passenger has strayed from seat position!\n" ); |
|
// GetOuter()->SetLocalOrigin( vecSeatOrigin ); |
|
// GetOuter()->SetLocalAngles( vecSeatAngles ); |
|
} |
|
} |
|
else |
|
{ |
|
Warning( "Passenger is in vehicle without a valid seat position! -- EJECTED\n" ); |
|
GetOuter()->SetParent( NULL ); |
|
Disable(); |
|
|
|
return; |
|
} |
|
} |
|
#endif // DEBUG |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gather conditions for our use in making decisions |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::GatherConditions( void ) |
|
{ |
|
if ( IsEnabled() == false ) |
|
return BaseClass::GatherConditions(); |
|
|
|
// Sense the state of the car |
|
GatherVehicleStateConditions(); |
|
|
|
BaseClass::GatherConditions(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet ) |
|
{ |
|
if ( m_hVehicle == NULL ) |
|
return; |
|
|
|
// Mark whether we're overturned or not |
|
bool bOverturned = m_hVehicle->IsOverturned(); |
|
criteriaSet.AppendCriteria( "vehicle_overturned", bOverturned ? "1" : "0" ); |
|
|
|
// Denote whether we're in the vehicle or not |
|
bool bInsideVehicle = ( GetPassengerState() == PASSENGER_STATE_INSIDE ); |
|
criteriaSet.AppendCriteria( "vehicle_inside", bInsideVehicle ? "1" : "0" ); |
|
|
|
// Note what angle we're at (extreme or normal) |
|
Vector vecUp( 0.0f, 0.0f, 1.0f ); |
|
Vector vecVehicleUp; |
|
m_hVehicle->GetVectors( NULL, NULL, &vecVehicleUp ); |
|
|
|
float flVehicleUp = DotProduct( vecVehicleUp, vecUp ); |
|
criteriaSet.AppendCriteria( "vehicle_tilt", UTIL_VarArgs( "%.2f", flVehicleUp ) ); |
|
|
|
// Set the vehicle's speed (necessary for certain types of movement judgments) |
|
float flVehicleSpeed = sqrt( m_vehicleState.m_flLastSpeedSqr ); |
|
criteriaSet.AppendCriteria( "vehicle_speed", UTIL_VarArgs( "%f", flVehicleSpeed ) ); |
|
|
|
// Whether or not the passenger is currently able to enter the vehicle (only accounts for locking really) |
|
bool bCanExitVehicle = ( m_hVehicle->NPC_CanExitVehicle( GetOuter(), true ) ); |
|
criteriaSet.AppendCriteria( "vehicle_can_exit", bCanExitVehicle ? "1" : "0" ); |
|
|
|
// Whether or not the passenger is currently able to exit the vehicle (only accounts for locking really) |
|
bool bCanEnterVehicle = ( m_hVehicle->NPC_CanEnterVehicle( GetOuter(), true ) ); |
|
criteriaSet.AppendCriteria( "vehicle_can_enter", bCanEnterVehicle ? "1" : "0" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Cache off our frame numbers from the sequence keyvalue blocks |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::CacheBlendTargets( void ) |
|
{ |
|
// Get the keyvalues for this sequence |
|
KeyValues *seqValues = GetOuter()->GetSequenceKeyValues( m_nTransitionSequence ); |
|
if ( seqValues == NULL ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
// Get the entry/exit subkeys |
|
KeyValues *blendValues = seqValues->FindKey( "entryexit_blend" ); |
|
if ( blendValues == NULL ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
// Find our frame range on this sequence |
|
int nMaxFrames = Studio_MaxFrame( GetOuter()->GetModelPtr(), m_nTransitionSequence, GetOuter()->GetPoseParameterArray() ); |
|
|
|
// Find a key by this name |
|
KeyValues *subKeys = blendValues->FindKey( ORIGIN_KEYNAME ); |
|
if ( subKeys ) |
|
{ |
|
// Retrieve our frame numbers |
|
m_flOriginStartFrame = subKeys->GetFloat( "startframe", 0.0f ); |
|
m_flOriginEndFrame = subKeys->GetFloat( "endframe", nMaxFrames ); |
|
|
|
// Convert to normalized values |
|
m_flOriginStartFrame = RemapValClamped( m_flOriginStartFrame, 0, nMaxFrames, 0.0f, 1.0f ); |
|
m_flOriginEndFrame = RemapValClamped( m_flOriginEndFrame, 0, nMaxFrames, 0.0f, 1.0f ); |
|
} |
|
|
|
// Find a key by this name |
|
subKeys = blendValues->FindKey( ANGLES_KEYNAME ); |
|
if ( subKeys ) |
|
{ |
|
// Retrieve our frame numbers |
|
m_flAnglesStartFrame = subKeys->GetFloat( "startframe", 0.0f ); |
|
m_flAnglesEndFrame = subKeys->GetFloat( "endframe", nMaxFrames ); |
|
|
|
// Convert to normalized values |
|
m_flAnglesStartFrame = RemapValClamped( m_flAnglesStartFrame, 0, nMaxFrames, 0.0f, 1.0f ); |
|
m_flAnglesEndFrame = RemapValClamped( m_flAnglesEndFrame, 0, nMaxFrames, 0.0f, 1.0f ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::SetTransitionSequence( int nSequence ) |
|
{ |
|
// We need to use the ACT_SCRIPT_CUSTOM_MOVE scenario for this type of custom anim |
|
m_nTransitionSequence = nSequence; |
|
GetOuter()->m_iszSceneCustomMoveSeq = AllocPooledString( GetOuter()->GetSequenceName( m_nTransitionSequence ) ); |
|
|
|
// Cache off our blending information at this point |
|
CacheBlendTargets(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::SpeakIfAllowed( AIConcept_t concept, const char *modifiers /*= NULL*/, bool bRespondingToPlayer /*= false*/, char *pszOutResponseChosen /*= NULL*/, size_t bufsize /*= 0*/ ) |
|
{ |
|
// FIXME: Store this cast off? |
|
CAI_PlayerAlly *pAlly = dynamic_cast<CAI_PlayerAlly *>(GetOuter()); |
|
if ( pAlly != NULL ) |
|
return pAlly->SpeakIfAllowed( concept, modifiers, bRespondingToPlayer, pszOutResponseChosen, bufsize ); |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Forces us to begin a dynamic scripted scene |
|
// Input : *lpszInteractionName - Name of the sequence we'll play |
|
// *pOther - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::ForceVehicleInteraction( const char *lpszInteractionName, CBaseCombatCharacter *pOther ) |
|
{ |
|
// Don't do this unless we're sitting in the cabin of the vehicle! |
|
if ( GetPassengerState() != PASSENGER_STATE_INSIDE ) |
|
return false; |
|
|
|
// Set a sequence and fire it off! |
|
GetOuter()->m_iszSceneCustomMoveSeq = AllocPooledString( lpszInteractionName ); |
|
GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE ); |
|
|
|
// Slam our schedule (very unsafe!) |
|
GetOuter()->SetSchedule( SCHED_PASSENGER_PLAY_SCRIPTED_ANIM ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fix up teleport event when in the vehicle |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ) |
|
{ |
|
//First, safely remove me from the vehicle |
|
if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE ) |
|
{ |
|
// Detach from the vehicle |
|
DetachFromVehicle(); |
|
FinishExitVehicle(); |
|
|
|
// Turn the behavior off |
|
GetOuter()->ClearSchedule( "ai_behavior_passenger: teleport while in vehicle" ); |
|
Disable(); |
|
} |
|
|
|
//Then allow the teleportation |
|
BaseClass::Teleport( newPosition, newAngles, newVelocity ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: We override this function because it can completely wreak havoc if |
|
// we're in the middle of a transition |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::ClearSchedule( const char *szReason ) |
|
{ |
|
// Cannot do this while we're transitioning, but it's also a bug because the code that called it was probably relying on this to work! |
|
if ( GetPassengerState() == PASSENGER_STATE_ENTERING || GetPassengerState() == PASSENGER_STATE_EXITING ) |
|
{ |
|
Warning("ClearSchedule rejected due to transitioning passenger: %s\n", szReason ); |
|
return; |
|
} |
|
|
|
// TODO: Even this will probably need more crafting depending on what we're doing in the vehicle |
|
// Otherwise allow it |
|
GetOuter()->ClearSchedule( szReason ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Dictate the terms for being interrupted by scripted schedules or scenes |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PassengerBehavior::IsInterruptable( void ) |
|
{ |
|
// NOTE: We should never be interrupted this way when in a car. This would effectively makes us go comatose if we |
|
// start a FACETO, MOVETO, or SEQUENCE command from a VCD. |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAI_PassengerBehavior::CancelEnterVehicle( void ) |
|
{ |
|
// Stop! |
|
if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE ) |
|
{ |
|
SetCondition( COND_PASSENGER_CANCEL_ENTER ); |
|
} |
|
} |
|
|
|
// ---------------------------------------------- |
|
// Custom AI declarations |
|
// ---------------------------------------------- |
|
|
|
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehavior ) |
|
{ |
|
DECLARE_ACTIVITY( ACT_PASSENGER_IDLE ) |
|
DECLARE_ACTIVITY( ACT_PASSENGER_RANGE_ATTACK1 ) |
|
|
|
DECLARE_CONDITION( COND_PASSENGER_HARD_IMPACT ) |
|
DECLARE_CONDITION( COND_PASSENGER_ENTERING ) |
|
DECLARE_CONDITION( COND_PASSENGER_EXITING ) |
|
DECLARE_CONDITION( COND_PASSENGER_VEHICLE_STARTED ) |
|
DECLARE_CONDITION( COND_PASSENGER_VEHICLE_STOPPED ) |
|
DECLARE_CONDITION( COND_PASSENGER_OVERTURNED ) |
|
DECLARE_CONDITION( COND_PASSENGER_CANCEL_ENTER ) |
|
DECLARE_CONDITION( COND_PASSENGER_ERRATIC_DRIVING ) |
|
DECLARE_CONDITION( COND_PASSENGER_PLAYER_ENTERED_VEHICLE ) |
|
DECLARE_CONDITION( COND_PASSENGER_PLAYER_EXITED_VEHICLE ) |
|
DECLARE_CONDITION( COND_PASSENGER_JOSTLE_SMALL ) |
|
|
|
DECLARE_TASK( TASK_PASSENGER_ENTER_VEHICLE ) |
|
DECLARE_TASK( TASK_PASSENGER_EXIT_VEHICLE ) |
|
DECLARE_TASK( TASK_PASSENGER_ATTACH_TO_VEHICLE ) |
|
DECLARE_TASK( TASK_PASSENGER_DETACH_FROM_VEHICLE ) |
|
DECLARE_TASK( TASK_PASSENGER_SET_IDEAL_ENTRY_YAW ) |
|
|
|
// FIXME: Move to companion |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_PASSENGER_ENTER_VEHICLE, |
|
|
|
" Tasks" |
|
" TASK_PASSENGER_SET_IDEAL_ENTRY_YAW 0" |
|
" TASK_FACE_IDEAL 0" |
|
" TASK_PASSENGER_ATTACH_TO_VEHICLE 0" |
|
" TASK_PASSENGER_ENTER_VEHICLE 0" |
|
"" |
|
" Interrupts" |
|
" COND_NO_CUSTOM_INTERRUPTS" |
|
) |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_PASSENGER_EXIT_VEHICLE, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_IDLE" |
|
" TASK_PASSENGER_DETACH_FROM_VEHICLE 0" |
|
" TASK_WAIT 0.1" // We must wait one tick for us to start being updated |
|
" TASK_PASSENGER_EXIT_VEHICLE 0" |
|
"" |
|
" Interrupts" |
|
" COND_NO_CUSTOM_INTERRUPTS" |
|
" COND_TASK_FAILED" |
|
) |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_PASSENGER_IDLE, |
|
|
|
" Tasks" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_WAIT 2" |
|
"" |
|
" Interrupts" |
|
" COND_PROVOKED" |
|
" COND_NEW_ENEMY" |
|
" COND_CAN_RANGE_ATTACK1" |
|
" COND_CAN_MELEE_ATTACK1" |
|
" COND_PASSENGER_EXITING" |
|
" COND_HEAR_DANGER" |
|
) |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_PASSENGER_PLAY_SCRIPTED_ANIM, |
|
|
|
" Tasks" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SCRIPT_CUSTOM_MOVE" |
|
"" |
|
" Interrupts" |
|
" COND_PASSENGER_HARD_IMPACT" |
|
) |
|
|
|
AI_END_CUSTOM_SCHEDULE_PROVIDER() |
|
}
|
|
|