source-engine/game/server/ai_navigator.cpp
2023-01-16 11:56:05 +03:00

4164 lines
116 KiB
C++

//========= 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 &center, 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( (intp)AIN_NO_NODE == NO_NODE );
#endif
return (AI_PathNode_t)(intp)( GetPathfinder()->NearestNodeToNPC() );
}
//-----------------------------------------------------------------------------
Vector CAI_Navigator::GetNodePos( AI_PathNode_t node )
{
return GetNetwork()->GetNode((intp)node)->GetPosition(GetHullType());
}
//-----------------------------------------------------------------------------
void CAI_Navigator::OnScheduleChange()
{
DbgNavMsg( GetOuter(), "Schedule change\n" );
}
//-----------------------------------------------------------------------------
void CAI_Navigator::OnClearPath(void)
{
}
//-----------------------------------------------------------------------------
void CAI_Navigator::OnNewGoal()
{
DbgNavMsg( GetOuter(), "New Goal\n" );
ResetCalculations();
m_fNavComplete = true;
}
//-----------------------------------------------------------------------------
void CAI_Navigator::OnNavComplete()
{
DbgNavMsg( GetOuter(), "Nav complete\n" );
ResetCalculations();
TaskMovementComplete();
m_fNavComplete = true;
}
//-----------------------------------------------------------------------------
void CAI_Navigator::OnNavFailed( bool bMovement )
{
DbgNavMsg( GetOuter(), "Nav failed\n" );
if ( bMovement )
GetOuter()->OnMovementFailed();
#ifdef DEBUG
if ( CurWaypointIsGoal() )
{
float flWaypointDist = ComputePathDistance( GetNavType(), GetLocalOrigin(), GetPath()->ActualGoalPosition() );
if ( flWaypointDist < GetGoalTolerance() + 0.1 )
{
DevMsg( "Nav failed but NPC was within goal tolerance?\n" );
}
}
#endif
ResetCalculations();
m_fNavComplete = true;
m_bLastNavFailed = true;
}
//-----------------------------------------------------------------------------
void CAI_Navigator::OnNavFailed( AI_TaskFailureCode_t code, bool bMovement )
{
if ( GetOuter()->ShouldFailNav( bMovement ) )
{
OnNavFailed( bMovement );
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 &params,
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 &params )
{
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 &params )
{
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 &params )
{
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 );
}
}
//-----------------------------------------------------------------------------