//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= // // Purpose: Behavior for NPCs riding in cars (with boys) // //============================================================================= #include "cbase.h" #include "ai_motor.h" #include "bone_setup.h" #include "vehicle_base.h" #include "entityblocker.h" #include "ai_behavior_passenger.h" // Custom activities int ACT_PASSENGER_IDLE; int ACT_PASSENGER_RANGE_ATTACK1; ConVar passenger_debug_transition( "passenger_debug_transition", "0" ); #define ORIGIN_KEYNAME "origin" #define ANGLES_KEYNAME "angles" BEGIN_DATADESC( CAI_PassengerBehavior ) 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 ), 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_flLastSpeed, FIELD_FLOAT ), END_DATADESC(); //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CAI_PassengerBehavior::CAI_PassengerBehavior( void ) : m_PassengerState( PASSENGER_STATE_OUTSIDE ), m_hVehicle( NULL ), m_bEnabled( false ), m_PassengerIntent( PASSENGER_INTENT_NONE ), m_nTransitionSequence( -1 ) { } #ifdef HL2_EPISODIC //----------------------------------------------------------------------------- // Purpose: Enables the behavior to run //----------------------------------------------------------------------------- void CAI_PassengerBehavior::Enable( CPropJeepEpisodic *pVehicle ) { if ( m_bEnabled ) return; m_bEnabled = true; m_hVehicle = pVehicle; SetPassengerState( PASSENGER_STATE_OUTSIDE ); } #endif //HL2_EPISODIC //----------------------------------------------------------------------------- // Purpose: Stops the behavior from being run //----------------------------------------------------------------------------- void CAI_PassengerBehavior::Disable( void ) { m_bEnabled = false; } //----------------------------------------------------------------------------- // Purpose: Starts the process of entering a vehicle //----------------------------------------------------------------------------- void CAI_PassengerBehavior::EnterVehicle( void ) { // Stop trying to exit the vehicle if ( GetPassengerState() != PASSENGER_STATE_INSIDE ) { m_PassengerIntent = PASSENGER_INTENT_ENTER; } if ( GetPassengerState() != PASSENGER_STATE_OUTSIDE ) return; SetCondition( COND_ENTERING_VEHICLE ); } //----------------------------------------------------------------------------- // Purpose: Starts the process of exiting a vehicle //----------------------------------------------------------------------------- void CAI_PassengerBehavior::ExitVehicle( void ) { // Must have a valid vehicle if ( m_hVehicle == NULL ) return; // We're not even entering the car yet, so just abandon the attempt if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE && m_PassengerIntent == PASSENGER_INTENT_ENTER ) { ClearCondition( COND_ENTERING_VEHICLE ); SetCondition( COND_CANCEL_ENTER_VEHICLE ); } // Mark our intent as wanting to exit again m_PassengerIntent = PASSENGER_INTENT_EXIT; // Must be in the seat if ( GetPassengerState() != PASSENGER_STATE_INSIDE ) return; // // Everything below this point will still attempt to exit the vehicle, once able // // Cannot exit while we're upside down if ( m_hVehicle->IsOverturned() ) return; // Interrupt what we're doing SetCondition( COND_EXITING_VEHICLE ); } //----------------------------------------------------------------------------- // 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() ); } //----------------------------------------------------------------------------- // 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: 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 ); 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; } // Get physics messages from our attached physics object #ifdef HL2_EPISODIC m_hVehicle->AddPhysicsChild( GetOuter() ); #endif //HL2_EPISODIC 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 ); GetMotor()->SetYawLocked( false ); // Re-enable the physical collisions for this NPC IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject(); if ( pPhysObj != NULL ) { pPhysObj->EnableCollisions( true ); } #ifdef HL2_EPISODIC m_hVehicle->RemovePhysicsChild( GetOuter() ); #endif //HL2_EPISODIC m_hVehicle->NPC_RemovePassenger( GetOuter() ); m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), (IsPassengerHostile()==false) ); // If we've not been told to enter immediately, we're done if ( m_PassengerIntent == PASSENGER_INTENT_EXIT ) { m_PassengerIntent = PASSENGER_INTENT_NONE; m_hVehicle = NULL; Disable(); } SetPassengerState( PASSENGER_STATE_OUTSIDE ); // Stop our custom move sequence GetOuter()->m_iszSceneCustomMoveSeq = NULL_STRING; } //----------------------------------------------------------------------------- // 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_ENTERING_VEHICLE ) ); GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_EXITING_VEHICLE ) ); GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_CANCEL_ENTER_VEHICLE ) ); } 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: Overrides the schedule selection // Output : int - Schedule to play //----------------------------------------------------------------------------- int CAI_PassengerBehavior::SelectSchedule( void ) { // Cancelling our entrance if ( HasCondition( COND_CANCEL_ENTER_VEHICLE ) ) { // Clear out our passenger intent m_PassengerIntent = PASSENGER_INTENT_NONE; ClearCondition( COND_CANCEL_ENTER_VEHICLE ); Disable(); return BaseClass::SelectSchedule(); } // Exiting schedule if ( HasCondition( COND_ENTERING_VEHICLE ) || m_PassengerIntent == PASSENGER_INTENT_ENTER ) { ClearCondition( COND_ENTERING_VEHICLE ); return SCHED_PASSENGER_ENTER_VEHICLE; } // Exiting schedule if ( HasCondition( COND_EXITING_VEHICLE ) || m_PassengerIntent == PASSENGER_INTENT_EXIT ) { ClearCondition( COND_EXITING_VEHICLE ); return SCHED_PASSENGER_EXIT_VEHICLE; } // Idle if ( GetPassengerState() == PASSENGER_STATE_INSIDE ) return SCHED_IDLE_STAND; return BaseClass::SelectSchedule(); } //----------------------------------------------------------------------------- // 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 CTraceFilterSkipTwoEntities 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: 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 ) { // 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 ); float flDownDelta = 64.0f; float flUpDelta = 16.0f; Vector vecGroundPos; if ( FindGroundAtPosition( GetOuter()->GetAbsOrigin() + vecDeltaPos, flUpDelta, flDownDelta, &vecGroundPos ) == false ) return false; if ( vecExitPoint != NULL ) { *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 true; } //----------------------------------------------------------------------------- // 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 ) { // Trace a hull between where we are and the exit point trace_t tr; CTraceFilterSkipTwoEntities skipFilter( GetOuter(), m_hVehicle, COLLISION_GROUP_NONE ); UTIL_TraceHull( vecStartPos, vecEndPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), 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( vecStartPos, vecEndPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 64, 2.0f ); } return false; } Vector vecGroundPos; // Trace down to the ground if ( FindGroundAtPosition( tr.endpos, 16.0f, 64.0f, &vecGroundPos ) ) { if ( passenger_debug_transition.GetBool() ) { NDebugOverlay::SweptBox( vecStartPos, vecGroundPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 0, 255, 0, 64, 2.0f ); } return true; } // We failed if ( passenger_debug_transition.GetBool() ) { NDebugOverlay::SweptBox( vecStartPos, vecGroundPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 64, 2.0f ); } 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_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 = 99999999999.9f; 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 GetEntryPoint( nSequence, &vecStartPos ); // 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 GetExitPoint( nSequence, &vecEndPos ); // 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 ) { // Find the exit activity to use int nSequence = FindExitSequence(); if ( nSequence == -1 ) return false; // We have to do this specially because the activities are not named SetTransitionSequence( nSequence ); // Cannot exit while we're upside down if ( m_hVehicle->IsOverturned() ) return false; // Get the exit position Vector vecGroundPos; if ( GetExitPoint( m_nTransitionSequence, &vecGroundPos, &m_vecTargetAngles ) == false ) return false; // 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. //----------------------------------------------------------------------------- void CAI_PassengerBehavior::GetEntryPoint( int nSequence, Vector *vecEntryPoint, QAngle *vecEntryAngles ) { if ( vecEntryPoint == NULL ) return; // 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; // Put it into the worldspace m_hVehicle->EntityToWorldSpace( vecTargetPos, vecEntryPoint ); 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() ) { 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 ); } } } //----------------------------------------------------------------------------- // Purpose: HACK: Stops the entity from translating oddly when they attach to // the vehicle //----------------------------------------------------------------------------- void CAI_PassengerBehavior::FixInterpolation( void ) { // Fix an interpolation problem StepSimulationData *step = (StepSimulationData *) GetOuter()->GetDataObject( STEPSIMULATION ); if ( step != NULL ) { memset( step, 0, sizeof(StepSimulationData) ); } // Also add the flag to suppress interpolation GetOuter()->AddEffects( EF_NOINTERP ); } //----------------------------------------------------------------------------- // 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 entrace animation before this point! Assert( m_nTransitionSequence != -1 ); // Start us playing the correct sequence GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE ); SetPassengerState( PASSENGER_STATE_ENTERING ); } break; case TASK_PASSENGER_EXIT_VEHICLE: { // You must have set your entrace animation before this point! Assert( m_nTransitionSequence != -1 ); // Start us playing the correct sequence GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE ); SetPassengerState( PASSENGER_STATE_EXITING ); } break; case TASK_PASSENGER_ATTACH_TO_VEHICLE: { // Parent to the vehicle GetOuter()->SetParent( m_hVehicle ); GetOuter()->SetGroundEntity( m_hVehicle ); FixInterpolation(); // 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 ); TaskComplete(); } break; case TASK_PASSENGER_DETACH_FROM_VEHICLE: { // Place an entity blocker where we're going to go if ( ReserveExitPoint() == false ) { OnExitVehicleFailed(); GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_IDLE ); TaskFail("Failed to find valid exit point\n"); return; } // Detach from the parent GetOuter()->SetParent( NULL ); GetOuter()->SetMoveType( MOVETYPE_STEP ); FixInterpolation(); TaskComplete(); } break; case TASK_PASSENGER_SET_IDEAL_ENTRY_YAW: { // Get the ideal facing to enter the vehicle Vector vecEntryPoint; QAngle vecEntryAngles; GetEntryPoint( m_nTransitionSequence, &vecEntryPoint, &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 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 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 *idealOrigin = ( vecTargetPos + vecDeltaPos ); // Ideal angles (*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 ) { // Always be seating when riding in the car if ( scheduleType == SCHED_IDLE_STAND && GetPassengerState() == PASSENGER_STATE_INSIDE ) 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; // 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_VEHICLE_OVERTURNED ); if ( m_vehicleState.m_bWasOverturned == false ) { m_vehicleState.m_bWasOverturned = true; } } else { ClearCondition( COND_VEHICLE_OVERTURNED ); m_vehicleState.m_bWasOverturned = false; } // Get our local velocity Vector localVelocity; GetLocalVehicleVelocity( &localVelocity ); // 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: Gather conditions for our use in making decisions //----------------------------------------------------------------------------- void CAI_PassengerBehavior::GatherConditions( void ) { // Sense the state of the car GatherVehicleStateConditions(); return BaseClass::GatherConditions(); } //----------------------------------------------------------------------------- // 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 = MAKE_STRING( GetOuter()->GetSequenceName( m_nTransitionSequence ) ); // Cache off our blending information at this point CacheBlendTargets(); } // ---------------------------------------------- // Custom AI declarations // ---------------------------------------------- AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehavior ) { DECLARE_ACTIVITY( ACT_PASSENGER_IDLE ) DECLARE_ACTIVITY( ACT_PASSENGER_RANGE_ATTACK1 ) DECLARE_CONDITION( COND_VEHICLE_HARD_IMPACT ) DECLARE_CONDITION( COND_ENTERING_VEHICLE ) DECLARE_CONDITION( COND_EXITING_VEHICLE ) DECLARE_CONDITION( COND_VEHICLE_OVERTURNED ) DECLARE_CONDITION( COND_CANCEL_ENTER_VEHICLE ) 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" ) DEFINE_SCHEDULE ( SCHED_PASSENGER_EXIT_VEHICLE, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_IDLE" " TASK_STOP_MOVING 0" " TASK_PASSENGER_DETACH_FROM_VEHICLE 0" " TASK_PASSENGER_EXIT_VEHICLE 0" "" " Interrupts" " COND_TASK_FAILED" ) DEFINE_SCHEDULE ( SCHED_PASSENGER_IDLE, " Tasks" " TASK_STOP_MOVING 0" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_IDLE" " TASK_WAIT_RANDOM 3" "" " Interrupts" " COND_PROVOKED" " COND_NEW_ENEMY" " COND_CAN_RANGE_ATTACK1" " COND_CAN_MELEE_ATTACK1" ) AI_END_CUSTOM_SCHEDULE_PROVIDER() }