diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_navigator.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_navigator.cpp')
| -rw-r--r-- | mp/src/game/server/ai_navigator.cpp | 8326 |
1 files changed, 4163 insertions, 4163 deletions
diff --git a/mp/src/game/server/ai_navigator.cpp b/mp/src/game/server/ai_navigator.cpp index 50a21819..d136c923 100644 --- a/mp/src/game/server/ai_navigator.cpp +++ b/mp/src/game/server/ai_navigator.cpp @@ -1,4163 +1,4163 @@ -//========= Copyright 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 <float.h> // 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 ),
- DEFINE_FIELD( m_bLocalSucceedOnWithinTolerance, 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_bLocalSucceedOnWithinTolerance = 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<AI_Waypoint_t> 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<AI_Waypoint_t> 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, MASK_NPCSOLID, 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, MASK_NPCSOLID, 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,
- MASK_NPCSOLID, 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,
- MASK_NPCSOLID, 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::IsGoalSet() const
-{
- return ( GetPath()->GoalType() != GOALTYPE_NONE );
-}
-
-//-----------------------------------------------------------------------------
-
-bool CAI_Navigator::IsGoalActive() const
-{
- return ( GetPath() && !( const_cast<CAI_Path *>(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()
-{
-#ifdef WIN32
- COMPILE_TIME_ASSERT( (int)AIN_NO_NODE == NO_NODE );
-#endif
- return (AI_PathNode_t)( GetPathfinder()->NearestNodeToNPC() );
-}
-
-//-----------------------------------------------------------------------------
-
-Vector CAI_Navigator::GetNodePos( AI_PathNode_t node )
-{
- return GetNetwork()->GetNode((int)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 );
- 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, MASK_NPCSOLID, GetOuter()->StepHeight(), -64, &vTestPoint );
-
- if ( CanFitAtPosition( vTestPoint, MASK_NPCSOLID, false, false ) )
- {
- if ( GetOuter()->GetMoveProbe()->CheckStandPosition( vTestPoint, MASK_NPCSOLID ) )
- {
- 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::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) ), MASK_NPCSOLID, 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(),
- MASK_NPCSOLID, 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<CPhysicsProp *>( 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, MASK_NPCSOLID, NULL, &moveTrace );
- if ( moveTrace.fStatus == AIMR_OK )
- {
- Vector vNewWaypoint = moveTrace.vEndPosition;
- GetMoveProbe()->MoveLimit( GetNavType(), vNewWaypoint, pMoveGoal->target, MASK_NPCSOLID_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, MASK_NPCSOLID, 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),
- MASK_NPCSOLID,
- &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 )
-{
- 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<CPhysicsProp *>( 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;
- }
- }
-
- 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 )
- {
- 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 )
- {
- 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: 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 ) )
- {
- // 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 = ( bHasGoal &&
- // !bIsTurning &&
- ActivityIsLocomotive( GetMovementActivity() ) );
- if ( fShouldMove )
- {
- 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_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:
- 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 );
- }
- else
- {
- // @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;
- }
-
- return true; // assume override move handles stopping issues
-}
-
-
-//-----------------------------------------------------------------------------
-// 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 = (CBasePropDoor *)(CBaseEntity *)pCurWaypoint->GetEHandleData();
- 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, MASK_NPCSOLID,
- 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(), MASK_NPCSOLID, 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 cost;
-
- cost = (vecStart - vecEnd).Length();
-
- if ( moveType == bits_CAP_MOVE_JUMP || moveType == bits_CAP_MOVE_CLIMB )
- {
- cost *= 2.0;
- }
-
- // Allow the NPC to override the movement cost
- GetOuter()->MovementCost( moveType, vecStart, vecEnd, &cost );
-
- return cost;
-}
-
-//-----------------------------------------------------------------------------
-// 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<CAI_BaseNPC *>(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(void)
-{
- 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();
-
- AI_Waypoint_t *pFirstWaypoint = pPathfinder->BuildRoute( origin, actualGoalPos, pTarget, tolerance, GetNavType(), m_bLocalSucceedOnWithinTolerance );
-
- 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<CAI_Hint *>( 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 = GetPathfinder()->NearestNodeToPoint( GetPath()->ActualGoalPosition() );
- 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(), MASK_NPCSOLID ) )
- {
- 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, MASK_NPCSOLID, 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);
-
- 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->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 );
- }
-}
-
-//-----------------------------------------------------------------------------
+//========= Copyright 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 <float.h> // 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 ), + DEFINE_FIELD( m_bLocalSucceedOnWithinTolerance, 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_bLocalSucceedOnWithinTolerance = 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<AI_Waypoint_t> 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<AI_Waypoint_t> 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, MASK_NPCSOLID, 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, MASK_NPCSOLID, 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, + MASK_NPCSOLID, 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, + MASK_NPCSOLID, 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::IsGoalSet() const +{ + return ( GetPath()->GoalType() != GOALTYPE_NONE ); +} + +//----------------------------------------------------------------------------- + +bool CAI_Navigator::IsGoalActive() const +{ + return ( GetPath() && !( const_cast<CAI_Path *>(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() +{ +#ifdef WIN32 + COMPILE_TIME_ASSERT( (int)AIN_NO_NODE == NO_NODE ); +#endif + return (AI_PathNode_t)( GetPathfinder()->NearestNodeToNPC() ); +} + +//----------------------------------------------------------------------------- + +Vector CAI_Navigator::GetNodePos( AI_PathNode_t node ) +{ + return GetNetwork()->GetNode((int)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 ); + 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, MASK_NPCSOLID, GetOuter()->StepHeight(), -64, &vTestPoint ); + + if ( CanFitAtPosition( vTestPoint, MASK_NPCSOLID, false, false ) ) + { + if ( GetOuter()->GetMoveProbe()->CheckStandPosition( vTestPoint, MASK_NPCSOLID ) ) + { + 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::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) ), MASK_NPCSOLID, 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(), + MASK_NPCSOLID, 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<CPhysicsProp *>( 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, MASK_NPCSOLID, NULL, &moveTrace ); + if ( moveTrace.fStatus == AIMR_OK ) + { + Vector vNewWaypoint = moveTrace.vEndPosition; + GetMoveProbe()->MoveLimit( GetNavType(), vNewWaypoint, pMoveGoal->target, MASK_NPCSOLID_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, MASK_NPCSOLID, 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), + MASK_NPCSOLID, + &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 ) +{ + 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<CPhysicsProp *>( 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; + } + } + + 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 ) + { + 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 ) + { + 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: 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 ) ) + { + // 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 = ( bHasGoal && + // !bIsTurning && + ActivityIsLocomotive( GetMovementActivity() ) ); + if ( fShouldMove ) + { + 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_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: + 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 ); + } + else + { + // @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; + } + + return true; // assume override move handles stopping issues +} + + +//----------------------------------------------------------------------------- +// 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 = (CBasePropDoor *)(CBaseEntity *)pCurWaypoint->GetEHandleData(); + 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, MASK_NPCSOLID, + 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(), MASK_NPCSOLID, 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 cost; + + cost = (vecStart - vecEnd).Length(); + + if ( moveType == bits_CAP_MOVE_JUMP || moveType == bits_CAP_MOVE_CLIMB ) + { + cost *= 2.0; + } + + // Allow the NPC to override the movement cost + GetOuter()->MovementCost( moveType, vecStart, vecEnd, &cost ); + + return cost; +} + +//----------------------------------------------------------------------------- +// 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<CAI_BaseNPC *>(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(void) +{ + 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(); + + AI_Waypoint_t *pFirstWaypoint = pPathfinder->BuildRoute( origin, actualGoalPos, pTarget, tolerance, GetNavType(), m_bLocalSucceedOnWithinTolerance ); + + 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<CAI_Hint *>( 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 = GetPathfinder()->NearestNodeToPoint( GetPath()->ActualGoalPosition() ); + 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(), MASK_NPCSOLID ) ) + { + 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, MASK_NPCSOLID, 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); + + 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->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 ); + } +} + +//----------------------------------------------------------------------------- |