//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // @TODO (toml 06-26-02): The entry points in this file need to be organized //=============================================================================// #include "cbase.h" #include // for FLT_MAX #include "animation.h" // for NOMOTION #include "collisionutils.h" #include "ndebugoverlay.h" #include "isaverestore.h" #include "saverestore_utlvector.h" #include "ai_navigator.h" #include "ai_node.h" #include "ai_route.h" #include "ai_routedist.h" #include "ai_waypoint.h" #include "ai_pathfinder.h" #include "ai_link.h" #include "ai_memory.h" #include "ai_motor.h" #include "ai_localnavigator.h" #include "ai_moveprobe.h" #include "ai_hint.h" #include "BasePropDoor.h" #include "props.h" #include "physics_npc_solver.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" const char * g_ppszGoalTypes[] = { "GOALTYPE_NONE", "GOALTYPE_TARGETENT", "GOALTYPE_ENEMY", "GOALTYPE_PATHCORNER", "GOALTYPE_LOCATION", "GOALTYPE_LOCATION_NEAREST_NODE", "GOALTYPE_FLANK", "GOALTYPE_COVER", }; #define AIGetGoalTypeText(type) (g_ppszGoalTypes[(type)]) ConVar ai_vehicle_avoidance("ai_vehicle_avoidance", "1", FCVAR_CHEAT ); #ifdef DEBUG_AI_NAVIGATION ConVar ai_debug_nav("ai_debug_nav", "0"); #endif #ifdef DEBUG ConVar ai_test_nav_failure_handling("ai_test_nav_failure_handling", "0"); int g_PathFailureCounter; int g_MoveFailureCounter; #define ShouldTestFailPath() ( ai_test_nav_failure_handling.GetBool() && g_PathFailureCounter++ % 20 == 0 ) #define ShouldTestFailMove() ( ai_test_nav_failure_handling.GetBool() && g_MoveFailureCounter++ % 100 == 0 ) #else #define ShouldTestFailPath() (0) #define ShouldTestFailMove() (0) #endif //----------------------------------------------------------------------------- #ifdef DEBUG bool g_fTestSteering = 0; #endif //----------------------------------------------------------------------------- class CAI_NavInHintGroupFilter : public INearestNodeFilter { public: CAI_NavInHintGroupFilter( string_t iszGroup = NULL_STRING ) : m_iszGroup( iszGroup ) { } bool IsValid( CAI_Node *pNode ) { if ( !pNode->GetHint() ) { return false; } if ( pNode->GetHint()->GetGroup() != m_iszGroup ) { return false; } return true; } bool ShouldContinue() { return true; } string_t m_iszGroup; }; //----------------------------------------------------------------------------- const Vector AIN_NO_DEST( FLT_MAX, FLT_MAX, FLT_MAX ); #define NavVecToString(v) ((v == AIN_NO_DEST) ? "AIN_NO_DEST" : VecToString(v)) #define FLIER_CUT_CORNER_DIST 16 // 8 means the npc's bounding box is contained without the box of the node in WC #define NAV_STOP_MOVING_TOLERANCE 6 // Goal tolerance for TASK_STOP_MOVING stopping paths //----------------------------------------------------------------------------- // class CAI_Navigator //----------------------------------------------------------------------------- BEGIN_SIMPLE_DATADESC( CAI_Navigator ) DEFINE_FIELD( m_navType, FIELD_INTEGER ), // m_pMotor // m_pMoveProbe // m_pLocalNavigator // m_pAINetwork DEFINE_EMBEDDEDBYREF( m_pPath ), // m_pClippedWaypoints (not saved) // m_flTimeClipped (not saved) // m_PreviousMoveActivity (not saved) // m_PreviousArrivalActivity (not saved) DEFINE_FIELD( m_bValidateActivitySpeed, FIELD_BOOLEAN ), DEFINE_FIELD( m_bCalledStartMove, FIELD_BOOLEAN ), DEFINE_FIELD( m_fNavComplete, FIELD_BOOLEAN ), DEFINE_FIELD( m_bNotOnNetwork, FIELD_BOOLEAN ), DEFINE_FIELD( m_bLastNavFailed, FIELD_BOOLEAN ), DEFINE_FIELD( m_flNextSimplifyTime, FIELD_TIME ), DEFINE_FIELD( m_bForcedSimplify, FIELD_BOOLEAN ), DEFINE_FIELD( m_flLastSuccessfulSimplifyTime, FIELD_TIME ), DEFINE_FIELD( m_flTimeLastAvoidanceTriangulate, FIELD_TIME ), DEFINE_FIELD( m_timePathRebuildMax, FIELD_FLOAT ), DEFINE_FIELD( m_timePathRebuildDelay, FIELD_FLOAT ), DEFINE_FIELD( m_timePathRebuildFail, FIELD_TIME ), DEFINE_FIELD( m_timePathRebuildNext, FIELD_TIME ), DEFINE_FIELD( m_fRememberStaleNodes, FIELD_BOOLEAN ), DEFINE_FIELD( m_bNoPathcornerPathfinds, FIELD_BOOLEAN ), // m_fPeerMoveWait (think transient) // m_hPeerWaitingOn (peer move fields do not need to be saved, tied to current schedule and path, which are not saved) // m_PeerWaitMoveTimer (ibid) // m_PeerWaitClearTimer(ibid) // m_NextSidestepTimer (ibid) DEFINE_FIELD( m_hBigStepGroundEnt, FIELD_EHANDLE ), DEFINE_FIELD( m_hLastBlockingEnt, FIELD_EHANDLE ), // m_vPosBeginFailedSteer (reset on load) // m_timeBeginFailedSteer (reset on load) // m_nNavFailCounter (reset on load) // m_flLastNavFailTime (reset on load) END_DATADESC() //----------------------------------------------------------------------------- CAI_Navigator::CAI_Navigator(CAI_BaseNPC *pOuter) : BaseClass(pOuter) { m_pPath = new CAI_Path; m_pAINetwork = NULL; m_bNotOnNetwork = false; m_flNextSimplifyTime = 0; m_flLastSuccessfulSimplifyTime = -1; m_pClippedWaypoints = new CAI_WaypointList; m_flTimeClipped = -1; m_bValidateActivitySpeed = true; m_bCalledStartMove = false; // ---------------------------- m_navType = NAV_GROUND; m_fNavComplete = false; m_bLastNavFailed = false; // ---------------------------- m_PeerWaitMoveTimer.Set(0.25); // 2 thinks m_PeerWaitClearTimer.Set(3.0); m_NextSidestepTimer.Set(5.0); m_vPosBeginFailedSteer = vec3_invalid; m_timeBeginFailedSteer = FLT_MAX; m_flTimeLastAvoidanceTriangulate = -1; // ---------------------------- m_bNoPathcornerPathfinds = false; m_fRememberStaleNodes = true; m_pMotor = NULL; m_pMoveProbe = NULL; m_pLocalNavigator = NULL; m_nNavFailCounter = 0; m_flLastNavFailTime = -1; } //----------------------------------------------------------------------------- void CAI_Navigator::Init( CAI_Network *pNetwork ) { m_pMotor = GetOuter()->GetMotor(); m_pMoveProbe = GetOuter()->GetMoveProbe(); m_pLocalNavigator = GetOuter()->GetLocalNavigator(); m_pAINetwork = pNetwork; } //----------------------------------------------------------------------------- CAI_Navigator::~CAI_Navigator() { delete m_pPath; m_pClippedWaypoints->RemoveAll(); delete m_pClippedWaypoints; } //----------------------------------------------------------------------------- const short AI_NAVIGATOR_SAVE_VERSION = 1; void CAI_Navigator::Save( ISave &save ) { save.WriteShort( &AI_NAVIGATOR_SAVE_VERSION ); CUtlVector minPathArray; AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint ) { if ( ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP ) ) { CAI_WaypointList minCompletionPath; if ( GetStoppingPath( &minCompletionPath ) && !minCompletionPath.IsEmpty() ) { AI_Waypoint_t *pCurrent = minCompletionPath.GetLast(); while ( pCurrent ) { minPathArray.AddToTail( *pCurrent ); pCurrent = pCurrent->GetPrev(); } minCompletionPath.RemoveAll(); } } } SaveUtlVector( &save, &minPathArray, FIELD_EMBEDDED ); } //----------------------------------------------------------------------------- void CAI_Navigator::Restore( IRestore &restore ) { short version = restore.ReadShort(); if ( version != AI_NAVIGATOR_SAVE_VERSION ) return; CUtlVector minPathArray; RestoreUtlVector( &restore, &minPathArray, FIELD_EMBEDDED ); if ( minPathArray.Count() ) { for ( int i = 0; i < minPathArray.Count(); i++ ) { m_pClippedWaypoints->PrependWaypoint( minPathArray[i].GetPos(), minPathArray[i].NavType(), ( minPathArray[i].Flags() & ~bits_WP_TO_PATHCORNER ), minPathArray[i].flYaw ); } m_flTimeClipped = gpGlobals->curtime + 1000; // time passes between restore and onrestore } } //----------------------------------------------------------------------------- bool CAI_Navigator::ActivityIsLocomotive( Activity activity ) { // FIXME: should be calling HasMovement() for a sequence return ( activity > ACT_IDLE ); } //----------------------------------------------------------------------------- CAI_Pathfinder *CAI_Navigator::GetPathfinder() { return GetOuter()->GetPathfinder(); } //----------------------------------------------------------------------------- const CAI_Pathfinder *CAI_Navigator::GetPathfinder() const { return GetOuter()->GetPathfinder(); } //----------------------------------------------------------------------------- CBaseEntity *CAI_Navigator::GetNavTargetEntity() { if ( GetGoalType() == GOALTYPE_ENEMY || GetGoalType() == GOALTYPE_TARGETENT ) return GetOuter()->GetNavTargetEntity(); return GetPath()->GetTarget(); } //----------------------------------------------------------------------------- void CAI_Navigator::TaskMovementComplete() { GetOuter()->TaskMovementComplete(); } //----------------------------------------------------------------------------- float CAI_Navigator::MaxYawSpeed() { return GetOuter()->MaxYawSpeed(); } //----------------------------------------------------------------------------- void CAI_Navigator::SetSpeed( float fl ) { GetOuter()->m_flSpeed = fl; } //----------------------------------------------------------------------------- bool CAI_Navigator::FindPath( const AI_NavGoal_t &goal, unsigned flags ) { CAI_Path *pPath = GetPath(); MARK_TASK_EXPENSIVE(); // Clear out previous state if ( flags & AIN_CLEAR_PREVIOUS_STATE ) pPath->Clear(); else if ( flags & AIN_CLEAR_TARGET ) pPath->ClearTarget(); // Set the activity if ( goal.activity != AIN_DEF_ACTIVITY ) pPath->SetMovementActivity( goal.activity ); else if ( pPath->GetMovementActivity() == ACT_INVALID ) pPath->SetMovementActivity( ( GetOuter()->GetState() == NPC_STATE_COMBAT ) ? ACT_RUN : ACT_WALK ); // Set the tolerance if ( goal.tolerance == AIN_HULL_TOLERANCE ) pPath->SetGoalTolerance( GetHullWidth() ); else if ( goal.tolerance != AIN_DEF_TOLERANCE ) pPath->SetGoalTolerance( goal.tolerance ); else if (pPath->GetGoalTolerance() == 0 ) pPath->SetGoalTolerance( GetOuter()->GetDefaultNavGoalTolerance() ); if (pPath->GetGoalTolerance() < 0.1 ) DevMsg( GetOuter(), "Suspicious navigation goal tolerance specified\n"); pPath->SetWaypointTolerance( GetHullWidth() * 0.5 ); pPath->SetGoalType( GOALTYPE_NONE ); // avoids a spurious warning about setting the goal type twice pPath->SetGoalType( goal.type ); pPath->SetGoalFlags( goal.flags ); CBaseEntity *pPathTarget = goal.pTarget; if ((goal.type == GOALTYPE_TARGETENT) || (goal.type == GOALTYPE_ENEMY)) { // Guarantee that the path target if (goal.type == GOALTYPE_TARGETENT) pPathTarget = GetTarget(); else pPathTarget = GetEnemy(); Assert( goal.pTarget == AIN_DEF_TARGET || goal.pTarget == pPathTarget ); // Set the goal offset if ( goal.dest != AIN_NO_DEST ) pPath->SetTargetOffset( goal.dest ); // We're not setting the goal position here because // it's going to be computed + set in DoFindPath. } else { // When our goaltype is position based, we have to set // the goal position here because it won't get set during DoFindPath(). if ( goal.dest != AIN_NO_DEST ) pPath->ResetGoalPosition( goal.dest ); else if ( goal.destNode != AIN_NO_NODE ) pPath->ResetGoalPosition( GetNodePos( goal.destNode ) ); } if ( pPathTarget > AIN_DEF_TARGET ) { pPath->SetTarget( pPathTarget ); } pPath->ClearWaypoints(); bool result = FindPath( ( flags & AIN_NO_PATH_TASK_FAIL ) == 0 ); if ( result == false ) { if ( flags & AIN_DISCARD_IF_FAIL ) pPath->Clear(); else pPath->SetGoalType( GOALTYPE_NONE ); } else { if ( goal.arrivalActivity != AIN_DEF_ACTIVITY && goal.arrivalActivity > ACT_RESET ) { pPath->SetArrivalActivity( goal.arrivalActivity ); } else if ( goal.arrivalSequence != -1 ) { pPath->SetArrivalSequence( goal.arrivalSequence ); } // Initialize goal facing direction // FIXME: This is a poor way to initialize the arrival direction, apps calling SetGoal() // should do this themselves, and/or it should be part of AI_NavGoal_t // FIXME: A number of calls to SetGoal() building routes to their enemy but don't set the flags! if (goal.type == GOALTYPE_ENEMY) { pPath->SetGoalDirection( pPathTarget ); pPath->SetGoalSpeed( pPathTarget ); } else { pPath->SetGoalDirection( pPath->ActualGoalPosition() - GetAbsOrigin() ); } } return result; } ConVar ai_navigator_generate_spikes( "ai_navigator_generate_spikes", "0" ); ConVar ai_navigator_generate_spikes_strength( "ai_navigator_generate_spikes_strength", "8" ); //----------------------------------------------------------------------------- bool CAI_Navigator::SetGoal( const AI_NavGoal_t &goal, unsigned flags ) { // Queue this up if we're in the middle of a frame if ( PostFrameNavigationSystem()->IsGameFrameRunning() ) { // Send off the query for queuing PostFrameNavigationSystem()->EnqueueEntityNavigationQuery( GetOuter(), CreateFunctor( this, &CAI_Navigator::SetGoal, RefToVal( goal ), flags ) ); // Complete immediately if we're waiting on that // FIXME: This will probably cause a lot of subtle little nuisances... if ( ( flags & AIN_NO_PATH_TASK_FAIL ) == 0 || GetOuter()->IsCurTaskContinuousMove() ) { TaskComplete(); } // For now, always succeed -- we need to deal with failures on the next frame return true; } CAI_Path *pPath = GetPath(); OnNewGoal(); // Clear out previous state if ( flags & AIN_CLEAR_PREVIOUS_STATE ) { ClearPath(); } if ( GetOuter()->IsCurTaskContinuousMove() || ai_post_frame_navigation.GetBool() ) { flags |= AIN_NO_PATH_TASK_FAIL; } bool result = FindPath( goal, flags ); if ( result == false ) { DbgNavMsg( GetOuter(), "Failed to pathfind to nav goal:\n" ); DbgNavMsg1( GetOuter(), " Type: %s\n", AIGetGoalTypeText( goal.type) ); DbgNavMsg1( GetOuter(), " Dest: %s\n", NavVecToString( goal.dest ) ); DbgNavMsg1( GetOuter(), " Dest node: %p\n", goal.destNode ); DbgNavMsg1( GetOuter(), " Target: %p\n", goal.pTarget ); if ( flags & AIN_DISCARD_IF_FAIL ) ClearPath(); } else { DbgNavMsg( GetOuter(), "New goal set:\n" ); DbgNavMsg1( GetOuter(), " Type: %s\n", AIGetGoalTypeText( goal.type) ); DbgNavMsg1( GetOuter(), " Dest: %s\n", NavVecToString( goal.dest ) ); DbgNavMsg1( GetOuter(), " Dest node: %p\n", goal.destNode ); DbgNavMsg1( GetOuter(), " Target: %p\n", goal.pTarget ); DbgNavMsg1( GetOuter(), " Tolerance: %.1f\n", GetPath()->GetGoalTolerance() ); DbgNavMsg1( GetOuter(), " Waypoint tol: %.1f\n", GetPath()->GetWaypointTolerance() ); DbgNavMsg1( GetOuter(), " Activity: %s\n", GetOuter()->GetActivityName(GetPath()->GetMovementActivity()) ); DbgNavMsg1( GetOuter(), " Arrival act: %s\n", GetOuter()->GetActivityName(GetPath()->GetArrivalActivity()) ); DbgNavMsg1( GetOuter(), " Arrival seq: %d\n", GetPath()->GetArrivalSequence() ); DbgNavMsg1( GetOuter(), " Goal dir: %s\n", NavVecToString( GetPath()->GetGoalDirection(GetAbsOrigin())) ); // Set our ideal yaw. This has to be done *after* finding the path, // because the goal position may not be set until then if ( goal.flags & AIN_YAW_TO_DEST ) { DbgNavMsg( GetOuter(), " Yaw to dest\n" ); GetMotor()->SetIdealYawToTarget( pPath->ActualGoalPosition() ); } SimplifyPath( true, goal.maxInitialSimplificationDist ); } return result; } //----------------------------------------------------------------------------- // Change the target of the path //----------------------------------------------------------------------------- bool CAI_Navigator::SetGoalTarget( CBaseEntity *pEntity, const Vector &offset ) { OnNewGoal(); CAI_Path *pPath = GetPath(); pPath->SetTargetOffset( offset ); pPath->SetTarget( pEntity ); pPath->ClearWaypoints(); return FindPath( !GetOuter()->IsCurTaskContinuousMove() ); } //----------------------------------------------------------------------------- bool CAI_Navigator::SetRadialGoal( const Vector &destination, const Vector ¢er, float radius, float arc, float stepDist, bool bClockwise, bool bAirRoute) { DbgNavMsg( GetOuter(), "Set radial goal\n" ); OnNewGoal(); GetPath()->SetGoalType(GOALTYPE_LOCATION); GetPath()->SetWaypoints( GetPathfinder()->BuildRadialRoute( GetLocalOrigin(), center, destination, radius, arc, stepDist, bClockwise, GetPath()->GetGoalTolerance(), bAirRoute ), true); GetPath()->SetGoalTolerance( GetOuter()->GetDefaultNavGoalTolerance() ); return IsGoalActive(); } //----------------------------------------------------------------------------- bool CAI_Navigator::SetRandomGoal( const Vector &from, float minPathLength, const Vector &dir ) { DbgNavMsg( GetOuter(), "Set random goal\n" ); OnNewGoal(); if ( GetNetwork()->NumNodes() <= 0 ) return false; INearestNodeFilter *pFilter = NULL; CAI_NavInHintGroupFilter filter; if ( GetOuter()->GetHintGroup() != NULL_STRING ) { filter.m_iszGroup = GetOuter()->GetHintGroup(); pFilter = &filter; } int fromNodeID = GetNetwork()->NearestNodeToPoint( GetOuter(), from, true, pFilter ); if (fromNodeID == NO_NODE) return false; AI_Waypoint_t* pRoute = GetPathfinder()->FindShortRandomPath( fromNodeID, minPathLength, dir ); if (!pRoute) return false; GetPath()->SetGoalType(GOALTYPE_LOCATION); GetPath()->SetWaypoints(pRoute); GetPath()->SetLastNodeAsGoal(); GetPath()->SetGoalTolerance( GetOuter()->GetDefaultNavGoalTolerance() ); SimplifyPath( true ); return true; } //----------------------------------------------------------------------------- bool CAI_Navigator::SetDirectGoal( const Vector &goalPos, Navigation_t navType ) { DbgNavMsg( GetOuter(), "Set direct goal\n" ); OnNewGoal(); ClearPath(); GetPath()->SetGoalType( GOALTYPE_LOCATION ); GetPath()->SetWaypoints( new AI_Waypoint_t( goalPos, 0, navType, bits_WP_TO_GOAL, NO_NODE ) ); GetPath()->SetGoalTolerance( GetOuter()->GetDefaultNavGoalTolerance() ); GetPath()->SetGoalPosition( goalPos ); return true; } //----------------------------------------------------------------------------- // Placeholder implementation for wander goals: cast a few random vectors and // accept the first one that still lies on the navmesh. // Side effect: vector goal of navigator is set. // Returns: true on goal set, false otherwise. static bool SetWanderGoalByRandomVector(CAI_Navigator *pNav, float minRadius, float maxRadius, int numTries) { while (--numTries >= 0) { float dist = random->RandomFloat( minRadius, maxRadius ); Vector dir = UTIL_YawToVector( random->RandomFloat( 0, 359.99 ) ); if ( pNav->SetVectorGoal( dir, dist, minRadius ) ) return true; } return false; } bool CAI_Navigator::SetWanderGoal( float minRadius, float maxRadius ) { // @Note (toml 11-07-02): this is a bogus placeholder implementation!!! // // First try using a random setvector goal, and then try SetRandomGoal(). // Except, if we have a hint group, first try SetRandomGoal() (which // respects hint groups) and then fall back on the setvector. if( !GetOuter()->GetHintGroup() ) { return ( SetWanderGoalByRandomVector( this, minRadius, maxRadius, 5 ) || SetRandomGoal( 1 ) ); } else { return ( SetRandomGoal(1) || SetWanderGoalByRandomVector( this, minRadius, maxRadius, 5 ) ); } } //----------------------------------------------------------------------------- void CAI_Navigator::CalculateDeflection( const Vector &start, const Vector &dir, const Vector &normal, Vector *pResult ) { Vector temp; CrossProduct( dir, normal, temp ); CrossProduct( normal, temp, *pResult ); VectorNormalize( *pResult ); } //----------------------------------------------------------------------------- bool CAI_Navigator::SetVectorGoal( const Vector &dir, float targetDist, float minDist, bool fShouldDeflect ) { DbgNavMsg( GetOuter(), "Set vector goal\n" ); Vector result; if ( FindVectorGoal( &result, dir, targetDist, minDist, fShouldDeflect ) ) return SetGoal( result ); return false; } //----------------------------------------------------------------------------- bool CAI_Navigator::SetVectorGoalFromTarget( const Vector &goalPos, float minDist, bool fShouldDeflect ) { Vector vDir = goalPos; float dist = ComputePathDirection( GetNavType(), GetLocalOrigin(), goalPos, &vDir ); return SetVectorGoal( vDir, dist, minDist, fShouldDeflect ); } //----------------------------------------------------------------------------- bool CAI_Navigator::FindVectorGoal( Vector *pResult, const Vector &dir, float targetDist, float minDist, bool fShouldDeflect ) { AIMoveTrace_t moveTrace; float distAchieved = 0; MARK_TASK_EXPENSIVE(); Vector testLoc = GetLocalOrigin() + ( dir * targetDist ); GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), testLoc, GetOuter()->GetAITraceMask(), NULL, &moveTrace ); if ( moveTrace.fStatus != AIMR_OK ) { distAchieved = targetDist - moveTrace.flDistObstructed; if ( fShouldDeflect && moveTrace.vHitNormal != vec3_origin ) { Vector vecDeflect; Vector vecNormal = moveTrace.vHitNormal; if ( GetNavType() == NAV_GROUND ) vecNormal.z = 0; CalculateDeflection( moveTrace.vEndPosition, dir, vecNormal, &vecDeflect ); testLoc = moveTrace.vEndPosition + ( vecDeflect * ( targetDist - distAchieved ) ); Vector temp = moveTrace.vEndPosition; GetMoveProbe()->MoveLimit( GetNavType(), temp, testLoc, GetOuter()->GetAITraceMask(), NULL, &moveTrace ); distAchieved += ( targetDist - distAchieved ) - moveTrace.flDistObstructed; } if ( distAchieved < minDist + 0.01 ) return false; } *pResult = moveTrace.vEndPosition; return true; } //----------------------------------------------------------------------------- bool CAI_Navigator::SetRandomGoal( float minPathLength, const Vector &dir ) { return SetRandomGoal( GetLocalOrigin(), minPathLength, dir ); } //----------------------------------------------------------------------------- bool CAI_Navigator::PrependLocalAvoidance( float distObstacle, const AIMoveTrace_t &directTrace ) { if ( AIStrongOpt() ) return false; if ( GetOuter()->IsFlaggedEfficient() ) return false; if ( m_flTimeLastAvoidanceTriangulate >= gpGlobals->curtime ) return false; // Only triangulate once per think at most m_flTimeLastAvoidanceTriangulate = gpGlobals->curtime; AI_PROFILE_SCOPE(CAI_Navigator_PrependLocalAvoidance); AI_Waypoint_t *pAvoidanceRoute = NULL; Vector vStart = GetLocalOrigin(); if ( distObstacle < GetHullWidth() * 0.5 ) { AIMoveTrace_t backawayTrace; Vector vTestBackaway = GetCurWaypointPos() - GetLocalOrigin(); VectorNormalize( vTestBackaway ); vTestBackaway *= -GetHullWidth(); vTestBackaway += GetLocalOrigin(); int flags = ( GetNavType() == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT; if ( GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), vTestBackaway, GetOuter()->GetAITraceMask(), GetNavTargetEntity(), 100.0, flags, &backawayTrace ) ) { vStart = backawayTrace.vEndPosition; pAvoidanceRoute = new AI_Waypoint_t( vStart, 0, GetNavType(), bits_WP_TO_DETOUR, NO_NODE ); } } AI_Waypoint_t *pTriangulation = GetPathfinder()->BuildTriangulationRoute( vStart, GetCurWaypointPos(), GetNavTargetEntity(), bits_WP_TO_DETOUR, NO_NODE, 0.0, distObstacle, GetNavType() ); if ( !pTriangulation ) { delete pAvoidanceRoute; return false; } if ( pAvoidanceRoute ) pAvoidanceRoute->SetNext( pTriangulation ); else pAvoidanceRoute = pTriangulation; // @TODO (toml 02-04-04): it would be better to do this on each triangulation test to // improve the odds of success. however, difficult in current structure float moveThisInterval = GetMotor()->CalcIntervalMove(); Vector dir = pAvoidanceRoute->GetPos() - GetLocalOrigin(); float dist = VectorNormalize( dir ); Vector testPos; if ( dist > moveThisInterval ) { dist = moveThisInterval; testPos = GetLocalOrigin() + dir * dist; } else { testPos = pAvoidanceRoute->GetPos(); } int flags = ( GetNavType() == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT; if ( !GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), testPos, GetOuter()->GetAITraceMask(), GetNavTargetEntity(), 100.0, flags ) ) { DeleteAll( pAvoidanceRoute ); return false; } // FIXME: should the route get simplified? DbgNavMsg( GetOuter(), "Adding triangulation\n" ); GetPath()->PrependWaypoints( pAvoidanceRoute ); return true; } //----------------------------------------------------------------------------- void CAI_Navigator::PrependWaypoint( const Vector &newPoint, Navigation_t navType, unsigned waypointFlags ) { GetPath()->PrependWaypoint( newPoint, navType, waypointFlags ); } //----------------------------------------------------------------------------- const Vector &CAI_Navigator::GetGoalPos() const { return GetPath()->BaseGoalPosition(); } //----------------------------------------------------------------------------- CBaseEntity *CAI_Navigator::GetGoalTarget() { return GetPath()->GetTarget(); } //----------------------------------------------------------------------------- float CAI_Navigator::GetGoalTolerance() const { return GetPath()->GetGoalTolerance(); } //----------------------------------------------------------------------------- void CAI_Navigator::SetGoalTolerance(float tolerance) { GetPath()->SetGoalTolerance(tolerance); } //----------------------------------------------------------------------------- bool CAI_Navigator::RefindPathToGoal( bool fSignalTaskStatus, bool bDontIgnoreBadLinks ) { return FindPath( fSignalTaskStatus, bDontIgnoreBadLinks ); } //----------------------------------------------------------------------------- bool CAI_Navigator::UpdateGoalPos( const Vector &goalPos ) { // Queue this up if we're in the middle of a frame if ( PostFrameNavigationSystem()->IsGameFrameRunning() ) { // Send off the query for queuing PostFrameNavigationSystem()->EnqueueEntityNavigationQuery( GetOuter(), CreateFunctor( this, &CAI_Navigator::UpdateGoalPos, RefToVal( goalPos ) ) ); // For now, always succeed -- we need to deal with failures on the next frame return true; } DbgNavMsg( GetOuter(), "Updating goal pos\n" ); if ( GetNavType() == NAV_JUMP ) { DevMsg( "Updating goal pos while jumping!\n" ); AssertOnce( 0 ); return false; } // FindPath should be finding the goal position if the goal type is // one of these two... We could just ignore the suggested position // in these two cases I suppose! Assert( (GetPath()->GoalType() != GOALTYPE_ENEMY) && (GetPath()->GoalType() != GOALTYPE_TARGETENT) ); GetPath()->ResetGoalPosition( goalPos ); if ( FindPath( !GetOuter()->IsCurTaskContinuousMove() ) ) { SimplifyPath( true ); return true; } return false; } //----------------------------------------------------------------------------- Activity CAI_Navigator::SetMovementActivity(Activity activity) { return GetPath()->SetMovementActivity( activity ); } //----------------------------------------------------------------------------- void CAI_Navigator::StopMoving( bool bImmediate ) { DbgNavMsg1( GetOuter(), "CAI_Navigator::StopMoving( %d )\n", bImmediate ); if ( IsGoalSet() ) { if ( bImmediate || !SetGoalFromStoppingPath() ) { OnNavComplete(); } } else ClearGoal(); } //----------------------------------------------------------------------------- bool CAI_Navigator::ClearGoal() { DbgNavMsg( GetOuter(), "CAI_Navigator::ClearGoal()\n" ); ClearPath(); OnNewGoal(); return true; } //----------------------------------------------------------------------------- int CAI_Navigator::GetMovementSequence( ) { int sequence = GetPath()->GetMovementSequence( ); if (sequence == ACT_INVALID) { Activity activity = GetPath()->GetMovementActivity(); Assert( activity != ACT_INVALID ); sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ) ); if ( sequence == ACT_INVALID ) { DevMsg( GetOuter(), "No appropriate sequence for movement activity %s (%d)\n", GetOuter()->GetActivityName( GetPath()->GetArrivalActivity() ), GetPath()->GetArrivalActivity() ); if ( activity == ACT_SCRIPT_CUSTOM_MOVE ) { sequence = GetOuter()->GetScriptCustomMoveSequence(); } else { sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( ACT_WALK ) ); } } Assert( sequence != ACT_INVALID ); GetPath()->SetMovementSequence( sequence ); } return sequence; } //----------------------------------------------------------------------------- void CAI_Navigator::SetMovementSequence( int sequence ) { GetPath()->SetMovementSequence( sequence ); } //----------------------------------------------------------------------------- Activity CAI_Navigator::GetMovementActivity() const { return GetPath()->GetMovementActivity(); } //----------------------------------------------------------------------------- void CAI_Navigator::SetArrivalActivity(Activity activity) { GetPath()->SetArrivalActivity(activity); } //----------------------------------------------------------------------------- int CAI_Navigator::GetArrivalSequence( int curSequence ) { int sequence = GetPath()->GetArrivalSequence( ); if (sequence == ACT_INVALID) { Activity activity = GetOuter()->GetStoppedActivity(); Assert( activity != ACT_INVALID ); if (activity == ACT_INVALID) { activity = ACT_IDLE; } sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), curSequence ); if ( sequence == ACT_INVALID ) { DevMsg( GetOuter(), "No appropriate sequence for arrival activity %s (%d)\n", GetOuter()->GetActivityName( GetPath()->GetArrivalActivity() ), GetPath()->GetArrivalActivity() ); sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( ACT_IDLE ), curSequence ); } Assert( sequence != ACT_INVALID ); GetPath()->SetArrivalSequence( sequence ); } return sequence; } //----------------------------------------------------------------------------- void CAI_Navigator::SetArrivalSequence( int sequence ) { GetPath()->SetArrivalActivity( ACT_INVALID ); GetPath()->SetArrivalSequence( sequence ); } //----------------------------------------------------------------------------- Activity CAI_Navigator::GetArrivalActivity( ) const { return GetPath()->GetArrivalActivity( ); } //----------------------------------------------------------------------------- void CAI_Navigator::SetArrivalDirection( const Vector &goalDirection ) { GetPath()->SetGoalDirection( goalDirection ); } //----------------------------------------------------------------------------- void CAI_Navigator::SetArrivalDirection( const QAngle &goalAngle ) { Vector goalDirection; AngleVectors( goalAngle, &goalDirection ); GetPath()->SetGoalDirection( goalDirection ); } //----------------------------------------------------------------------------- void CAI_Navigator::SetArrivalDirection( CBaseEntity * pTarget ) { GetPath()->SetGoalDirection( pTarget ); } //----------------------------------------------------------------------------- Vector CAI_Navigator::GetArrivalDirection( ) { return GetPath()->GetGoalDirection( GetAbsOrigin() ); } //----------------------------------------------------------------------------- void CAI_Navigator::SetArrivalSpeed( float flSpeed ) { GetPath()->SetGoalSpeed( flSpeed ); } //----------------------------------------------------------------------------- float CAI_Navigator::GetArrivalSpeed( void ) { float flSpeed = GetPath()->GetGoalSpeed( GetAbsOrigin() ); if (flSpeed >= 0.0) { return flSpeed; } int sequence = GetArrivalSequence( ACT_INVALID ); if (sequence != ACT_INVALID) { flSpeed = GetOuter()->GetEntryVelocity( sequence ); SetArrivalSpeed( flSpeed ); } else { flSpeed = 0.0; } return flSpeed; } //----------------------------------------------------------------------------- void CAI_Navigator::SetArrivalDistance( float flDistance ) { GetPath()->SetGoalStoppingDistance( flDistance ); } //----------------------------------------------------------------------------- float CAI_Navigator::GetArrivalDistance() const { return GetPath()->GetGoalStoppingDistance(); } //----------------------------------------------------------------------------- const Vector &CAI_Navigator::GetCurWaypointPos() const { return GetPath()->CurWaypointPos(); } //----------------------------------------------------------------------------- int CAI_Navigator::GetCurWaypointFlags() const { return GetPath()->CurWaypointFlags(); } //----------------------------------------------------------------------------- GoalType_t CAI_Navigator::GetGoalType() const { return GetPath()->GoalType(); } //----------------------------------------------------------------------------- int CAI_Navigator::GetGoalFlags() const { return GetPath()->GoalFlags(); } //----------------------------------------------------------------------------- bool CAI_Navigator::CurWaypointIsGoal() const { return GetPath()->CurWaypointIsGoal(); } bool CAI_Navigator::CurWaypointRequiresPreciseMovement() const { return ( GetPath()->CurWaypointFlags() & bits_WP_PRECISE_MOVEMENT ) != 0; } //----------------------------------------------------------------------------- bool CAI_Navigator::IsGoalSet() const { return ( GetPath()->GoalType() != GOALTYPE_NONE ); } //----------------------------------------------------------------------------- bool CAI_Navigator::IsGoalActive() const { return ( GetPath() && !( const_cast(GetPath())->IsEmpty() ) ); } //----------------------------------------------------------------------------- bool CAI_Navigator::GetPointAlongPath( Vector *pResult, float distance, bool fReducibleOnly ) { if ( !GetPath()->GetCurWaypoint() ) return false; AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); AI_Waypoint_t *pEndPoint = pCurWaypoint; float distRemaining = distance; float distToNext; Vector vPosPrev = GetLocalOrigin(); while ( pEndPoint->GetNext() ) { distToNext = ComputePathDistance( GetNavType(), vPosPrev, pEndPoint->GetPos() ); if ( distToNext > distRemaining) break; distRemaining -= distToNext; vPosPrev = pEndPoint->GetPos(); if ( fReducibleOnly && !pEndPoint->IsReducible() ) break; pEndPoint = pEndPoint->GetNext(); } Vector &result = *pResult; float distToEnd = ComputePathDistance( GetNavType(), vPosPrev, pEndPoint->GetPos() ); if ( distToEnd - distRemaining < 0.1 ) { result = pEndPoint->GetPos(); } else { result = pEndPoint->GetPos() - vPosPrev; VectorNormalize( result ); result *= distRemaining; result += vPosPrev; } return true; } //----------------------------------------------------------------------------- float CAI_Navigator::GetPathDistanceToGoal() { return GetPath()->GetPathDistanceToGoal(GetAbsOrigin()); } //----------------------------------------------------------------------------- float CAI_Navigator::GetPathTimeToGoal() { if ( GetOuter()->m_flGroundSpeed ) return (GetPathDistanceToGoal() / GetOuter()->m_flGroundSpeed); return 0; } //----------------------------------------------------------------------------- AI_PathNode_t CAI_Navigator::GetNearestNode() { #if !defined( GNUC ) COMPILE_TIME_ASSERT( ((int)AIN_NO_NODE == (int)NO_NODE) ); #endif return (AI_PathNode_t)(intp)( GetPathfinder()->NearestNodeToNPC() ); } //----------------------------------------------------------------------------- Vector CAI_Navigator::GetNodePos( AI_PathNode_t node ) { return GetNetwork()->GetNode(size_cast( (intp)node ) )->GetPosition(GetHullType()); } //----------------------------------------------------------------------------- void CAI_Navigator::OnScheduleChange() { DbgNavMsg( GetOuter(), "Schedule change\n" ); } //----------------------------------------------------------------------------- void CAI_Navigator::OnClearPath(void) { } //----------------------------------------------------------------------------- void CAI_Navigator::OnNewGoal() { DbgNavMsg( GetOuter(), "New Goal\n" ); ResetCalculations(); m_fNavComplete = true; } //----------------------------------------------------------------------------- void CAI_Navigator::OnNavComplete() { DbgNavMsg( GetOuter(), "Nav complete\n" ); ResetCalculations(); TaskMovementComplete(); m_fNavComplete = true; } //----------------------------------------------------------------------------- void CAI_Navigator::OnNavFailed( bool bMovement ) { DbgNavMsg( GetOuter(), "Nav failed\n" ); if ( bMovement ) GetOuter()->OnMovementFailed(); #ifdef DEBUG if ( CurWaypointIsGoal() ) { float flWaypointDist = ComputePathDistance( GetNavType(), GetLocalOrigin(), GetPath()->ActualGoalPosition() ); if ( flWaypointDist < GetGoalTolerance() + 0.1 ) { DevMsg( "Nav failed but NPC was within goal tolerance?\n" ); } } #endif ResetCalculations(); m_fNavComplete = true; m_bLastNavFailed = true; } //----------------------------------------------------------------------------- void CAI_Navigator::OnNavFailed( AI_TaskFailureCode_t code, bool bMovement ) { if ( GetOuter()->ShouldFailNav( bMovement ) ) { OnNavFailed( bMovement ); DbgNavMsg( GetOuter(), "CAI_Navigator::OnNavFailed calling SetActivity(GetStoppedActivity)"); SetActivity( GetOuter()->GetStoppedActivity() ); TaskFail(code); } else { m_nNavFailCounter++; m_flLastNavFailTime = gpGlobals->curtime; if ( GetOuter()->ShouldBruteForceFailedNav() ) { if (bMovement) { m_timeBeginFailedSteer = FLT_MAX; // if failing, turn off collisions with the object CBaseEntity *pBlocker = GetBlockingEntity(); // FIXME: change this to only be for MOVETYPE_VPHYSICS? if (pBlocker && !pBlocker->IsWorld() && !pBlocker->IsPlayer() && !FClassnameIs( pBlocker, "func_tracktrain" )) { //pBlocker->DrawBBoxOverlay( 2.0f ); if (NPCPhysics_CreateSolver( GetOuter(), pBlocker, true, 10.0f ) != NULL) { ClearNavFailCounter(); } } // if still failing, try jumping forward through the route if (GetNavFailCounter() > 0) { if (TeleportAlongPath()) { ClearNavFailCounter(); } } } else { CBaseEntity *pBlocker = GetMoveProbe()->GetBlockingEntity(); if (pBlocker) { //pBlocker->DrawBBoxOverlay( 2.0f ); if (NPCPhysics_CreateSolver( GetOuter(), pBlocker, true, 10.0f ) != NULL) { ClearNavFailCounter(); } } } } } } //----------------------------------------------------------------------------- void CAI_Navigator::OnNavFailed( const char *pszGeneralFailText, bool bMovement ) { OnNavFailed( MakeFailCode( pszGeneralFailText ), bMovement ); } //----------------------------------------------------------------------------- int CAI_Navigator::GetNavFailCounter() const { return m_nNavFailCounter; } //----------------------------------------------------------------------------- void CAI_Navigator::ClearNavFailCounter() { m_nNavFailCounter = 0; } //----------------------------------------------------------------------------- float CAI_Navigator::GetLastNavFailTime() const { return m_flLastNavFailTime; } //----------------------------------------------------------------------------- bool CAI_Navigator::TeleportAlongPath() { while (GetPath()->GetCurWaypoint()) { Vector vecStart; Vector vTestPoint; vecStart = GetPath()->CurWaypointPos(); AdvancePath(); GetOuter()->GetMoveProbe()->FloorPoint( vecStart, GetOuter()->GetAITraceMask(), GetOuter()->StepHeight(), -64, &vTestPoint ); if ( CanFitAtPosition( vTestPoint, GetOuter()->GetAITraceMask(), false, false ) ) { if ( GetOuter()->GetMoveProbe()->CheckStandPosition( vTestPoint, GetOuter()->GetAITraceMask() ) ) { GetOuter()->Teleport( &vTestPoint, NULL, NULL ); // clear ground entity, let normal fall code reestablish what the npc is now standing on GetOuter()->SetGroundEntity( NULL ); GetOuter()->PhysicsTouchTriggers( &vTestPoint ); return true; } } if (CurWaypointIsGoal()) break; } return false; } //----------------------------------------------------------------------------- void CAI_Navigator::ResetCalculations() { m_hPeerWaitingOn = NULL; m_PeerWaitMoveTimer.Force(); m_PeerWaitClearTimer.Force(); m_hBigStepGroundEnt = NULL; m_NextSidestepTimer.Force(); m_bCalledStartMove = false; m_vPosBeginFailedSteer = vec3_invalid; m_timeBeginFailedSteer = FLT_MAX; m_flLastSuccessfulSimplifyTime = -1; GetLocalNavigator()->ResetMoveCalculations(); GetMotor()->ResetMoveCalculations(); GetMoveProbe()->ClearBlockingEntity(); m_nNavFailCounter = 0; m_flLastNavFailTime = -1; } //----------------------------------------------------------------------------- // Purpose: Sets navigation type, maintaining any necessary invariants //----------------------------------------------------------------------------- void CAI_Navigator::SetNavType( Navigation_t navType ) { m_navType = navType; } //----------------------------------------------------------------------------- AIMoveResult_t CAI_Navigator::MoveCrawl() { if (!PreMove()) return AIMR_ILLEGAL; if ( ShouldTestFailMove() ) return AIMR_ILLEGAL; // -------------------------------- AIMoveResult_t result = AIMR_ILLEGAL; if ( MoveUpdateWaypoint( &result ) ) return result; // -------------------------------- // Set activity to be the Navigation activity float preMoveSpeed = GetIdealSpeed(); Activity preMoveActivity = GetActivity(); int nPreMoveSequence = GetOuter()->GetSequence(); // this is an unfortunate necessity to ensure setting back the activity picks the right one if it had been sticky Vector vStart = GetAbsOrigin(); // -------------------------------- // FIXME: only need since IdealSpeed isn't based on movement activity but immediate activity! SetActivity( GetMovementActivity() ); if ( m_bValidateActivitySpeed && GetIdealSpeed() <= 0.0f ) { if ( GetActivity() == ACT_TRANSITION ) return AIMR_OK; DevMsg( "%s moving with speed <= 0 (%s)\n", GetEntClassname(), GetOuter()->GetSequenceName( GetSequence() ) ); } // -------------------------------- if ( GetNavType() != NAV_CRAWL ) { DbgNavMsg( GetOuter(), "Crawl start\n" ); } SetNavType( NAV_CRAWL ); AILocalMoveGoal_t move; MoveCalcBaseGoal( &move ); result = MoveEnact( move ); // -------------------------------- // If we didn't actually move, but it was a success (i.e., blocked but within tolerance), make sure no visual artifact // FIXME: only needed because of the above slamming of SetActivity(), which is only needed // because GetIdealSpeed() looks at the current activity instead of the movement activity. if ( result == AIMR_OK && preMoveSpeed < 0.01 ) { if ( ( GetAbsOrigin() - vStart ).Length() < 0.01 ) { GetOuter()->SetSequence( nPreMoveSequence ); SetActivity( preMoveActivity ); } } // -------------------------------- return result; } //----------------------------------------------------------------------------- AIMoveResult_t CAI_Navigator::MoveClimb() { // -------------------------------------------------- // CLIMB START // -------------------------------------------------- const Vector &climbDest = GetPath()->CurWaypointPos(); Vector climbDir = climbDest - GetLocalOrigin(); float climbDist = VectorNormalize( climbDir ); if ( GetNavType() != NAV_CLIMB ) { DbgNavMsg( GetOuter(), "Climb start\n" ); GetMotor()->MoveClimbStart( climbDest, climbDir, climbDist, GetPath()->CurWaypointYaw() ); } SetNavType( NAV_CLIMB ); // Look for a block by another NPC, and attempt to recover AIMoveTrace_t moveTrace; if ( climbDist > 0.01 && !GetMoveProbe()->MoveLimit( NAV_CLIMB, GetLocalOrigin(), GetLocalOrigin() + ( climbDir * MIN(0.1,climbDist - 0.005) ), GetOuter()->GetAITraceMask(), GetNavTargetEntity(), &moveTrace ) ) { CAI_BaseNPC *pOther = ( moveTrace.pObstruction ) ? moveTrace.pObstruction->MyNPCPointer() : NULL; if ( pOther ) { bool bAbort = false; if ( !pOther->IsMoving() ) bAbort = true; else if ( pOther->GetNavType() == NAV_CLIMB && climbDir.z <= 0.01 ) { const Vector &otherClimbDest = pOther->GetNavigator()->GetPath()->CurWaypointPos(); Vector otherClimbDir = otherClimbDest - pOther->GetLocalOrigin(); VectorNormalize( otherClimbDir ); if ( otherClimbDir.Dot( climbDir ) < 0 ) { bAbort = true; if ( pOther->GetNavigator()->GetStoppingPath( m_pClippedWaypoints ) ) { m_flTimeClipped = gpGlobals->curtime; SetNavType(NAV_GROUND); // end of clipped will be on ground SetGravity( 1.0 ); if ( RefindPathToGoal( false ) ) { bAbort = false; } SetGravity( 0.0 ); SetNavType(NAV_CLIMB); } } } if ( bAbort ) { DbgNavMsg( GetOuter(), "Climb fail\n" ); GetMotor()->MoveClimbStop(); SetNavType(NAV_GROUND); return AIMR_BLOCKED_NPC; } } } // count NAV_CLIMB nodes remaining int climbNodesLeft = 0; AI_Waypoint_t *pWaypoint = GetPath()->GetCurWaypoint(); while (pWaypoint && pWaypoint->NavType() == NAV_CLIMB) { ++climbNodesLeft; pWaypoint = pWaypoint->GetNext(); } AIMoveResult_t result = GetMotor()->MoveClimbExecute( climbDest, climbDir, climbDist, GetPath()->CurWaypointYaw(), climbNodesLeft ); if ( result == AIMR_CHANGE_TYPE ) { if ( GetPath()->GetCurWaypoint()->GetNext() ) AdvancePath(); else OnNavComplete(); if ( !GetPath()->GetCurWaypoint() || !GetPath()->GetCurWaypoint()->GetNext() || !(GetPath()->CurWaypointNavType() == NAV_CLIMB)) { DbgNavMsg( GetOuter(), "Climb stop\n" ); GetMotor()->MoveClimbStop(); SetNavType(NAV_GROUND); } } else if ( result != AIMR_OK ) { DbgNavMsg( GetOuter(), "Climb fail (2)\n" ); GetMotor()->MoveClimbStop(); SetNavType(NAV_GROUND); return result; } return result; } //----------------------------------------------------------------------------- AIMoveResult_t CAI_Navigator::MoveJump() { // -------------------------------------------------- // JUMPING // -------------------------------------------------- if ( (GetNavType() != NAV_JUMP) && (GetEntFlags() & FL_ONGROUND) ) { // -------------------------------------------------- // Now check if I can actually jump this distance? // -------------------------------------------------- AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_JUMP, GetLocalOrigin(), GetPath()->CurWaypointPos(), GetOuter()->GetAITraceMask(), GetNavTargetEntity(), &moveTrace ); if ( IsMoveBlocked( moveTrace ) ) { return moveTrace.fStatus; } SetNavType(NAV_JUMP); DbgNavMsg( GetOuter(), "Jump start\n" ); GetMotor()->MoveJumpStart( moveTrace.vJumpVelocity ); } // -------------------------------------------------- // LANDING (from jump) // -------------------------------------------------- else if (GetNavType() == NAV_JUMP && (GetEntFlags() & FL_ONGROUND)) { // DevMsg( "jump to %f %f %f landed %f %f %f", GetPath()->CurWaypointPos().x, GetPath()->CurWaypointPos().y, GetPath()->CurWaypointPos().z, GetLocalOrigin().x, GetLocalOrigin().y, GetLocalOrigin().z ); DbgNavMsg( GetOuter(), "Jump stop\n" ); AIMoveResult_t result = GetMotor()->MoveJumpStop( ); if (result == AIMR_CHANGE_TYPE) { SetNavType(NAV_GROUND); // -------------------------------------------------- // If I've jumped to my goal I'm done // -------------------------------------------------- if (CurWaypointIsGoal()) { OnNavComplete(); return AIMR_OK; } // -------------------------------------------------- // Otherwise advance my route and walk // -------------------------------------------------- else { AdvancePath(); return AIMR_CHANGE_TYPE; } } return AIMR_OK; } // -------------------------------------------------- // IN-AIR (from jump) // -------------------------------------------------- else { GetMotor()->MoveJumpExecute( ); } return AIMR_OK; } //----------------------------------------------------------------------------- void CAI_Navigator::MoveCalcBaseGoal( AILocalMoveGoal_t *pMoveGoal ) { AI_PROFILE_SCOPE( CAI_Navigator_MoveCalcBaseGoal ); pMoveGoal->navType = GetNavType(); pMoveGoal->target = GetPath()->CurWaypointPos(); pMoveGoal->maxDist = ComputePathDirection( GetNavType(), GetLocalOrigin(), pMoveGoal->target, &pMoveGoal->dir ); pMoveGoal->facing = pMoveGoal->dir; pMoveGoal->speed = GetMotor()->GetSequenceGroundSpeed( GetMovementSequence() ); pMoveGoal->curExpectedDist = pMoveGoal->speed * GetMotor()->GetMoveInterval(); pMoveGoal->pMoveTarget = GetNavTargetEntity(); if ( pMoveGoal->curExpectedDist > pMoveGoal->maxDist ) pMoveGoal->curExpectedDist = pMoveGoal->maxDist; if ( GetPath()->CurWaypointIsGoal()) { pMoveGoal->flags |= AILMG_TARGET_IS_GOAL; } else { AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint->GetNext() && pCurWaypoint->GetNext()->NavType() != pCurWaypoint->NavType() ) pMoveGoal->flags |= AILMG_TARGET_IS_TRANSITION; } const Task_t *pCurTask = GetOuter()->GetTask(); if ( pCurTask && pCurTask->iTask == TASK_STOP_MOVING ) { // If we're running stop moving, don't steer or run avoidance paths // This stops the NPC wiggling around as they attempt to reach a stopping // path that's pushed right up against geometry. (Tracker #48656) pMoveGoal->flags |= ( AILMG_NO_STEER | AILMG_NO_AVOIDANCE_PATHS ); } pMoveGoal->pPath = GetPath(); } //----------------------------------------------------------------------------- bool CAI_Navigator::OnCalcBaseMove( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) { if ( GetOuter()->OnCalcBaseMove( pMoveGoal, distClear, pResult ) ) { DebugNoteMovementFailureIfBlocked( *pResult ); return true; } return false; } //----------------------------------------------------------------------------- bool CAI_Navigator::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) { bool fTargetIsGoal = ( ( pMoveGoal->flags & AILMG_TARGET_IS_GOAL ) != 0 ); bool fShouldAttemptHit = false; bool fShouldAdvancePath = false; float tolerance = 0; if ( fTargetIsGoal ) { fShouldAttemptHit = true; tolerance = GetPath()->GetGoalTolerance(); } else if ( !( pMoveGoal->flags & AILMG_TARGET_IS_TRANSITION ) ) { fShouldAttemptHit = true; fShouldAdvancePath = true; tolerance = GetPath()->GetWaypointTolerance(); // If the immediate goal is close, and the clearance brings into tolerance, // just try and move on if ( pMoveGoal->maxDist < 4*12 && pMoveGoal->maxDist - distClear < tolerance ) tolerance = pMoveGoal->maxDist + 1; } if ( fShouldAttemptHit ) { if ( distClear > pMoveGoal->maxDist ) { #ifdef PHYSICS_NPC_SHADOW_DISCREPENCY if ( distClear < AI_EPS_CASTS ) // needed because vphysics can pull us back up to this far { DebugNoteMovementFailure(); *pResult = pMoveGoal->directTrace.fStatus; pMoveGoal->maxDist = 0; return true; } #endif *pResult = AIMR_OK; return true; } #ifdef PHYSICS_NPC_SHADOW_DISCREPENCY if ( pMoveGoal->maxDist + AI_EPS_CASTS < tolerance ) #else if ( pMoveGoal->maxDist < tolerance ) #endif { if ( !fTargetIsGoal || ( pMoveGoal->directTrace.fStatus != AIMR_BLOCKED_NPC ) || ( !((CAI_BaseNPC *)pMoveGoal->directTrace.pObstruction)->IsMoving() ) ) { pMoveGoal->maxDist = distClear; *pResult = AIMR_OK; if ( fShouldAdvancePath ) { AdvancePath(); } else if ( distClear < 0.025 ) { *pResult = pMoveGoal->directTrace.fStatus; } return true; } } } #ifdef HL2_EPISODIC // Build an avoidance path around a vehicle if ( ai_vehicle_avoidance.GetBool() && pMoveGoal->directTrace.pObstruction != NULL && pMoveGoal->directTrace.pObstruction->GetServerVehicle() != NULL ) { //FIXME: This should change into a flag which forces an OBB route to be formed around the entity in question! AI_Waypoint_t *pOBB = GetPathfinder()->BuildOBBAvoidanceRoute( GetOuter()->GetAbsOrigin(), GetGoalPos(), pMoveGoal->directTrace.pObstruction, GetNavTargetEntity(), GetNavType() ); // See if we need to complete this navigation if ( pOBB == NULL ) { /* if ( GetOuter()->ShouldFailNav( true ) == false ) { // Create a physics solver to allow us to pass NPCPhysics_CreateSolver( GetOuter(), pMoveGoal->directTrace.pObstruction, true, 5.0f ); return true; } */ } else { // Otherwise we have a clear path to move around GetPath()->PrependWaypoints( pOBB ); return true; } } #endif // HL2_EPISODIC // Allow the NPC to override this behavior. Above logic takes priority if ( GetOuter()->OnObstructionPreSteer( pMoveGoal, distClear, pResult ) ) { DebugNoteMovementFailureIfBlocked( *pResult ); return true; } if ( !m_hBigStepGroundEnt.Get() && pMoveGoal->directTrace.pObstruction && distClear < GetHullWidth() && pMoveGoal->directTrace.pObstruction == GetOuter()->GetGroundEntity() && ( pMoveGoal->directTrace.pObstruction->IsPlayer() || dynamic_cast( pMoveGoal->directTrace.pObstruction ) ) ) { m_hBigStepGroundEnt = pMoveGoal->directTrace.pObstruction; *pResult = AIMR_CHANGE_TYPE; return true; } return false; } //----------------------------------------------------------------------------- bool CAI_Navigator::OnInsufficientStopDist( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) { // Allow the NPC to override this behavior if ( GetOuter()->OnInsufficientStopDist( pMoveGoal, distClear, pResult )) { DebugNoteMovementFailureIfBlocked( *pResult ); return true; } #ifdef PHYSICS_NPC_SHADOW_DISCREPENCY if ( distClear < AI_EPS_CASTS ) // needed because vphysics can pull us back up to this far { DebugNoteMovementFailure(); *pResult = pMoveGoal->directTrace.fStatus; pMoveGoal->maxDist = 0; return true; } #endif if ( !IsMovingOutOfWay( *pMoveGoal, distClear ) ) { float goalDist = ComputePathDistance( GetNavType(), GetAbsOrigin(), GetPath()->ActualGoalPosition() ); if ( goalDist < GetGoalTolerance() + 0.01 ) { pMoveGoal->maxDist = distClear; pMoveGoal->flags |= AILMG_CONSUME_INTERVAL; OnNavComplete(); *pResult = AIMR_OK; return true; } if ( m_NextSidestepTimer.Expired() ) { // Try bumping to side m_NextSidestepTimer.Reset(); AIMoveTrace_t moveTrace; Vector vDeflection; CalculateDeflection( GetLocalOrigin(), pMoveGoal->dir, pMoveGoal->directTrace.vHitNormal, &vDeflection ); for ( int i = 1; i > -2; i -= 2 ) { Vector testLoc = GetLocalOrigin() + ( vDeflection * GetHullWidth() * 2.0) * i; GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), testLoc, GetOuter()->GetAITraceMask(), NULL, &moveTrace ); if ( moveTrace.fStatus == AIMR_OK ) { Vector vNewWaypoint = moveTrace.vEndPosition; GetMoveProbe()->MoveLimit( GetNavType(), vNewWaypoint, pMoveGoal->target, GetOuter()->GetAITraceMask_BrushOnly(), NULL, &moveTrace ); if ( moveTrace.fStatus == AIMR_OK ) { PrependWaypoint( vNewWaypoint, GetNavType(), bits_WP_TO_DETOUR ); *pResult = AIMR_CHANGE_TYPE; return true; } } } } if ( distClear < 1.0 ) { // too close, nothing happening, I'm screwed DebugNoteMovementFailure(); *pResult = pMoveGoal->directTrace.fStatus; pMoveGoal->maxDist = 0; return true; } return false; } *pResult = AIMR_OK; pMoveGoal->maxDist = distClear; pMoveGoal->flags |= AILMG_CONSUME_INTERVAL; return true; } //----------------------------------------------------------------------------- bool CAI_Navigator::OnFailedSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) { // Allow the NPC to override this behavior if ( GetOuter()->OnFailedSteer( pMoveGoal, distClear, pResult )) { DebugNoteMovementFailureIfBlocked( *pResult ); return true; } if ( pMoveGoal->flags & AILMG_TARGET_IS_GOAL ) { if ( distClear >= GetPathDistToGoal() ) { *pResult = AIMR_OK; return true; } if ( distClear > pMoveGoal->maxDist - GetPath()->GetGoalTolerance() ) { Assert( CurWaypointIsGoal() && fabs(pMoveGoal->maxDist - GetPathDistToCurWaypoint()) < 0.01 ); if ( pMoveGoal->maxDist > distClear ) pMoveGoal->maxDist = distClear; if ( distClear < 0.125 ) OnNavComplete(); pMoveGoal->flags |= AILMG_CONSUME_INTERVAL; *pResult = AIMR_OK; return true; } } if ( !( pMoveGoal->flags & AILMG_TARGET_IS_TRANSITION ) ) { float distToWaypoint = GetPathDistToCurWaypoint(); float halfHull = GetHullWidth() * 0.5; if ( distToWaypoint < halfHull ) { if ( distClear > distToWaypoint + halfHull ) { *pResult = AIMR_OK; return true; } } } #if 0 if ( pMoveGoal->directTrace.pObstruction->MyNPCPointer() && !GetOuter()->IsUsingSmallHull() && GetOuter()->SetHullSizeSmall() ) { *pResult = AIMR_CHANGE_TYPE; return true; } #endif if ( !TestingSteering() && pMoveGoal->directTrace.fStatus == AIMR_BLOCKED_NPC && pMoveGoal->directTrace.vHitNormal != vec3_origin ) { AIMoveTrace_t moveTrace; Vector vDeflection; CalculateDeflection( GetLocalOrigin(), pMoveGoal->dir, pMoveGoal->directTrace.vHitNormal, &vDeflection ); if ( pMoveGoal->dir.AsVector2D().Dot( vDeflection.AsVector2D() ) > 0.7 ) { Vector testLoc = GetLocalOrigin() + ( vDeflection * pMoveGoal->curExpectedDist ); GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), testLoc, GetOuter()->GetAITraceMask(), NULL, &moveTrace ); if ( moveTrace.fStatus == AIMR_OK ) { pMoveGoal->dir = vDeflection; pMoveGoal->maxDist = pMoveGoal->curExpectedDist; *pResult = AIMR_OK; return true; } } } // If fail steer more than once after a second with no measurable progres, fail completely // This usually means a sucessful triangulation was not actually a valid avoidance. const float MOVE_TOLERANCE = 12.0; const float TIME_TOLERANCE = 1.0; if ( m_vPosBeginFailedSteer == vec3_invalid || ( m_vPosBeginFailedSteer - GetAbsOrigin() ).LengthSqr() > Square(MOVE_TOLERANCE) ) { m_vPosBeginFailedSteer = GetAbsOrigin(); m_timeBeginFailedSteer = gpGlobals->curtime; } else if ( GetNavType() == NAV_GROUND && gpGlobals->curtime - m_timeBeginFailedSteer > TIME_TOLERANCE && GetOuter()->m_flGroundSpeed * TIME_TOLERANCE > MOVE_TOLERANCE ) { *pResult = AIMR_ILLEGAL; return true; } if ( !(pMoveGoal->flags & AILMG_NO_AVOIDANCE_PATHS) && distClear < pMoveGoal->maxDist && !TestingSteering() ) { if ( PrependLocalAvoidance( distClear, pMoveGoal->directTrace ) ) { *pResult = AIMR_CHANGE_TYPE; return true; } } return false; } //----------------------------------------------------------------------------- bool CAI_Navigator::OnFailedLocalNavigation( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) { // Allow the NPC to override this behavior if ( GetOuter()->OnFailedLocalNavigation( pMoveGoal, distClear, pResult )) { DebugNoteMovementFailureIfBlocked( *pResult ); return true; } if ( DelayNavigationFailure( pMoveGoal->directTrace ) ) { *pResult = AIMR_OK; pMoveGoal->maxDist = distClear; pMoveGoal->flags |= AILMG_CONSUME_INTERVAL; return true; } return false; } //----------------------------------------------------------------------------- bool CAI_Navigator::DelayNavigationFailure( const AIMoveTrace_t &trace ) { // This code only handles the case of a group of AIs in close proximity, preparing // to move mostly as a group, but on slightly different think schedules. Without // this patience factor, in the middle or at the rear might fail just because it // happened to have its think function called a half cycle before the one // in front of it. CAI_BaseNPC *pBlocker = trace.pObstruction ? trace.pObstruction->MyNPCPointer() : NULL; Assert( m_fPeerMoveWait == false || pBlocker == m_hPeerWaitingOn ); // expected to be cleared each frame, and never call this function twice if ( !m_fPeerMoveWait || pBlocker != m_hPeerWaitingOn ) { if ( pBlocker ) { if ( m_hPeerWaitingOn != pBlocker || m_PeerWaitClearTimer.Expired() ) { m_fPeerMoveWait = true; m_hPeerWaitingOn = pBlocker; m_PeerWaitMoveTimer.Reset(); m_PeerWaitClearTimer.Reset(); if ( pBlocker->GetGroundEntity() == GetOuter() ) { trace_t bumpTrace; pBlocker->GetMoveProbe()->TraceHull( pBlocker->GetAbsOrigin(), pBlocker->GetAbsOrigin() + Vector(0,0,2.0), GetOuter()->GetAITraceMask(), &bumpTrace ); if ( bumpTrace.fraction == 1.0 ) { UTIL_SetOrigin(pBlocker, bumpTrace.endpos, true); } } } else if ( m_hPeerWaitingOn == pBlocker && !m_PeerWaitMoveTimer.Expired() ) { m_fPeerMoveWait = true; } } } return m_fPeerMoveWait; } //----------------------------------------------------------------------------- // @TODO (toml 11-12-02): right now, physics can pull the box back pretty far even though a hull // trace said we could make the move. Jay is looking into it. For now, if the NPC physics shadow // is active, allow for a bugger tolerance extern ConVar npc_vphysics; bool test_it = false; bool CAI_Navigator::MoveUpdateWaypoint( AIMoveResult_t *pResult ) { // Note that goal & waypoint tolerances are handled in progress blockage cases (e.g., OnObstructionPreSteer) AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); float waypointDist = ComputePathDistance( GetNavType(), GetLocalOrigin(), pCurWaypoint->GetPos() ); bool bIsGoal = CurWaypointIsGoal(); float tolerance = ( npc_vphysics.GetBool() ) ? 0.25 : 0.0625; bool fHit = false; if ( waypointDist <= tolerance ) { if ( test_it ) { if ( pCurWaypoint->GetNext() && pCurWaypoint->GetNext()->NavType() != pCurWaypoint->NavType() ) { if ( waypointDist < 0.001 ) fHit = true; } else fHit = true; } else fHit = true; } if ( fHit ) { if ( bIsGoal ) { OnNavComplete(); *pResult = AIMR_OK; } else { AdvancePath(); *pResult = AIMR_CHANGE_TYPE; } return true; } return false; } //----------------------------------------------------------------------------- bool CAI_Navigator::OnMoveStalled( const AILocalMoveGoal_t &move ) { DbgNavMsg(GetOuter(), "CAI_Navigator::OnMoveStalled calling SetActivity(GetStoppedActivity)"); SetActivity( GetOuter()->GetStoppedActivity() ); return true; } //----------------------------------------------------------------------------- bool CAI_Navigator::OnMoveExecuteFailed( const AILocalMoveGoal_t &move, const AIMoveTrace_t &trace, AIMotorMoveResult_t fMotorResult, AIMoveResult_t *pResult ) { // Allow the NPC to override this behavior if ( GetOuter()->OnMoveExecuteFailed( move, trace, fMotorResult, pResult )) { DebugNoteMovementFailureIfBlocked( *pResult ); return true; } if ( !m_hBigStepGroundEnt.Get() && trace.pObstruction && trace.flTotalDist - trace.flDistObstructed < GetHullWidth() && trace.pObstruction == GetOuter()->GetGroundEntity() && ( trace.pObstruction->IsPlayer() || dynamic_cast( trace.pObstruction ) ) ) { m_hBigStepGroundEnt = trace.pObstruction; *pResult = AIMR_CHANGE_TYPE; return true; } if ( fMotorResult == AIM_PARTIAL_HIT_TARGET ) { OnNavComplete(); *pResult = AIMR_OK; } else if ( fMotorResult == AIM_PARTIAL_HIT_NPC && DelayNavigationFailure( trace ) ) { *pResult = AIMR_OK; } return true; } //----------------------------------------------------------------------------- bool CAI_Navigator::OnMoveBlocked( AIMoveResult_t *pResult ) { if ( *pResult == AIMR_BLOCKED_NPC && GetPath()->GetCurWaypoint() && ( GetPath()->GetCurWaypoint()->Flags() & bits_WP_TO_DOOR ) ) { CBasePropDoor *pDoor = (CBasePropDoor *)(CBaseEntity *)GetPath()->GetCurWaypoint()->GetEHandleData(); if (pDoor != NULL) { GetOuter()->OpenPropDoorBegin( pDoor ); *pResult = AIMR_OK; return true; } } // Allow the NPC to override this behavior if ( GetOuter()->OnMoveBlocked( pResult )) return true; float flWaypointDist; if ( !GetPath()->CurWaypointIsGoal() && GetPath()->GetCurWaypoint()->IsReducible() ) { flWaypointDist = ComputePathDistance( GetNavType(), GetLocalOrigin(), GetCurWaypointPos() ); if ( flWaypointDist < GetHullWidth() ) { AdvancePath(); *pResult = AIMR_CHANGE_TYPE; } } DbgNavMsg(GetOuter(), "CAI_Navigator::OnMoveBlocked calling SetActivity(GetStoppedActivity)"); SetActivity( GetOuter()->GetStoppedActivity() ); const float EPS = 0.1; flWaypointDist = ComputePathDistance( GetNavType(), GetLocalOrigin(), GetPath()->ActualGoalPosition() ); if ( flWaypointDist < GetGoalTolerance() + EPS ) { OnNavComplete(); *pResult = AIMR_OK; return true; } return false; } //------------------------------------- AIMoveResult_t CAI_Navigator::MoveEnact( const AILocalMoveGoal_t &baseMove ) { AIMoveResult_t result = AIMR_ILLEGAL; AILocalMoveGoal_t move = baseMove; result = GetLocalNavigator()->MoveCalc( &move, ( m_flLastSuccessfulSimplifyTime == gpGlobals->curtime ) ); if ( result != AIMR_OK ) m_hLastBlockingEnt = move.directTrace.pObstruction; else { m_hLastBlockingEnt = NULL; GetMoveProbe()->ClearBlockingEntity(); } if ( result == AIMR_OK && !m_fNavComplete ) { Assert( GetPath()->GetCurWaypoint() ); result = GetMotor()->MoveNormalExecute( move ); } else if ( result != AIMR_CHANGE_TYPE ) { GetMotor()->MoveStop(); } if ( IsMoveBlocked( result ) ) { OnMoveBlocked( &result ); } return result; } //----------------------------------------------------------------------------- AIMoveResult_t CAI_Navigator::MoveNormal() { if (!PreMove()) return AIMR_ILLEGAL; if ( ShouldTestFailMove() ) return AIMR_ILLEGAL; // -------------------------------- AIMoveResult_t result = AIMR_ILLEGAL; if ( MoveUpdateWaypoint( &result ) ) return result; // -------------------------------- // Set activity to be the Navigation activity float preMoveSpeed = GetIdealSpeed(); Activity preMoveActivity = GetActivity(); int nPreMoveSequence = GetOuter()->GetSequence(); // this is an unfortunate necessity to ensure setting back the activity picks the right one if it had been sticky Vector vStart = GetAbsOrigin(); // -------------------------------- // FIXME: only need since IdealSpeed isn't based on movement activity but immediate activity! SetActivity( GetMovementActivity() ); if ( m_bValidateActivitySpeed && GetIdealSpeed() <= 0.0f ) { if ( GetActivity() == ACT_TRANSITION ) return AIMR_OK; DevMsg( "%s moving with speed <= 0 (%s)\n", GetEntClassname(), GetOuter()->GetSequenceName( GetSequence() ) ); } // -------------------------------- AILocalMoveGoal_t move; MoveCalcBaseGoal( &move ); result = MoveEnact( move ); // -------------------------------- // If we didn't actually move, but it was a success (i.e., blocked but within tolerance), make sure no visual artifact // FIXME: only needed because of the above slamming of SetActivity(), which is only needed // because GetIdealSpeed() looks at the current activity instead of the movement activity. if ( result == AIMR_OK && preMoveSpeed < 0.01 ) { if ( ( GetAbsOrigin() - vStart ).Length() < 0.01 ) { DbgNavMsg2(GetOuter(), "CAI_Navigator::MoveNormal calling SetSequence(%s) and SetActivity(%s)", GetOuter()->GetSequenceName(nPreMoveSequence), GetOuter()->GetActivityName(preMoveActivity)); GetOuter()->SetSequence( nPreMoveSequence ); SetActivity( preMoveActivity ); } } // -------------------------------- return result; } //----------------------------------------------------------------------------- bool CAI_Navigator::PreMove() { Navigation_t goalType = GetPath()->CurWaypointNavType(); Navigation_t curType = GetNavType(); m_fPeerMoveWait = false; if ( goalType == NAV_GROUND && curType != NAV_GROUND ) { // Warn for things other than crawling if ( curType != NAV_CRAWL ) DevMsg( "Warning: %s(%s) appears to have wrong nav type in CAI_Navigator::MoveGround()\n", GetOuter()->GetClassname(), STRING( GetOuter()->GetEntityName() ) ); switch ( curType ) { case NAV_CLIMB: { GetMotor()->MoveClimbStop(); break; } case NAV_JUMP: { GetMotor()->MoveJumpStop(); break; } } SetNavType( NAV_GROUND ); } else if ( goalType == NAV_FLY && curType != NAV_FLY ) { AssertMsg( 0, ( "GetNavType() == NAV_FLY" ) ); return false; } // -------------------------------- Assert( GetMotor()->GetMoveInterval() > 0 ); // -------------------------------- SimplifyPath(); return true; } //-------------------------------------------------------------------------------------------- bool CAI_Navigator::IsMovingOutOfWay( const AILocalMoveGoal_t &moveGoal, float distClear ) { // FIXME: We can make this work for regular entities; although the // original code was simply looking @ NPCs. I'm reproducing that code now // although I want to make it work for both. CAI_BaseNPC *pBlocker = moveGoal.directTrace.pObstruction ? moveGoal.directTrace.pObstruction->MyNPCPointer() : NULL; // if it's the world, it ain't moving if (!pBlocker) return false; // if they're doing something, assume it'll work out if (pBlocker->IsMoving()) { if ( distClear > moveGoal.curExpectedDist * 0.75 ) return true; Vector2D velBlocker = pBlocker->GetMotor()->GetCurVel().AsVector2D(); Vector2D velBlockerNorm = velBlocker; Vector2DNormalize( velBlockerNorm ); float dot = moveGoal.dir.AsVector2D().Dot( velBlockerNorm ); if (dot > -0.25 ) { return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Return true if the navigator should apply movement to the NPC //----------------------------------------------------------------------------- bool CAI_Navigator::ShouldMove( bool bHasAGoal ) { return ( bHasAGoal && ActivityIsLocomotive( GetMovementActivity() ) ); } //----------------------------------------------------------------------------- // Purpose: Move towards the next waypoint on my route // Input : // Output : //----------------------------------------------------------------------------- enum AINavResult_t { AINR_OK, AINR_NO_GOAL, AINR_NO_ROUTE, AINR_BLOCKED, AINR_ILLEGAL }; bool CAI_Navigator::Move( float flInterval ) { if (flInterval > 1.0) { // Bound interval so we don't get ludicrous motion when debugging // or when framerate drops catostrophically flInterval = 1.0; } if ( GetOuter()->OverrideMove( flInterval ) ) return true; // assume override move handles stopping issues // UNDONE: Figure out how much of the timestep was consumed by movement // this frame and restart the movement/schedule engine if necessary bool bHasGoal = GetGoalType() != GOALTYPE_NONE; bool bIsTurning = HasMemory( bits_MEMORY_TURNING ); if ( bHasGoal ) { if ( bIsTurning ) { if ( gpGlobals->curtime - GetPath()->GetStartTime() > 5 ) { Forget( bits_MEMORY_TURNING ); bIsTurning = false; DbgNavMsg( GetOuter(), "NPC appears stuck turning. Proceeding.\n" ); } } if ( ActivityIsLocomotive( m_PreviousMoveActivity ) && !ActivityIsLocomotive( GetMovementActivity() ) ) { SetMovementActivity( GetOuter()->TranslateActivity( m_PreviousMoveActivity ) ); } } else { m_PreviousMoveActivity = ACT_RESET; m_PreviousArrivalActivity = ACT_RESET; } bool fShouldMove = ShouldMove( bHasGoal ); if ( !fShouldMove ) { // @TODO (toml 10-30-02): the climb part of this is unacceptable, but needed until navigation can handle commencing // a navigation while in the middle of a climb if ( GetNavType() == NAV_CLIMB ) { GetMotor()->MoveClimbStop(); SetNavType( NAV_GROUND ); } GetMotor()->MoveStop(); AssertMsg( TaskIsRunning() || TaskIsComplete(), ("Schedule stalled!!\n") ); return false; } AINavResult_t result = AINR_OK; GetMotor()->SetMoveInterval( flInterval ); // --------------------------------------------------------------------- // Move should never happen if I don't have a movement goal or route // --------------------------------------------------------------------- if ( GetPath()->GoalType() == GOALTYPE_NONE ) { DevWarning( "Move requested with no route!\n" ); result = AINR_NO_GOAL; } else if (!GetPath()->GetCurWaypoint()) { DevWarning( "Move goal with no route!\n" ); GetPath()->Clear(); result = AINR_NO_ROUTE; } if ( result == AINR_OK ) { // --------------------------------------------------------------------- // If I've been asked to wait, let's wait // --------------------------------------------------------------------- if ( GetOuter()->ShouldMoveWait() ) { GetMotor()->MovePaused(); return false; } int nLoopCount = 0; bool bMoved = false; AIMoveResult_t moveResult = AIMR_CHANGE_TYPE; m_fNavComplete = false; while ( moveResult >= AIMR_OK && !m_fNavComplete ) { if ( GetMotor()->GetMoveInterval() <= 0 ) { moveResult = AIMR_OK; break; } // TODO: move higher up the call chain? if ( !m_bCalledStartMove ) { GetMotor()->MoveStart(); m_bCalledStartMove = true; } if ( m_hBigStepGroundEnt && m_hBigStepGroundEnt != GetOuter()->GetGroundEntity() ) m_hBigStepGroundEnt = NULL; switch (GetPath()->CurWaypointNavType()) { case NAV_CRAWL: moveResult = MoveCrawl(); break; case NAV_CLIMB: moveResult = MoveClimb(); break; case NAV_JUMP: moveResult = MoveJump(); break; case NAV_GROUND: case NAV_FLY: moveResult = MoveNormal(); break; default: DevMsg( "Bogus route move type!"); moveResult = AIMR_ILLEGAL; break; } if ( moveResult == AIMR_OK ) bMoved = true; ++nLoopCount; if ( nLoopCount > 16 ) { DevMsg( "ERROR: %s navigation not terminating. Possibly bad cyclical solving?\n", GetOuter()->GetDebugName() ); moveResult = AIMR_ILLEGAL; switch (GetPath()->CurWaypointNavType()) { case NAV_GROUND: case NAV_FLY: case NAV_CRAWL: OnMoveBlocked( &moveResult ); break; } break; } } // -------------------------------------------- // Update move status // -------------------------------------------- if ( IsMoveBlocked( moveResult ) ) { bool bRecovered = false; if (moveResult != AIMR_BLOCKED_NPC || GetNavType() == NAV_CLIMB || GetNavType() == NAV_JUMP || GetPath()->CurWaypointNavType() == NAV_JUMP ) { if ( MarkCurWaypointFailedLink() ) { AI_Waypoint_t *pSavedWaypoints = GetPath()->GetCurWaypoint(); if ( pSavedWaypoints ) { GetPath()->SetWaypoints( NULL ); if ( RefindPathToGoal( false, true ) ) { DeleteAll( pSavedWaypoints ); bRecovered = true; } else GetPath()->SetWaypoints( pSavedWaypoints ); } } } if ( !bRecovered ) { OnNavFailed( ( moveResult == AIMR_ILLEGAL ) ? FAIL_NO_ROUTE_ILLEGAL : FAIL_NO_ROUTE_BLOCKED, true ); } } return bMoved; } static AI_TaskFailureCode_t failures[] = { NO_TASK_FAILURE, // AINR_OK (never should happen) FAIL_NO_ROUTE_GOAL, // AINR_NO_GOAL FAIL_NO_ROUTE, // AINR_NO_ROUTE FAIL_NO_ROUTE_BLOCKED, // AINR_BLOCKED FAIL_NO_ROUTE_ILLEGAL // AINR_ILLEGAL }; OnNavFailed( failures[result], false ); return false; } //----------------------------------------------------------------------------- // Purpose: Returns yaw speed based on what they're doing. //----------------------------------------------------------------------------- float CAI_Navigator::CalcYawSpeed( void ) { // Allow the NPC to override this behavior float flNPCYaw = GetOuter()->CalcYawSpeed(); if (flNPCYaw >= 0.0f) return flNPCYaw; float maxYaw = MaxYawSpeed(); //return maxYaw; if( IsGoalSet() && GetIdealSpeed() != 0.0) { // --------------------------------------------------- // If not moving to a waypoint use a base turing speed // --------------------------------------------------- if (!GetPath()->GetCurWaypoint()) { return maxYaw; } // -------------------------------------------------------------- // If moving towards a waypoint, set the turn speed based on the // distance of the waypoint and my forward velocity // -------------------------------------------------------------- if (GetIdealSpeed() > 0) { // ----------------------------------------------------------------- // Get the projection of npc's heading direction on the waypoint dir // ----------------------------------------------------------------- float waypointDist = (GetPath()->CurWaypointPos() - GetLocalOrigin()).Length(); // If waypoint is close, aim for the waypoint if (waypointDist < 100) { float scale = 1 + (0.01*(100 - waypointDist)); return (maxYaw * scale); } } } return maxYaw; } //----------------------------------------------------------------------------- float CAI_Navigator::GetStepDownMultiplier() { if ( m_hBigStepGroundEnt ) { if ( !m_hBigStepGroundEnt->IsPlayer() ) return 2.6; else return 10.0; } return 1.0; } //----------------------------------------------------------------------------- // Purpose: Attempts to advance the route to the next waypoint, triangulating // around entities that are in the way // Input : // Output : //----------------------------------------------------------------------------- void CAI_Navigator::AdvancePath() { DbgNavMsg( GetOuter(), "Advancing path\n" ); AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); bool bPassingPathcorner = ( ( pCurWaypoint->Flags() & bits_WP_TO_PATHCORNER ) != 0 ); if ( bPassingPathcorner ) { Assert( !pCurWaypoint->GetNext() || (pCurWaypoint->GetNext()->Flags() & (bits_WP_TO_PATHCORNER | bits_WP_TO_GOAL )) == (bits_WP_TO_PATHCORNER | bits_WP_TO_GOAL )); CBaseEntity *pEntity = pCurWaypoint->hPathCorner; if ( pEntity ) { variant_t emptyVariant; pEntity->AcceptInput( "InPass", GetOuter(), pEntity, emptyVariant, 0 ); } } if ( GetPath()->CurWaypointIsGoal() ) return; if ( pCurWaypoint->Flags() & bits_WP_TO_DOOR ) { CBasePropDoor *pDoor = static_cast( pCurWaypoint->GetEHandleData().Get() ); if (pDoor != NULL) { GetOuter()->OpenPropDoorBegin(pDoor); } else { DevMsg("%s trying to open a door that has been deleted!\n", GetOuter()->GetDebugName()); } } GetPath()->Advance(); // If we've just passed a path_corner, advance m_pGoalEnt if ( bPassingPathcorner ) { pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint ) { Assert( (pCurWaypoint->Flags() & (bits_WP_TO_PATHCORNER | bits_WP_TO_GOAL )) == (bits_WP_TO_PATHCORNER | bits_WP_TO_GOAL )); SetGoalEnt( pCurWaypoint->hPathCorner ); DoFindPathToPathcorner( pCurWaypoint->hPathCorner ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #ifdef DEBUG ConVar ai_disable_path_simplification( "ai_disable_path_simplification","0" ); #define IsSimplifyPathDisabled() ai_disable_path_simplification.GetBool() #else #define IsSimplifyPathDisabled() false #endif const float MIN_ANGLE_COS_SIMPLIFY = 0.766; // 40 deg left or right bool CAI_Navigator::ShouldAttemptSimplifyTo( const Vector &pos ) { if ( m_bForcedSimplify ) return true; Vector vecToPos = ( pos - GetLocalOrigin() ); vecToPos.z = 0; VectorNormalize( vecToPos ); Vector vecCurrentDirectionOfMovement = ( GetCurWaypointPos() - GetLocalOrigin() ); vecCurrentDirectionOfMovement.z = 0; VectorNormalize( vecCurrentDirectionOfMovement ); float dot = vecCurrentDirectionOfMovement.AsVector2D().Dot( vecToPos.AsVector2D() ); return ( m_bForcedSimplify || dot > MIN_ANGLE_COS_SIMPLIFY ); } //------------------------------------- bool CAI_Navigator::ShouldSimplifyTo( bool passedDetour, const Vector &pos ) { int flags = AIMLF_QUICK_REJECT; #ifndef NPCS_BLOCK_SIMPLIFICATION if ( !passedDetour ) flags |= AIMLF_IGNORE_TRANSIENTS; #endif AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), pos, GetOuter()->GetAITraceMask(), GetPath()->GetTarget(), 100, flags, &moveTrace ); return !IsMoveBlocked(moveTrace); } //------------------------------------- void CAI_Navigator::SimplifyPathInsertSimplification( AI_Waypoint_t *pSegmentStart, const Vector &point ) { if ( point != pSegmentStart->GetPos() ) { AI_Waypoint_t *pNextWaypoint = pSegmentStart->GetNext(); Assert( pNextWaypoint ); Assert( pSegmentStart->NavType() == pNextWaypoint->NavType() ); AI_Waypoint_t *pNewWaypoint = new AI_Waypoint_t( point, 0, pSegmentStart->NavType(), 0, NO_NODE ); while ( GetPath()->GetCurWaypoint() != pNextWaypoint ) { Assert( GetPath()->GetCurWaypoint()->IsReducible() ); GetPath()->Advance(); } pNewWaypoint->SetNext( pNextWaypoint ); GetPath()->SetWaypoints( pNewWaypoint ); } else { while ( GetPath()->GetCurWaypoint() != pSegmentStart ) { Assert( GetPath()->GetCurWaypoint()->IsReducible() ); GetPath()->Advance(); } } } //------------------------------------- bool CAI_Navigator::SimplifyPathForwardScan( const CAI_Navigator::SimplifyForwardScanParams ¶ms, AI_Waypoint_t *pCurWaypoint, const Vector &curPoint, float distRemaining, bool skipTest, bool passedDetour, int *pTestCount ) { AI_Waypoint_t *pNextWaypoint = pCurWaypoint->GetNext(); if ( !passedDetour ) passedDetour = ( ( pCurWaypoint->Flags() & bits_WP_TO_DETOUR) != 0 ); if ( distRemaining > 0) { if ( pCurWaypoint->IsReducible() ) { // Walk out to test point, or next waypoint AI_Waypoint_t *pRecursionWaypoint; Vector nextPoint; float distToNext = ComputePathDistance( GetNavType(), curPoint, pNextWaypoint->GetPos() ); if (distToNext < params.increment * 1.10 ) { nextPoint = pNextWaypoint->GetPos(); distRemaining -= distToNext; pRecursionWaypoint = pNextWaypoint; } else { Vector offset = pNextWaypoint->GetPos() - pCurWaypoint->GetPos(); VectorNormalize( offset ); offset *= params.increment; nextPoint = curPoint + offset; distRemaining -= params.increment; pRecursionWaypoint = pCurWaypoint; } bool skipTestNext = ( ComputePathDistance( GetNavType(), GetLocalOrigin(), nextPoint ) > params.radius + 0.1 ); if ( SimplifyPathForwardScan( params, pRecursionWaypoint, nextPoint, distRemaining, skipTestNext, passedDetour, pTestCount ) ) return true; } } if ( !skipTest && *pTestCount < params.maxSamples && ShouldAttemptSimplifyTo( curPoint ) ) { (*pTestCount)++; if ( ShouldSimplifyTo( passedDetour, curPoint ) ) { SimplifyPathInsertSimplification( pCurWaypoint, curPoint ); return true; } } return false; } //------------------------------------- bool CAI_Navigator::SimplifyPathForwardScan( const CAI_Navigator::SimplifyForwardScanParams ¶ms ) { AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); float distRemaining = params.scanDist - GetPathDistToCurWaypoint(); int testCount = 0; if ( distRemaining < 0.1 ) return false; if ( SimplifyPathForwardScan( params, pCurWaypoint, pCurWaypoint->GetPos(), distRemaining, true, false, &testCount ) ) return true; return false; } //------------------------------------- bool CAI_Navigator::SimplifyPathForward( float maxDist ) { AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); AI_Waypoint_t *pNextWaypoint = pCurWaypoint->GetNext(); if ( !pNextWaypoint ) return false; AI_PROFILE_SCOPE(CAI_Navigator_SimplifyPathForward); static SimplifyForwardScanParams fullScanParams = { 32 * 12, // Distance to move out path 12 * 12, // Radius within which a point must be to be valid 3 * 12, // Increment to move out on 4, // maximum number of point samples }; SimplifyForwardScanParams scanParams = fullScanParams; if ( maxDist > fullScanParams.radius ) { float ratio = (maxDist / fullScanParams.radius); fullScanParams.radius = maxDist; fullScanParams.scanDist *= ratio; fullScanParams.increment *= ratio; } if ( SimplifyPathForwardScan( scanParams ) ) return true; if ( ShouldAttemptSimplifyTo( pNextWaypoint->GetPos() ) && ComputePathDistance( GetNavType(), GetLocalOrigin(), pNextWaypoint->GetPos() ) < scanParams.scanDist && ShouldSimplifyTo( ( ( pCurWaypoint->Flags() & bits_WP_TO_DETOUR ) != 0 ), pNextWaypoint->GetPos() ) ) // @TODO (toml 04-25-03): need to handle this better. this is here because forward scan may look out so far that a close obvious solution is skipped (due to test limiting) { delete pCurWaypoint; GetPath()->SetWaypoints(pNextWaypoint); return true; } return false; } //------------------------------------- bool CAI_Navigator::SimplifyPathBacktrack() { AI_PROFILE_SCOPE(CAI_Navigator_SimplifyPathBacktrack); AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); AI_Waypoint_t *pNextWaypoint = pCurWaypoint->GetNext(); // ------------------------------------------------------------------------ // If both waypoints are ground waypoints and my path sends me back tracking // more than 24 inches, try to aim for (roughly) the nearest point on the line // connecting the first two waypoints // ------------------------------------------------------------------------ if (pCurWaypoint->GetNext() && (pNextWaypoint->Flags() & bits_WP_TO_NODE) && (pNextWaypoint->NavType() == NAV_GROUND) && (pCurWaypoint->NavType() == NAV_GROUND) && (pCurWaypoint->Flags() & bits_WP_TO_NODE) ) { Vector firstToMe = (GetLocalOrigin() - pCurWaypoint->GetPos()); Vector firstToNext = pNextWaypoint->GetPos() - pCurWaypoint->GetPos(); VectorNormalize(firstToNext); firstToMe.z = 0; firstToNext.z = 0; float firstDist = firstToMe.Length(); float firstProj = DotProduct(firstToMe,firstToNext); float goalTolerance = GetPath()->GetGoalTolerance(); if (firstProj>0.5*firstDist) { Vector targetPos = pCurWaypoint->GetPos() + (firstProj * firstToNext); // Only use a local or jump move int buildFlags = 0; if (CapabilitiesGet() & bits_CAP_MOVE_GROUND) buildFlags |= bits_BUILD_GROUND; if (CapabilitiesGet() & bits_CAP_MOVE_JUMP) buildFlags |= bits_BUILD_JUMP; // Make sure I can get to the new point AI_Waypoint_t *route1 = GetPathfinder()->BuildLocalRoute(GetLocalOrigin(), targetPos, GetPath()->GetTarget(), bits_WP_TO_DETOUR, NO_NODE, buildFlags, goalTolerance); if (!route1) return false; // Make sure the new point gets me to the target location AI_Waypoint_t *route2 = GetPathfinder()->BuildLocalRoute(targetPos, pNextWaypoint->GetPos(), GetPath()->GetTarget(), bits_WP_TO_DETOUR, NO_NODE, buildFlags, goalTolerance); if (!route2) { DeleteAll(route1); return false; } // Add the two route together AddWaypointLists(route1,route2); // Now add the rest of the old route to the new one AddWaypointLists(route1,pNextWaypoint->GetNext()); // Now advance the route linked list, putting the finished waypoints back in the waypoint pool AI_Waypoint_t *freeMe = pCurWaypoint->GetNext(); delete pCurWaypoint; delete freeMe; GetPath()->SetWaypoints(route1); return true; } } return false; } //------------------------------------- bool CAI_Navigator::SimplifyPathQuick() { AI_PROFILE_SCOPE(CAI_Navigator_SimplifyPathQuick); static SimplifyForwardScanParams quickScanParams[2] = { { (12.0 * 12.0) - 0.1, // Distance to move out path 12 * 12, // Radius within which a point must be to be valid 0.5 * 12, // Increment to move out on 1, // maximum number of point samples }, // Strong optimization version { (6.0 * 12.0) - 0.1, // Distance to move out path 8 * 12, // Radius within which a point must be to be valid 1.0 * 12, // Increment to move out on 1, // maximum number of point samples } }; if ( SimplifyPathForwardScan( quickScanParams[AIStrongOpt()] ) ) return true; return false; } //------------------------------------- // Second entry is the strong opt version const float ROUTE_SIMPLIFY_TIME_DELAY[2] = { 0.5, 1.0f }; const float NO_PVS_ROUTE_SIMPLIFY_TIME_DELAY[2] = { 1.0, 2.0f }; const float QUICK_SIMPLIFY_TIME_DELAY[2] = { FLT_MIN, 0.3f }; int g_iFrameLastSimplified; bool CAI_Navigator::SimplifyPath( bool bFirstForPath, float scanDist ) { AI_PROFILE_SCOPE(CAI_Navigator_SimplifyPath); if ( TestingSteering() || IsSimplifyPathDisabled() ) return false; bool bInPVS = GetOuter()->HasCondition( COND_IN_PVS ); bool bRetVal = false; Navigation_t navType = GetOuter()->GetNavType(); if (navType == NAV_GROUND || navType == NAV_FLY) { AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( !pCurWaypoint || !pCurWaypoint->IsReducible() ) return false; //----------------------------- bool bFullSimplify; bFullSimplify = ( m_flNextSimplifyTime <= gpGlobals->curtime ); if ( bFirstForPath && !bFullSimplify ) { bFullSimplify = bInPVS; } if ( AIStrongOpt() && bFullSimplify ) { if ( g_iFrameLastSimplified != gpGlobals->framecount ) { g_iFrameLastSimplified = gpGlobals->framecount; } else { bFullSimplify = false; } } m_bForcedSimplify = bFirstForPath; //----------------------------- if ( bFullSimplify ) { float simplifyDelay = ( bInPVS ) ? ROUTE_SIMPLIFY_TIME_DELAY[AIStrongOpt()] : NO_PVS_ROUTE_SIMPLIFY_TIME_DELAY[AIStrongOpt()]; if ( GetOuter()->GetMoveEfficiency() > AIME_NORMAL ) simplifyDelay *= 2; m_flNextSimplifyTime = gpGlobals->curtime + simplifyDelay; if ( SimplifyPathForward( scanDist ) ) bRetVal = true; else if ( SimplifyPathBacktrack() ) bRetVal = true; else if ( SimplifyPathQuick() ) bRetVal = true; } else if ( bFirstForPath || ( bInPVS && GetOuter()->GetMoveEfficiency() == AIME_NORMAL ) ) { if ( !AIStrongOpt() || gpGlobals->curtime - m_flLastSuccessfulSimplifyTime > QUICK_SIMPLIFY_TIME_DELAY[AIStrongOpt()] ) { if ( SimplifyPathQuick() ) bRetVal = true; } } } if ( bRetVal ) { m_flLastSuccessfulSimplifyTime = gpGlobals->curtime; DbgNavMsg( GetOuter(), "Simplified path\n" ); } return bRetVal; } //----------------------------------------------------------------------------- AI_NavPathProgress_t CAI_Navigator::ProgressFlyPath( const AI_ProgressFlyPathParams_t ¶ms ) { if ( IsGoalActive() ) { float waypointDist = ( GetCurWaypointPos() - GetLocalOrigin() ).Length(); if ( CurWaypointIsGoal() ) { float tolerance = MAX( params.goalTolerance, GetPath()->GetGoalTolerance() ); if ( waypointDist <= tolerance ) return AINPP_COMPLETE; } else { bool bIsStrictWaypoint = ( (GetPath()->CurWaypointFlags() & (bits_WP_TO_PATHCORNER|bits_WP_DONT_SIMPLIFY) ) != 0 ); float tolerance = (bIsStrictWaypoint) ? params.strictPointTolerance : params.waypointTolerance; if ( waypointDist <= tolerance ) { trace_t tr; AI_TraceLine( GetAbsOrigin(), GetPath()->GetCurWaypoint()->GetNext()->GetPos(), GetOuter()->GetAITraceMask(), GetOuter(), COLLISION_GROUP_NONE, &tr ); if ( tr.fraction == 1.0f ) { AdvancePath(); return AINPP_ADVANCED; } } if ( SimplifyFlyPath( params ) ) return AINPP_ADVANCED; } } return AINPP_NO_CHANGE; } //----------------------------------------------------------------------------- void CAI_Navigator::SimplifyFlyPath( unsigned collisionMask, const CBaseEntity *pTarget, float strictPointTolerance, float blockTolerance, AI_NpcBlockHandling_t blockHandling) { AI_ProgressFlyPathParams_t params( collisionMask, strictPointTolerance, blockTolerance, 0, 0, blockHandling ); params.SetCurrent( pTarget ); SimplifyFlyPath( params ); } //----------------------------------------------------------------------------- #define FLY_ROUTE_SIMPLIFY_TIME_DELAY 0.3 #define FLY_ROUTE_SIMPLIFY_LOOK_DIST (12.0*12.0) bool CAI_Navigator::SimplifyFlyPath( const AI_ProgressFlyPathParams_t ¶ms ) { if ( !GetPath()->GetCurWaypoint() ) return false; if ( m_flNextSimplifyTime > gpGlobals->curtime) return false; m_flNextSimplifyTime = gpGlobals->curtime + FLY_ROUTE_SIMPLIFY_TIME_DELAY; if ( params.bTrySimplify && SimplifyPathForward( FLY_ROUTE_SIMPLIFY_LOOK_DIST ) ) return true; // don't shorten path_corners bool bIsStrictWaypoint = ( !params.bTrySimplify || ( (GetPath()->CurWaypointFlags() & (bits_WP_TO_PATHCORNER|bits_WP_DONT_SIMPLIFY) ) != 0 ) ); Vector dir = GetCurWaypointPos() - GetLocalOrigin(); float length = VectorNormalize( dir ); if ( !bIsStrictWaypoint || length < params.strictPointTolerance ) { // FIXME: This seems strange... Why should this condition ever be met? // Don't advance your waypoint if you don't have one! if (GetPath()->CurWaypointIsGoal()) return false; AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_FLY, GetLocalOrigin(), GetPath()->NextWaypointPos(), params.collisionMask, params.pTarget, &moveTrace); if ( moveTrace.flDistObstructed - params.blockTolerance < 0.01 || ( ( params.blockHandling == AISF_IGNORE) && ( moveTrace.fStatus == AIMR_BLOCKED_NPC ) ) ) { AdvancePath(); return true; } else if ( moveTrace.pObstruction && params.blockHandling == AISF_AVOID ) { PrependLocalAvoidance( params.blockTolerance - moveTrace.flDistObstructed, moveTrace ); } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- float CAI_Navigator::MovementCost( int moveType, Vector &vecStart, Vector &vecEnd ) { float flCost = vecStart.DistTo( vecEnd ); if ( moveType == bits_CAP_MOVE_JUMP || moveType == bits_CAP_MOVE_CLIMB ) { flCost *= 2.0; } // Allow the NPC to override the movement cost GetOuter()->MovementCost( moveType, vecStart, vecEnd, &flCost ); return flCost; } //----------------------------------------------------------------------------- // Purpose: Will the entities hull fit at the given node // Input : // Output : Returns true if hull fits at node //----------------------------------------------------------------------------- bool CAI_Navigator::CanFitAtNode(int nodeNum, unsigned int collisionMask ) { // Make sure I have a network if (!GetNetwork()) { DevMsg("CanFitAtNode() called with no network!\n"); return false; } CAI_Node* pTestNode = GetNetwork()->GetNode(nodeNum); Vector startPos = pTestNode->GetPosition(GetHullType()); // ------------------------------------------------------------------- // Check ground nodes for standable bottom // ------------------------------------------------------------------- if (pTestNode->GetType() == NODE_GROUND) { if (!GetMoveProbe()->CheckStandPosition( startPos, collisionMask )) { return false; } } // ------------------------------------------------------------------- // Check climb exit nodes for standable bottom // ------------------------------------------------------------------- if ((pTestNode->GetType() == NODE_CLIMB) && (pTestNode->m_eNodeInfo & (bits_NODE_CLIMB_BOTTOM | bits_NODE_CLIMB_EXIT ))) { if (!GetMoveProbe()->CheckStandPosition( startPos, collisionMask )) { return false; } } // ------------------------------------------------------------------- // Check that hull fits at position of node // ------------------------------------------------------------------- if (!CanFitAtPosition( startPos, collisionMask )) { startPos.z += GetOuter()->StepHeight(); if (!CanFitAtPosition( startPos, collisionMask )) return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Returns true if NPC's hull fits in the given spot with the // given collision mask // Input : // Output : //----------------------------------------------------------------------------- bool CAI_Navigator::CanFitAtPosition( const Vector &vStartPos, unsigned int collisionMask, bool bIgnoreTransients, bool bAllowPlayerAvoid ) { CTraceFilterNav traceFilter( const_cast(GetOuter()), bIgnoreTransients, GetOuter(), COLLISION_GROUP_NONE, bAllowPlayerAvoid ); Vector vEndPos = vStartPos; vEndPos.z += 0.01; trace_t tr; AI_TraceHull( vStartPos, vEndPos, GetHullMins(), GetHullMaxs(), collisionMask, &traceFilter, &tr ); if (tr.startsolid) { return false; } return true; } //----------------------------------------------------------------------------- float CAI_Navigator::GetPathDistToCurWaypoint() const { return ( GetPath()->GetCurWaypoint() ) ? ComputePathDistance( GetNavType(), GetLocalOrigin(), GetPath()->CurWaypointPos() ) : 0; } //----------------------------------------------------------------------------- // Computes the distance to our goal, rebuilding waypoint distances if necessary. // Returns -1 if we still don't have a valid path length after rebuilding. // // NOTE: this should really be part of GetPathDistToGoal below, but I didn't // want to affect OnFailedSteer this close to shipping! (dvs: 8/16/07) //----------------------------------------------------------------------------- float CAI_Navigator::BuildAndGetPathDistToGoal() { if ( !GetPath() ) return -1; // Make sure it's fresh. GetPath()->GetPathLength(); if ( GetPath()->GetCurWaypoint() && ( GetPath()->GetCurWaypoint()->flPathDistGoal >= 0 ) ) return GetPathDistToGoal(); return -1; } // FIXME: this ignores the fact that flPathDistGoal might be -1, yielding nonsensical results. // See BuildAndGetPathDistToGoal above. float CAI_Navigator::GetPathDistToGoal() const { return ( GetPath()->GetCurWaypoint() ) ? ( GetPathDistToCurWaypoint() + GetPath()->GetCurWaypoint()->flPathDistGoal ) : 0; } //----------------------------------------------------------------------------- // Purpose: Attempts to build a route // Input : // Output : True if successful / false if fail //----------------------------------------------------------------------------- bool CAI_Navigator::FindPath( bool fSignalTaskStatus, bool bDontIgnoreBadLinks ) { // Test to see if we're resolving spiking problems via threading if ( ai_navigator_generate_spikes.GetBool() ) { unsigned int nLargeCount = (INT_MAX>>(ai_navigator_generate_spikes_strength.GetInt())); while ( nLargeCount-- ) {} } bool bRetrying = (HasMemory(bits_MEMORY_PATH_FAILED) && m_timePathRebuildMax != 0 ); if ( bRetrying ) { // If I've passed by fail time, fail this task if (m_timePathRebuildFail < gpGlobals->curtime) { if ( fSignalTaskStatus ) OnNavFailed( FAIL_NO_ROUTE ); else OnNavFailed(); return false; } else if ( m_timePathRebuildNext > gpGlobals->curtime ) { return false; } } bool bFindResult = DoFindPath(); if ( !bDontIgnoreBadLinks && !bFindResult && GetOuter()->IsNavigationUrgent() ) { GetPathfinder()->SetIgnoreBadLinks(); bFindResult = DoFindPath(); } if (bFindResult) { Forget(bits_MEMORY_PATH_FAILED); if (fSignalTaskStatus) { TaskComplete(); } return true; } if (m_timePathRebuildMax == 0) { if ( fSignalTaskStatus ) OnNavFailed( FAIL_NO_ROUTE ); else OnNavFailed(); return false; } else { if ( !bRetrying ) { Remember(bits_MEMORY_PATH_FAILED); m_timePathRebuildFail = gpGlobals->curtime + m_timePathRebuildMax; } m_timePathRebuildNext = gpGlobals->curtime + m_timePathRebuildDelay; return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Called when route fails. Marks last link on which that failure // occured as stale so when then next node route is build that link // will be explictly checked for blockages // Input : // Output : //----------------------------------------------------------------------------- bool CAI_Navigator::MarkCurWaypointFailedLink( void ) { if ( TestingSteering() ) return false; if ( !m_fRememberStaleNodes ) return false; // Prevent a crash in release if( !GetPath() || !GetPath()->GetCurWaypoint() ) return false; bool didMark = false; int startID = GetPath()->GetLastNodeReached(); int endID = GetPath()->GetCurWaypoint()->iNodeID; if ( endID != NO_NODE ) { bool bBlockAll = false; if ( m_hLastBlockingEnt != NULL && !m_hLastBlockingEnt->IsPlayer() && !m_hLastBlockingEnt->IsNPC() && m_hLastBlockingEnt->GetMoveType() == MOVETYPE_VPHYSICS && m_hLastBlockingEnt->VPhysicsGetObject() && ( !m_hLastBlockingEnt->VPhysicsGetObject()->IsMoveable() || m_hLastBlockingEnt->VPhysicsGetObject()->GetMass() > 200 ) ) { // Make sure it's a "large" object // - One dimension is >40 // - Other 2 dimensions are >30 CCollisionProperty *pCollisionProp = m_hLastBlockingEnt->CollisionProp(); bool bFoundLarge = false; bool bFoundSmall = false; Vector vecSize = pCollisionProp->OBBMaxs() - pCollisionProp->OBBMins(); for ( int i = 0; i < 3; i++ ) { if ( vecSize[i] > 40 ) { bFoundLarge = true; } if ( vecSize[i] < 30 ) { bFoundSmall = true; break; } } if ( bFoundLarge && !bFoundSmall ) { Vector vStartPos = GetNetwork()->GetNode( endID )->GetPosition( GetHullType() ); Vector vEndPos = vStartPos; vEndPos.z += 0.01; trace_t tr; UTIL_TraceModel( vStartPos, vEndPos, GetHullMins(), GetHullMaxs(), m_hLastBlockingEnt, COLLISION_GROUP_NONE, &tr ); if ( tr.startsolid ) bBlockAll = true; } } if ( bBlockAll ) { CAI_Node *pDestNode = GetNetwork()->GetNode( endID ); for ( int i = 0; i < pDestNode->NumLinks(); i++ ) { CAI_Link *pLink = pDestNode->GetLinkByIndex( i ); pLink->m_LinkInfo |= bits_LINK_STALE_SUGGESTED; pLink->m_timeStaleExpires = gpGlobals->curtime + 4.0; didMark = true; } } else if ( startID != NO_NODE ) { // Find link and mark it as stale CAI_Node *pNode = GetNetwork()->GetNode(startID); CAI_Link *pLink = pNode->GetLink( endID ); if ( pLink ) { pLink->m_LinkInfo |= bits_LINK_STALE_SUGGESTED; pLink->m_timeStaleExpires = gpGlobals->curtime + 4.0; didMark = true; } } } return didMark; } //----------------------------------------------------------------------------- // Purpose: Builds a route to the given vecGoal using either local movement // or nodes // Input : // Output : True is route was found, false otherwise //----------------------------------------------------------------------------- bool CAI_Navigator::DoFindPathToPos( ) { CAI_Path * pPath = GetPath(); CAI_Pathfinder *pPathfinder = GetPathfinder(); const Vector & actualGoalPos = pPath->ActualGoalPosition(); CBaseEntity * pTarget = pPath->GetTarget(); float tolerance = pPath->GetGoalTolerance(); Vector origin; if ( gpGlobals->curtime - m_flTimeClipped > 0.11 || m_bLastNavFailed ) m_pClippedWaypoints->RemoveAll(); if ( m_pClippedWaypoints->IsEmpty() ) origin = GetLocalOrigin(); else { AI_Waypoint_t *pLastClipped = m_pClippedWaypoints->GetLast(); origin = pLastClipped->GetPos(); } m_bLastNavFailed = false; pPath->ClearWaypoints(); int nBuildFlags = 0; if ( GetGoalFlags() & AIN_LOCAL_SUCCEEED_ON_WITHIN_TOLERANCE ) { nBuildFlags |= bits_BUILD_GET_CLOSE; } if ( GetGoalFlags() & AIN_NO_LOCAL_NAVIGATION ) { nBuildFlags |= bits_BUILD_NO_LOCAL_NAV; } if ( GetGoalFlags() & AIN_UNLIMITED_DISTANCE ) { nBuildFlags |= bits_BUILD_UNLIMITED_DISTANCE; } AI_Waypoint_t *pFirstWaypoint = pPathfinder->BuildRoute( origin, actualGoalPos, pTarget, tolerance, GetNavType(), nBuildFlags ); if (!pFirstWaypoint) { // Sorry no path return false; } pPath->SetWaypoints( pFirstWaypoint ); if ( ShouldTestFailPath() ) { pPath->ClearWaypoints(); return false; } if ( !m_pClippedWaypoints->IsEmpty() ) { AI_Waypoint_t *pFirstClipped = m_pClippedWaypoints->GetFirst(); m_pClippedWaypoints->Set( NULL ); pFirstClipped->ModifyFlags( bits_WP_DONT_SIMPLIFY, true ); pPath->PrependWaypoints( pFirstClipped ); pFirstWaypoint = pFirstClipped; } if ( pFirstWaypoint->IsReducible() && pFirstWaypoint->GetNext() && pFirstWaypoint->GetNext()->NavType() == GetNavType() && ShouldOptimizeInitialPathSegment( pFirstWaypoint ) ) { // If we're seemingly beyond the waypoint, and our hull is over the line, move on const float EPS = 0.1; Vector vClosest; CalcClosestPointOnLineSegment( origin, pFirstWaypoint->GetPos(), pFirstWaypoint->GetNext()->GetPos(), vClosest ); if ( ( pFirstWaypoint->GetPos() - vClosest ).Length() > EPS && ( origin - vClosest ).Length() < GetHullWidth() * 0.5 ) { pPath->Advance(); } } return true; } //----------------------------------------------------------------------------- CBaseEntity *CAI_Navigator::GetNextPathcorner( CBaseEntity *pPathCorner ) { CBaseEntity *pNextPathCorner = NULL; Assert( pPathCorner ); if ( pPathCorner ) { pNextPathCorner = pPathCorner->GetNextTarget(); CAI_Hint *pHint; if ( !pNextPathCorner && ( pHint = dynamic_cast( pPathCorner ) ) != NULL ) { int targetNode = pHint->GetTargetNode(); if ( targetNode != NO_NODE ) { CAI_Node *pTestNode = GetNetwork()->GetNode(targetNode); pNextPathCorner = pTestNode->GetHint(); } } } return pNextPathCorner; } //----------------------------------------------------------------------------- bool CAI_Navigator::DoFindPathToPathcorner( CBaseEntity *pPathCorner ) { // UNDONE: This is broken // UNDONE: Remove this and change the pathing code to be able to refresh itself and support // circular paths, etc. bool returnCode = false; Assert( GetPath()->GoalType() == GOALTYPE_PATHCORNER ); // NPC is on a path_corner loop if ( pPathCorner != NULL ) { // Find path to first pathcorner if ( ( GetGoalFlags() & AIN_NO_PATHCORNER_PATHFINDING ) || m_bNoPathcornerPathfinds ) { // HACKHACK: If the starting path_corner has a speed, copy that to the entity if ( pPathCorner->m_flSpeed != 0 ) SetSpeed( pPathCorner->m_flSpeed ); GetPath()->ClearWaypoints(); AI_Waypoint_t *pRoute = new AI_Waypoint_t( pPathCorner->GetLocalOrigin(), 0, GetNavType(), bits_WP_TO_PATHCORNER, NO_NODE ); pRoute->hPathCorner = pPathCorner; AI_Waypoint_t *pLast = pRoute; pPathCorner = GetNextPathcorner(pPathCorner); if ( pPathCorner ) { pLast = new AI_Waypoint_t( pPathCorner->GetLocalOrigin(), 0, GetNavType(), bits_WP_TO_PATHCORNER, NO_NODE ); pLast->hPathCorner = pPathCorner; pRoute->SetNext(pLast); } pLast->ModifyFlags( bits_WP_TO_GOAL, true ); GetPath()->SetWaypoints( pRoute, true ); returnCode = true; } else { Vector initPos = pPathCorner->GetLocalOrigin(); TranslateNavGoal( pPathCorner, initPos ); GetPath()->ResetGoalPosition( initPos ); float tolerance = GetPath()->GetGoalTolerance(); float outerTolerance = GetOuter()->GetDefaultNavGoalTolerance(); if ( outerTolerance > tolerance ) { GetPath()->SetGoalTolerance( outerTolerance ); tolerance = outerTolerance; } if ( ( returnCode = DoFindPathToPos() ) != false ) { // HACKHACK: If the starting path_corner has a speed, copy that to the entity if ( pPathCorner->m_flSpeed != 0 ) { SetSpeed( pPathCorner->m_flSpeed ); } AI_Waypoint_t *lastWaypoint = GetPath()->GetGoalWaypoint(); Assert(lastWaypoint); lastWaypoint->ModifyFlags( bits_WP_TO_PATHCORNER, true ); lastWaypoint->hPathCorner = pPathCorner; pPathCorner = GetNextPathcorner(pPathCorner); // first already accounted for in pathfind if ( pPathCorner ) { // Place a dummy node in that will be used to signal the next pathfind, also prevents // animation system from decellerating when approaching a pathcorner lastWaypoint->ModifyFlags( bits_WP_TO_GOAL, false ); // BRJ 10/4/02 // FIXME: I'm not certain about the navtype here AI_Waypoint_t *curWaypoint = new AI_Waypoint_t( pPathCorner->GetLocalOrigin(), 0, GetNavType(), (bits_WP_TO_PATHCORNER | bits_WP_TO_GOAL), NO_NODE ); Vector waypointPos = curWaypoint->GetPos(); TranslateNavGoal( pPathCorner, waypointPos ); curWaypoint->SetPos( waypointPos ); GetPath()->SetGoalTolerance( tolerance ); curWaypoint->hPathCorner = pPathCorner; lastWaypoint->SetNext(curWaypoint); GetPath()->ResetGoalPosition( curWaypoint->GetPos() ); } } } } return returnCode; } //----------------------------------------------------------------------------- // Purpose: Attemps to find a route // Input : // Output : //----------------------------------------------------------------------------- bool CAI_Navigator::DoFindPath( void ) { AI_PROFILE_SCOPE(CAI_Navigator_DoFindPath); DbgNavMsg( GetOuter(), "Finding new path\n" ); GetPath()->ClearWaypoints(); bool returnCode; returnCode = false; switch( GetPath()->GoalType() ) { case GOALTYPE_PATHCORNER: { returnCode = DoFindPathToPathcorner( GetGoalEnt() ); } break; case GOALTYPE_ENEMY: { // NOTE: This is going to set the goal position, which was *not* // set in SetGoal for this movement type CBaseEntity *pEnemy = GetPath()->GetTarget(); if (pEnemy) { Assert( pEnemy == GetEnemy() ); Vector newPos = GetEnemyLKP(); float tolerance = GetPath()->GetGoalTolerance(); float outerTolerance = GetOuter()->GetDefaultNavGoalTolerance(); if ( outerTolerance > tolerance ) { GetPath()->SetGoalTolerance( outerTolerance ); tolerance = outerTolerance; } TranslateNavGoal( pEnemy, newPos ); // NOTE: Calling reset here because this can get called // any time we have to update our goal position GetPath()->ResetGoalPosition( newPos ); GetPath()->SetGoalTolerance( tolerance ); returnCode = DoFindPathToPos(); } } break; case GOALTYPE_LOCATION: case GOALTYPE_FLANK: case GOALTYPE_COVER: returnCode = DoFindPathToPos(); break; case GOALTYPE_LOCATION_NEAREST_NODE: { int myNodeID; int destNodeID; returnCode = false; myNodeID = GetPathfinder()->NearestNodeToNPC(); if (myNodeID != NO_NODE) { destNodeID = GetNetwork()->NearestNodeToPoint( GetPath()->ActualGoalPosition(), false ); if (destNodeID != NO_NODE) { AI_Waypoint_t *pRoute = GetPathfinder()->FindBestPath( myNodeID, destNodeID ); if ( pRoute != NULL ) { GetPath()->SetWaypoints( pRoute ); GetPath()->SetLastNodeAsGoal(true); returnCode = true; } } } } break; case GOALTYPE_TARGETENT: { // NOTE: This is going to set the goal position, which was *not* // set in SetGoal for this movement type CBaseEntity *pTarget = GetPath()->GetTarget(); if ( pTarget ) { Assert( pTarget == GetTarget() ); // NOTE: Calling reset here because this can get called // any time we have to update our goal position Vector initPos = pTarget->GetAbsOrigin(); TranslateNavGoal( pTarget, initPos ); GetPath()->ResetGoalPosition( initPos ); returnCode = DoFindPathToPos(); } } break; } return returnCode; } //----------------------------------------------------------------------------- void CAI_Navigator::ClearPath( void ) { OnClearPath(); m_timePathRebuildMax = 0; // How long to try rebuilding path before failing task m_timePathRebuildFail = 0; // Current global time when should fail building path m_timePathRebuildNext = 0; // Global time to try rebuilding again m_timePathRebuildDelay = 0; // How long to wait before trying to rebuild again Forget( bits_MEMORY_PATH_FAILED ); AI_Waypoint_t *pWaypoint = GetPath()->GetCurWaypoint(); if ( pWaypoint ) { SaveStoppingPath(); m_PreviousMoveActivity = GetMovementActivity(); m_PreviousArrivalActivity = GetArrivalActivity(); if( m_pClippedWaypoints && m_pClippedWaypoints->GetFirst() ) { Assert( m_PreviousMoveActivity > ACT_RESET ); } while ( pWaypoint ) { if ( pWaypoint->iNodeID != NO_NODE ) { CAI_Node *pNode = GetNetwork()->GetNode(pWaypoint->iNodeID); if ( pNode ) { if ( pNode->IsLocked() ) pNode->Unlock(); } } pWaypoint = pWaypoint->GetNext(); } } GetPath()->Clear(); } //----------------------------------------------------------------------------- bool CAI_Navigator::GetStoppingPath( CAI_WaypointList * pClippedWaypoints ) { pClippedWaypoints->RemoveAll(); AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint ) { bool bMustCompleteCurrent = ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP ); float distRemaining = GetMotor()->MinStoppingDist( 0 ); if ( bMustCompleteCurrent ) { float distToCurrent = ComputePathDistance( pCurWaypoint->NavType(), GetLocalOrigin(), pCurWaypoint->GetPos() ); if ( pCurWaypoint->NavType() == NAV_CLIMB ) { if ( pCurWaypoint->GetNext() && pCurWaypoint->GetNext()->NavType() == NAV_CLIMB ) distToCurrent += ComputePathDistance( NAV_CLIMB, pCurWaypoint->GetPos(), pCurWaypoint->GetNext()->GetPos() ); distToCurrent += GetHullWidth() * 2.0; } if ( distToCurrent > distRemaining ) distRemaining = distToCurrent; } if ( bMustCompleteCurrent || distRemaining > 0.1 ) { Vector vPosPrev = GetLocalOrigin(); AI_Waypoint_t *pNextPoint = pCurWaypoint; AI_Waypoint_t *pSavedWaypoints = NULL; AI_Waypoint_t *pLastSavedWaypoint = NULL; AI_Waypoint_t *pNewWaypoint; while ( distRemaining > 0.01 && pNextPoint ) { if ( ( pNextPoint->NavType() == NAV_CLIMB || pNextPoint->NavType() == NAV_JUMP ) && !bMustCompleteCurrent ) { break; } #if PARANOID_NAV_CHECK_ON_MOMENTUM if ( !CanFitAtPosition( pNextPoint->GetPos(), GetAITraceMask() ) ) { break; } #endif if ( pNextPoint->NavType() != NAV_CLIMB || !pNextPoint->GetNext() || pNextPoint->GetNext()->NavType() != NAV_CLIMB ) bMustCompleteCurrent = false; float distToNext = ComputePathDistance( pNextPoint->NavType(), vPosPrev, pNextPoint->GetPos() ); if ( distToNext <= distRemaining + 0.01 ) { pNewWaypoint = new AI_Waypoint_t(*pNextPoint); if ( pNewWaypoint->Flags() & bits_WP_TO_PATHCORNER ) { pNewWaypoint->ModifyFlags( bits_WP_TO_PATHCORNER, false ); pNewWaypoint->hPathCorner = NULL; } pNewWaypoint->ModifyFlags( bits_WP_TO_GOAL | bits_WP_TO_NODE, false ); pNewWaypoint->iNodeID = NO_NODE; if ( pLastSavedWaypoint ) pLastSavedWaypoint->SetNext( pNewWaypoint ); else pSavedWaypoints = pNewWaypoint; pLastSavedWaypoint = pNewWaypoint; vPosPrev = pNextPoint->GetPos(); // NDebugOverlay::Cross3D( vPosPrev, 16, 255, 255, 0, false, 10.0f ); pNextPoint = pNextPoint->GetNext(); distRemaining -= distToNext; } else { Assert( !( pNextPoint->NavType() == NAV_CLIMB || pNextPoint->NavType() == NAV_JUMP ) ); Vector remainder = pNextPoint->GetPos() - vPosPrev; VectorNormalize( remainder ); float yaw = UTIL_VecToYaw( remainder ); remainder *= distRemaining; remainder += vPosPrev; AIMoveTrace_t trace; if ( GetMoveProbe()->MoveLimit( pNextPoint->NavType(), vPosPrev, remainder, GetOuter()->GetAITraceMask(), NULL, 100, AIMLF_DEFAULT | AIMLF_2D, &trace ) ) { pNewWaypoint = new AI_Waypoint_t( trace.vEndPosition, yaw, pNextPoint->NavType(), bits_WP_TO_GOAL, 0); if ( pLastSavedWaypoint ) pLastSavedWaypoint->SetNext( pNewWaypoint ); else pSavedWaypoints = pNewWaypoint; } distRemaining = 0; } } if ( pSavedWaypoints ) { pClippedWaypoints->Set( pSavedWaypoints ); return true; } } } return false; } //----------------------------------------------------------------------------- void CAI_Navigator::IgnoreStoppingPath( void ) { if( m_pClippedWaypoints && m_pClippedWaypoints->GetFirst() ) { AI_Waypoint_t *pWaypoint = m_pClippedWaypoints->GetFirst(); if( pWaypoint->NavType() != NAV_JUMP && pWaypoint->NavType() != NAV_CLIMB ) { m_pClippedWaypoints->RemoveAll(); } } } //----------------------------------------------------------------------------- ConVar ai_use_clipped_paths( "ai_use_clipped_paths", "1" ); void CAI_Navigator::SaveStoppingPath( void ) { m_flTimeClipped = -1; m_pClippedWaypoints->RemoveAll(); AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint ) { if ( ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP ) || ai_use_clipped_paths.GetBool() ) { if ( GetStoppingPath( m_pClippedWaypoints ) ) m_flTimeClipped = gpGlobals->curtime; } } } //----------------------------------------------------------------------------- bool CAI_Navigator::SetGoalFromStoppingPath() { if ( m_pClippedWaypoints && m_pClippedWaypoints->IsEmpty() ) SaveStoppingPath(); if ( m_pClippedWaypoints && !m_pClippedWaypoints->IsEmpty() ) { if ( m_PreviousMoveActivity <= ACT_RESET && GetMovementActivity() <= ACT_RESET ) { m_pClippedWaypoints->RemoveAll(); DevWarning( 2, "%s has a stopping path and no valid. Movement activity: %s (prev %s)\n", GetOuter()->GetDebugName(), ActivityList_NameForIndex(GetMovementActivity()), ActivityList_NameForIndex(m_PreviousMoveActivity) ); return false; } if ( ( m_pClippedWaypoints->GetFirst()->NavType() == NAV_CLIMB || m_pClippedWaypoints->GetFirst()->NavType() == NAV_JUMP ) ) { const Task_t *pCurTask = GetOuter()->GetTask(); if ( pCurTask && pCurTask->iTask == TASK_STOP_MOVING ) { // Clipped paths are used for 2 reasons: Prepending movement that must be finished in the case of climbing/jumping, // and bringing an NPC to a stop. In the second case, we should never be starting climb or jump movement. m_pClippedWaypoints->RemoveAll(); return false; } } if ( !GetPath()->IsEmpty() ) GetPath()->ClearWaypoints(); GetPath()->SetWaypoints( m_pClippedWaypoints->GetFirst(), true ); m_pClippedWaypoints->Set( NULL ); GetPath()->SetGoalType( GOALTYPE_NONE ); GetPath()->SetGoalType( GOALTYPE_LOCATION ); GetPath()->SetGoalTolerance( NAV_STOP_MOVING_TOLERANCE ); Assert( GetPath()->GetCurWaypoint() ); Assert( m_PreviousMoveActivity != ACT_INVALID ); if ( m_PreviousMoveActivity != ACT_RESET ) GetPath()->SetMovementActivity( m_PreviousMoveActivity ); if ( m_PreviousArrivalActivity != ACT_RESET ) GetPath()->SetArrivalActivity( m_PreviousArrivalActivity ); return true; } return false; } //----------------------------------------------------------------------------- static Vector GetRouteColor(Navigation_t navType, int waypointFlags) { if (navType == NAV_JUMP) { return Vector(255,0,0); } if (waypointFlags & bits_WP_TO_GOAL) { return Vector(200,0,255); } if (waypointFlags & bits_WP_TO_DETOUR) { return Vector(0,200,255); } if (waypointFlags & bits_WP_TO_NODE) { return Vector(0,0,255); } else //if (waypointBits & bits_WP_TO_PATHCORNER) { return Vector(0,255,150); } } //----------------------------------------------------------------------------- // Returns a color for debugging purposes //----------------------------------------------------------------------------- static Vector GetWaypointColor(Navigation_t navType) { switch( navType ) { case NAV_JUMP: return Vector(255,90,90); case NAV_GROUND: return Vector(0,255,255); case NAV_CLIMB: return Vector(90,255,255); case NAV_FLY: return Vector(90,90,255); case NAV_CRAWL: return Vector(255,255,0); default: return Vector(255,0,0); } } //----------------------------------------------------------------------------- void CAI_Navigator::DrawDebugRouteOverlay(void) { AI_Waypoint_t *waypoint = GetPath()->GetCurWaypoint(); if (waypoint) { Vector RGB = GetRouteColor(waypoint->NavType(), waypoint->Flags()); NDebugOverlay::Line(GetLocalOrigin(), waypoint->GetPos(), RGB[0],RGB[1],RGB[2], true,0); } while (waypoint) { Vector RGB = GetWaypointColor(waypoint->NavType()); NDebugOverlay::Box(waypoint->GetPos(), Vector(-3,-3,-3),Vector(3,3,3), RGB[0],RGB[1],RGB[2], true,0); if ( waypoint->Flags() & bits_WP_PRECISE_MOVEMENT ) { NDebugOverlay::Box(waypoint->GetPos(), Vector(-6,-6,-6),Vector(6,6,6), 255, 0, 0, true,0); } if ( waypoint->Flags() & bits_WP_DONT_SIMPLIFY ) { NDebugOverlay::Box(waypoint->GetPos(), Vector(-9,-9,-9),Vector(9,9,9), 255, 0, 255, true,0); } if (waypoint->GetNext()) { Vector RGB = GetRouteColor(waypoint->GetNext()->NavType(), waypoint->GetNext()->Flags()); NDebugOverlay::Line(waypoint->GetPos(), waypoint->GetNext()->GetPos(),RGB[0],RGB[1],RGB[2], true,0); } waypoint = waypoint->GetNext(); } if ( GetPath()->GoalType() != GOALTYPE_NONE ) { Vector vecGoalPos = GetPath()->ActualGoalPosition(); Vector vecGoalDir = GetPath()->GetGoalDirection( GetOuter()->GetAbsOrigin() ); NDebugOverlay::Line( vecGoalPos, vecGoalPos + vecGoalDir * 32, 0,0,255, true, 2.0 ); float flYaw = UTIL_VecToYaw( vecGoalDir ); NDebugOverlay::Text( vecGoalPos, CFmtStr("yaw: %f", flYaw), true, 1 ); } } //-----------------------------------------------------------------------------