You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4163 lines
116 KiB
4163 lines
116 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// @TODO (toml 06-26-02): The entry points in this file need to be organized |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
|
|
#include <float.h> // for FLT_MAX |
|
|
|
#include "animation.h" // for NOMOTION |
|
#include "collisionutils.h" |
|
#include "ndebugoverlay.h" |
|
#include "isaverestore.h" |
|
#include "saverestore_utlvector.h" |
|
|
|
#include "ai_navigator.h" |
|
#include "ai_node.h" |
|
#include "ai_route.h" |
|
#include "ai_routedist.h" |
|
#include "ai_waypoint.h" |
|
#include "ai_pathfinder.h" |
|
#include "ai_link.h" |
|
#include "ai_memory.h" |
|
#include "ai_motor.h" |
|
#include "ai_localnavigator.h" |
|
#include "ai_moveprobe.h" |
|
#include "ai_hint.h" |
|
#include "BasePropDoor.h" |
|
#include "props.h" |
|
#include "physics_npc_solver.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
const char * g_ppszGoalTypes[] = |
|
{ |
|
"GOALTYPE_NONE", |
|
"GOALTYPE_TARGETENT", |
|
"GOALTYPE_ENEMY", |
|
"GOALTYPE_PATHCORNER", |
|
"GOALTYPE_LOCATION", |
|
"GOALTYPE_LOCATION_NEAREST_NODE", |
|
"GOALTYPE_FLANK", |
|
"GOALTYPE_COVER", |
|
}; |
|
|
|
#define AIGetGoalTypeText(type) (g_ppszGoalTypes[(type)]) |
|
|
|
ConVar ai_vehicle_avoidance("ai_vehicle_avoidance", "1", FCVAR_CHEAT ); |
|
|
|
#ifdef DEBUG_AI_NAVIGATION |
|
ConVar ai_debug_nav("ai_debug_nav", "0"); |
|
#endif |
|
|
|
#ifdef DEBUG |
|
ConVar ai_test_nav_failure_handling("ai_test_nav_failure_handling", "0"); |
|
int g_PathFailureCounter; |
|
int g_MoveFailureCounter; |
|
#define ShouldTestFailPath() ( ai_test_nav_failure_handling.GetBool() && g_PathFailureCounter++ % 20 == 0 ) |
|
#define ShouldTestFailMove() ( ai_test_nav_failure_handling.GetBool() && g_MoveFailureCounter++ % 100 == 0 ) |
|
#else |
|
#define ShouldTestFailPath() (0) |
|
#define ShouldTestFailMove() (0) |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
#ifdef DEBUG |
|
bool g_fTestSteering = 0; |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
class CAI_NavInHintGroupFilter : public INearestNodeFilter |
|
{ |
|
public: |
|
CAI_NavInHintGroupFilter( string_t iszGroup = NULL_STRING ) : |
|
m_iszGroup( iszGroup ) |
|
{ |
|
} |
|
|
|
bool IsValid( CAI_Node *pNode ) |
|
{ |
|
if ( !pNode->GetHint() ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( pNode->GetHint()->GetGroup() != m_iszGroup ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool ShouldContinue() |
|
{ |
|
return true; |
|
} |
|
|
|
string_t m_iszGroup; |
|
|
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
const Vector AIN_NO_DEST( FLT_MAX, FLT_MAX, FLT_MAX ); |
|
#define NavVecToString(v) ((v == AIN_NO_DEST) ? "AIN_NO_DEST" : VecToString(v)) |
|
|
|
#define FLIER_CUT_CORNER_DIST 16 // 8 means the npc's bounding box is contained without the box of the node in WC |
|
|
|
#define NAV_STOP_MOVING_TOLERANCE 6 // Goal tolerance for TASK_STOP_MOVING stopping paths |
|
|
|
//----------------------------------------------------------------------------- |
|
// class CAI_Navigator |
|
//----------------------------------------------------------------------------- |
|
|
|
BEGIN_SIMPLE_DATADESC( CAI_Navigator ) |
|
|
|
DEFINE_FIELD( m_navType, FIELD_INTEGER ), |
|
// m_pMotor |
|
// m_pMoveProbe |
|
// m_pLocalNavigator |
|
// m_pAINetwork |
|
DEFINE_EMBEDDEDBYREF( m_pPath ), |
|
// m_pClippedWaypoints (not saved) |
|
// m_flTimeClipped (not saved) |
|
// m_PreviousMoveActivity (not saved) |
|
// m_PreviousArrivalActivity (not saved) |
|
DEFINE_FIELD( m_bValidateActivitySpeed, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bCalledStartMove, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_fNavComplete, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bNotOnNetwork, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bLastNavFailed, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flNextSimplifyTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_bForcedSimplify, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flLastSuccessfulSimplifyTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flTimeLastAvoidanceTriangulate, FIELD_TIME ), |
|
DEFINE_FIELD( m_timePathRebuildMax, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_timePathRebuildDelay, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_timePathRebuildFail, FIELD_TIME ), |
|
DEFINE_FIELD( m_timePathRebuildNext, FIELD_TIME ), |
|
DEFINE_FIELD( m_fRememberStaleNodes, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bNoPathcornerPathfinds, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bLocalSucceedOnWithinTolerance, FIELD_BOOLEAN ), |
|
// m_fPeerMoveWait (think transient) |
|
// m_hPeerWaitingOn (peer move fields do not need to be saved, tied to current schedule and path, which are not saved) |
|
// m_PeerWaitMoveTimer (ibid) |
|
// m_PeerWaitClearTimer(ibid) |
|
// m_NextSidestepTimer (ibid) |
|
DEFINE_FIELD( m_hBigStepGroundEnt, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hLastBlockingEnt, FIELD_EHANDLE ), |
|
// m_vPosBeginFailedSteer (reset on load) |
|
// m_timeBeginFailedSteer (reset on load) |
|
// m_nNavFailCounter (reset on load) |
|
// m_flLastNavFailTime (reset on load) |
|
|
|
END_DATADESC() |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
CAI_Navigator::CAI_Navigator(CAI_BaseNPC *pOuter) |
|
: BaseClass(pOuter) |
|
{ |
|
m_pPath = new CAI_Path; |
|
m_pAINetwork = NULL; |
|
m_bNotOnNetwork = false; |
|
m_flNextSimplifyTime = 0; |
|
|
|
m_flLastSuccessfulSimplifyTime = -1; |
|
|
|
m_pClippedWaypoints = new CAI_WaypointList; |
|
m_flTimeClipped = -1; |
|
|
|
m_bValidateActivitySpeed = true; |
|
m_bCalledStartMove = false; |
|
|
|
// ---------------------------- |
|
|
|
m_navType = NAV_GROUND; |
|
m_fNavComplete = false; |
|
m_bLastNavFailed = false; |
|
|
|
// ---------------------------- |
|
|
|
m_PeerWaitMoveTimer.Set(0.25); // 2 thinks |
|
m_PeerWaitClearTimer.Set(3.0); |
|
m_NextSidestepTimer.Set(5.0); |
|
|
|
m_vPosBeginFailedSteer = vec3_invalid; |
|
m_timeBeginFailedSteer = FLT_MAX; |
|
|
|
m_flTimeLastAvoidanceTriangulate = -1; |
|
|
|
// ---------------------------- |
|
|
|
m_bNoPathcornerPathfinds = false; |
|
m_bLocalSucceedOnWithinTolerance = false; |
|
|
|
m_fRememberStaleNodes = true; |
|
|
|
m_pMotor = NULL; |
|
m_pMoveProbe = NULL; |
|
m_pLocalNavigator = NULL; |
|
|
|
|
|
m_nNavFailCounter = 0; |
|
m_flLastNavFailTime = -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::Init( CAI_Network *pNetwork ) |
|
{ |
|
m_pMotor = GetOuter()->GetMotor(); |
|
m_pMoveProbe = GetOuter()->GetMoveProbe(); |
|
m_pLocalNavigator = GetOuter()->GetLocalNavigator(); |
|
m_pAINetwork = pNetwork; |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
CAI_Navigator::~CAI_Navigator() |
|
{ |
|
delete m_pPath; |
|
m_pClippedWaypoints->RemoveAll(); |
|
delete m_pClippedWaypoints; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
const short AI_NAVIGATOR_SAVE_VERSION = 1; |
|
|
|
void CAI_Navigator::Save( ISave &save ) |
|
{ |
|
save.WriteShort( &AI_NAVIGATOR_SAVE_VERSION ); |
|
|
|
CUtlVector<AI_Waypoint_t> minPathArray; |
|
|
|
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); |
|
if ( pCurWaypoint ) |
|
{ |
|
if ( ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP ) ) |
|
{ |
|
CAI_WaypointList minCompletionPath; |
|
if ( GetStoppingPath( &minCompletionPath ) && !minCompletionPath.IsEmpty() ) |
|
{ |
|
AI_Waypoint_t *pCurrent = minCompletionPath.GetLast(); |
|
while ( pCurrent ) |
|
{ |
|
minPathArray.AddToTail( *pCurrent ); |
|
pCurrent = pCurrent->GetPrev(); |
|
} |
|
minCompletionPath.RemoveAll(); |
|
} |
|
} |
|
} |
|
|
|
SaveUtlVector( &save, &minPathArray, FIELD_EMBEDDED ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::Restore( IRestore &restore ) |
|
{ |
|
short version = restore.ReadShort(); |
|
|
|
if ( version != AI_NAVIGATOR_SAVE_VERSION ) |
|
return; |
|
|
|
CUtlVector<AI_Waypoint_t> minPathArray; |
|
RestoreUtlVector( &restore, &minPathArray, FIELD_EMBEDDED ); |
|
|
|
if ( minPathArray.Count() ) |
|
{ |
|
for ( int i = 0; i < minPathArray.Count(); i++ ) |
|
{ |
|
m_pClippedWaypoints->PrependWaypoint( minPathArray[i].GetPos(), minPathArray[i].NavType(), ( minPathArray[i].Flags() & ~bits_WP_TO_PATHCORNER ), minPathArray[i].flYaw ); |
|
} |
|
m_flTimeClipped = gpGlobals->curtime + 1000; // time passes between restore and onrestore |
|
} |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::ActivityIsLocomotive( Activity activity ) |
|
{ |
|
// FIXME: should be calling HasMovement() for a sequence |
|
return ( activity > ACT_IDLE ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
CAI_Pathfinder *CAI_Navigator::GetPathfinder() |
|
{ |
|
return GetOuter()->GetPathfinder(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
const CAI_Pathfinder *CAI_Navigator::GetPathfinder() const |
|
{ |
|
return GetOuter()->GetPathfinder(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
CBaseEntity *CAI_Navigator::GetNavTargetEntity() |
|
{ |
|
if ( GetGoalType() == GOALTYPE_ENEMY || GetGoalType() == GOALTYPE_TARGETENT ) |
|
return GetOuter()->GetNavTargetEntity(); |
|
return GetPath()->GetTarget(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::TaskMovementComplete() |
|
{ |
|
GetOuter()->TaskMovementComplete(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_Navigator::MaxYawSpeed() |
|
{ |
|
return GetOuter()->MaxYawSpeed(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetSpeed( float fl ) |
|
{ |
|
GetOuter()->m_flSpeed = fl; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::FindPath( const AI_NavGoal_t &goal, unsigned flags ) |
|
{ |
|
CAI_Path *pPath = GetPath(); |
|
|
|
MARK_TASK_EXPENSIVE(); |
|
|
|
// Clear out previous state |
|
if ( flags & AIN_CLEAR_PREVIOUS_STATE ) |
|
pPath->Clear(); |
|
else if ( flags & AIN_CLEAR_TARGET ) |
|
pPath->ClearTarget(); |
|
|
|
// Set the activity |
|
if ( goal.activity != AIN_DEF_ACTIVITY ) |
|
pPath->SetMovementActivity( goal.activity ); |
|
else if ( pPath->GetMovementActivity() == ACT_INVALID ) |
|
pPath->SetMovementActivity( ( GetOuter()->GetState() == NPC_STATE_COMBAT ) ? ACT_RUN : ACT_WALK ); |
|
|
|
// Set the tolerance |
|
if ( goal.tolerance == AIN_HULL_TOLERANCE ) |
|
pPath->SetGoalTolerance( GetHullWidth() ); |
|
else if ( goal.tolerance != AIN_DEF_TOLERANCE ) |
|
pPath->SetGoalTolerance( goal.tolerance ); |
|
else if (pPath->GetGoalTolerance() == 0 ) |
|
pPath->SetGoalTolerance( GetOuter()->GetDefaultNavGoalTolerance() ); |
|
|
|
if (pPath->GetGoalTolerance() < 0.1 ) |
|
DevMsg( GetOuter(), "Suspicious navigation goal tolerance specified\n"); |
|
|
|
pPath->SetWaypointTolerance( GetHullWidth() * 0.5 ); |
|
|
|
pPath->SetGoalType( GOALTYPE_NONE ); // avoids a spurious warning about setting the goal type twice |
|
pPath->SetGoalType( goal.type ); |
|
pPath->SetGoalFlags( goal.flags ); |
|
|
|
CBaseEntity *pPathTarget = goal.pTarget; |
|
if ((goal.type == GOALTYPE_TARGETENT) || (goal.type == GOALTYPE_ENEMY)) |
|
{ |
|
// Guarantee that the path target |
|
if (goal.type == GOALTYPE_TARGETENT) |
|
pPathTarget = GetTarget(); |
|
else |
|
pPathTarget = GetEnemy(); |
|
|
|
Assert( goal.pTarget == AIN_DEF_TARGET || goal.pTarget == pPathTarget ); |
|
|
|
// Set the goal offset |
|
if ( goal.dest != AIN_NO_DEST ) |
|
pPath->SetTargetOffset( goal.dest ); |
|
|
|
// We're not setting the goal position here because |
|
// it's going to be computed + set in DoFindPath. |
|
} |
|
else |
|
{ |
|
// When our goaltype is position based, we have to set |
|
// the goal position here because it won't get set during DoFindPath(). |
|
if ( goal.dest != AIN_NO_DEST ) |
|
pPath->ResetGoalPosition( goal.dest ); |
|
else if ( goal.destNode != AIN_NO_NODE ) |
|
pPath->ResetGoalPosition( GetNodePos( goal.destNode ) ); |
|
} |
|
|
|
if ( pPathTarget > AIN_DEF_TARGET ) |
|
{ |
|
pPath->SetTarget( pPathTarget ); |
|
} |
|
|
|
pPath->ClearWaypoints(); |
|
bool result = FindPath( ( flags & AIN_NO_PATH_TASK_FAIL ) == 0 ); |
|
|
|
if ( result == false ) |
|
{ |
|
if ( flags & AIN_DISCARD_IF_FAIL ) |
|
pPath->Clear(); |
|
else |
|
pPath->SetGoalType( GOALTYPE_NONE ); |
|
} |
|
else |
|
{ |
|
if ( goal.arrivalActivity != AIN_DEF_ACTIVITY && goal.arrivalActivity > ACT_RESET ) |
|
{ |
|
pPath->SetArrivalActivity( goal.arrivalActivity ); |
|
} |
|
else if ( goal.arrivalSequence != -1 ) |
|
{ |
|
pPath->SetArrivalSequence( goal.arrivalSequence ); |
|
} |
|
|
|
// Initialize goal facing direction |
|
// FIXME: This is a poor way to initialize the arrival direction, apps calling SetGoal() |
|
// should do this themselves, and/or it should be part of AI_NavGoal_t |
|
// FIXME: A number of calls to SetGoal() building routes to their enemy but don't set the flags! |
|
if (goal.type == GOALTYPE_ENEMY) |
|
{ |
|
pPath->SetGoalDirection( pPathTarget ); |
|
pPath->SetGoalSpeed( pPathTarget ); |
|
} |
|
else |
|
{ |
|
pPath->SetGoalDirection( pPath->ActualGoalPosition() - GetAbsOrigin() ); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
ConVar ai_navigator_generate_spikes( "ai_navigator_generate_spikes", "0" ); |
|
ConVar ai_navigator_generate_spikes_strength( "ai_navigator_generate_spikes_strength", "8" ); |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::SetGoal( const AI_NavGoal_t &goal, unsigned flags ) |
|
{ |
|
// Queue this up if we're in the middle of a frame |
|
if ( PostFrameNavigationSystem()->IsGameFrameRunning() ) |
|
{ |
|
// Send off the query for queuing |
|
PostFrameNavigationSystem()->EnqueueEntityNavigationQuery( GetOuter(), CreateFunctor( this, &CAI_Navigator::SetGoal, RefToVal( goal ), flags ) ); |
|
|
|
// Complete immediately if we're waiting on that |
|
// FIXME: This will probably cause a lot of subtle little nuisances... |
|
if ( ( flags & AIN_NO_PATH_TASK_FAIL ) == 0 || GetOuter()->IsCurTaskContinuousMove() ) |
|
{ |
|
TaskComplete(); |
|
} |
|
|
|
// For now, always succeed -- we need to deal with failures on the next frame |
|
return true; |
|
} |
|
|
|
CAI_Path *pPath = GetPath(); |
|
|
|
OnNewGoal(); |
|
|
|
// Clear out previous state |
|
if ( flags & AIN_CLEAR_PREVIOUS_STATE ) |
|
ClearPath(); |
|
|
|
if ( GetOuter()->IsCurTaskContinuousMove() || ai_post_frame_navigation.GetBool() ) |
|
flags |= AIN_NO_PATH_TASK_FAIL; |
|
|
|
bool result = FindPath( goal, flags ); |
|
|
|
if ( result == false ) |
|
{ |
|
DbgNavMsg( GetOuter(), "Failed to pathfind to nav goal:\n" ); |
|
DbgNavMsg1( GetOuter(), " Type: %s\n", AIGetGoalTypeText( goal.type) ); |
|
DbgNavMsg1( GetOuter(), " Dest: %s\n", NavVecToString( goal.dest ) ); |
|
DbgNavMsg1( GetOuter(), " Dest node: %p\n", goal.destNode ); |
|
DbgNavMsg1( GetOuter(), " Target: %p\n", goal.pTarget ); |
|
|
|
if ( flags & AIN_DISCARD_IF_FAIL ) |
|
ClearPath(); |
|
} |
|
else |
|
{ |
|
DbgNavMsg( GetOuter(), "New goal set:\n" ); |
|
DbgNavMsg1( GetOuter(), " Type: %s\n", AIGetGoalTypeText( goal.type) ); |
|
DbgNavMsg1( GetOuter(), " Dest: %s\n", NavVecToString( goal.dest ) ); |
|
DbgNavMsg1( GetOuter(), " Dest node: %p\n", goal.destNode ); |
|
DbgNavMsg1( GetOuter(), " Target: %p\n", goal.pTarget ); |
|
DbgNavMsg1( GetOuter(), " Tolerance: %.1f\n", GetPath()->GetGoalTolerance() ); |
|
DbgNavMsg1( GetOuter(), " Waypoint tol: %.1f\n", GetPath()->GetWaypointTolerance() ); |
|
DbgNavMsg1( GetOuter(), " Activity: %s\n", GetOuter()->GetActivityName(GetPath()->GetMovementActivity()) ); |
|
DbgNavMsg1( GetOuter(), " Arrival act: %s\n", GetOuter()->GetActivityName(GetPath()->GetArrivalActivity()) ); |
|
DbgNavMsg1( GetOuter(), " Arrival seq: %d\n", GetPath()->GetArrivalSequence() ); |
|
DbgNavMsg1( GetOuter(), " Goal dir: %s\n", NavVecToString( GetPath()->GetGoalDirection(GetAbsOrigin())) ); |
|
|
|
// Set our ideal yaw. This has to be done *after* finding the path, |
|
// because the goal position may not be set until then |
|
if ( goal.flags & AIN_YAW_TO_DEST ) |
|
{ |
|
DbgNavMsg( GetOuter(), " Yaw to dest\n" ); |
|
GetMotor()->SetIdealYawToTarget( pPath->ActualGoalPosition() ); |
|
} |
|
|
|
SimplifyPath( true, goal.maxInitialSimplificationDist ); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Change the target of the path |
|
//----------------------------------------------------------------------------- |
|
bool CAI_Navigator::SetGoalTarget( CBaseEntity *pEntity, const Vector &offset ) |
|
{ |
|
OnNewGoal(); |
|
CAI_Path *pPath = GetPath(); |
|
pPath->SetTargetOffset( offset ); |
|
pPath->SetTarget( pEntity ); |
|
pPath->ClearWaypoints(); |
|
return FindPath( !GetOuter()->IsCurTaskContinuousMove() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::SetRadialGoal( const Vector &destination, const Vector ¢er, float radius, float arc, float stepDist, bool bClockwise, bool bAirRoute) |
|
{ |
|
DbgNavMsg( GetOuter(), "Set radial goal\n" ); |
|
OnNewGoal(); |
|
GetPath()->SetGoalType(GOALTYPE_LOCATION); |
|
|
|
GetPath()->SetWaypoints( GetPathfinder()->BuildRadialRoute( GetLocalOrigin(), center, destination, radius, arc, stepDist, bClockwise, GetPath()->GetGoalTolerance(), bAirRoute ), true); |
|
GetPath()->SetGoalTolerance( GetOuter()->GetDefaultNavGoalTolerance() ); |
|
|
|
return IsGoalActive(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::SetRandomGoal( const Vector &from, float minPathLength, const Vector &dir ) |
|
{ |
|
DbgNavMsg( GetOuter(), "Set random goal\n" ); |
|
OnNewGoal(); |
|
if ( GetNetwork()->NumNodes() <= 0 ) |
|
return false; |
|
|
|
INearestNodeFilter *pFilter = NULL; |
|
CAI_NavInHintGroupFilter filter; |
|
if ( GetOuter()->GetHintGroup() != NULL_STRING ) |
|
{ |
|
filter.m_iszGroup = GetOuter()->GetHintGroup(); |
|
pFilter = &filter; |
|
} |
|
|
|
int fromNodeID = GetNetwork()->NearestNodeToPoint( GetOuter(), from, true, pFilter ); |
|
|
|
if (fromNodeID == NO_NODE) |
|
return false; |
|
|
|
AI_Waypoint_t* pRoute = GetPathfinder()->FindShortRandomPath( fromNodeID, minPathLength, dir ); |
|
|
|
if (!pRoute) |
|
return false; |
|
|
|
GetPath()->SetGoalType(GOALTYPE_LOCATION); |
|
GetPath()->SetWaypoints(pRoute); |
|
GetPath()->SetLastNodeAsGoal(); |
|
GetPath()->SetGoalTolerance( GetOuter()->GetDefaultNavGoalTolerance() ); |
|
|
|
SimplifyPath( true ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::SetDirectGoal( const Vector &goalPos, Navigation_t navType ) |
|
{ |
|
DbgNavMsg( GetOuter(), "Set direct goal\n" ); |
|
OnNewGoal(); |
|
ClearPath(); |
|
GetPath()->SetGoalType( GOALTYPE_LOCATION ); |
|
GetPath()->SetWaypoints( new AI_Waypoint_t( goalPos, 0, navType, bits_WP_TO_GOAL, NO_NODE ) ); |
|
GetPath()->SetGoalTolerance( GetOuter()->GetDefaultNavGoalTolerance() ); |
|
GetPath()->SetGoalPosition( goalPos ); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Placeholder implementation for wander goals: cast a few random vectors and |
|
// accept the first one that still lies on the navmesh. |
|
// Side effect: vector goal of navigator is set. |
|
// Returns: true on goal set, false otherwise. |
|
static bool SetWanderGoalByRandomVector(CAI_Navigator *pNav, float minRadius, float maxRadius, int numTries) |
|
{ |
|
while (--numTries >= 0) |
|
{ |
|
float dist = random->RandomFloat( minRadius, maxRadius ); |
|
Vector dir = UTIL_YawToVector( random->RandomFloat( 0, 359.99 ) ); |
|
|
|
if ( pNav->SetVectorGoal( dir, dist, minRadius ) ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CAI_Navigator::SetWanderGoal( float minRadius, float maxRadius ) |
|
{ |
|
// @Note (toml 11-07-02): this is a bogus placeholder implementation!!! |
|
// |
|
// First try using a random setvector goal, and then try SetRandomGoal(). |
|
// Except, if we have a hint group, first try SetRandomGoal() (which |
|
// respects hint groups) and then fall back on the setvector. |
|
if( !GetOuter()->GetHintGroup() ) |
|
{ |
|
return ( SetWanderGoalByRandomVector( this, minRadius, maxRadius, 5 ) || |
|
SetRandomGoal( 1 ) ); |
|
} |
|
else |
|
{ |
|
return ( SetRandomGoal(1) || |
|
SetWanderGoalByRandomVector( this, minRadius, maxRadius, 5 ) ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::CalculateDeflection( const Vector &start, const Vector &dir, const Vector &normal, Vector *pResult ) |
|
{ |
|
Vector temp; |
|
CrossProduct( dir, normal, temp ); |
|
CrossProduct( normal, temp, *pResult ); |
|
VectorNormalize( *pResult ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::SetVectorGoal( const Vector &dir, float targetDist, float minDist, bool fShouldDeflect ) |
|
{ |
|
DbgNavMsg( GetOuter(), "Set vector goal\n" ); |
|
|
|
Vector result; |
|
|
|
if ( FindVectorGoal( &result, dir, targetDist, minDist, fShouldDeflect ) ) |
|
return SetGoal( result ); |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::SetVectorGoalFromTarget( const Vector &goalPos, float minDist, bool fShouldDeflect ) |
|
{ |
|
Vector vDir = goalPos; |
|
float dist = ComputePathDirection( GetNavType(), GetLocalOrigin(), goalPos, &vDir ); |
|
return SetVectorGoal( vDir, dist, minDist, fShouldDeflect ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::FindVectorGoal( Vector *pResult, const Vector &dir, float targetDist, float minDist, bool fShouldDeflect ) |
|
{ |
|
AIMoveTrace_t moveTrace; |
|
float distAchieved = 0; |
|
|
|
MARK_TASK_EXPENSIVE(); |
|
|
|
Vector testLoc = GetLocalOrigin() + ( dir * targetDist ); |
|
GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), testLoc, MASK_NPCSOLID, NULL, &moveTrace ); |
|
|
|
if ( moveTrace.fStatus != AIMR_OK ) |
|
{ |
|
distAchieved = targetDist - moveTrace.flDistObstructed; |
|
if ( fShouldDeflect && moveTrace.vHitNormal != vec3_origin ) |
|
{ |
|
Vector vecDeflect; |
|
Vector vecNormal = moveTrace.vHitNormal; |
|
if ( GetNavType() == NAV_GROUND ) |
|
vecNormal.z = 0; |
|
|
|
CalculateDeflection( moveTrace.vEndPosition, dir, vecNormal, &vecDeflect ); |
|
|
|
testLoc = moveTrace.vEndPosition + ( vecDeflect * ( targetDist - distAchieved ) ); |
|
|
|
Vector temp = moveTrace.vEndPosition; |
|
GetMoveProbe()->MoveLimit( GetNavType(), temp, testLoc, MASK_NPCSOLID, NULL, &moveTrace ); |
|
|
|
distAchieved += ( targetDist - distAchieved ) - moveTrace.flDistObstructed; |
|
} |
|
|
|
if ( distAchieved < minDist + 0.01 ) |
|
return false; |
|
} |
|
|
|
*pResult = moveTrace.vEndPosition; |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::SetRandomGoal( float minPathLength, const Vector &dir ) |
|
{ |
|
return SetRandomGoal( GetLocalOrigin(), minPathLength, dir ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::PrependLocalAvoidance( float distObstacle, const AIMoveTrace_t &directTrace ) |
|
{ |
|
if ( AIStrongOpt() ) |
|
return false; |
|
|
|
if ( GetOuter()->IsFlaggedEfficient() ) |
|
return false; |
|
|
|
if ( m_flTimeLastAvoidanceTriangulate >= gpGlobals->curtime ) |
|
return false; // Only triangulate once per think at most |
|
|
|
m_flTimeLastAvoidanceTriangulate = gpGlobals->curtime; |
|
|
|
AI_PROFILE_SCOPE(CAI_Navigator_PrependLocalAvoidance); |
|
|
|
AI_Waypoint_t *pAvoidanceRoute = NULL; |
|
|
|
Vector vStart = GetLocalOrigin(); |
|
|
|
if ( distObstacle < GetHullWidth() * 0.5 ) |
|
{ |
|
AIMoveTrace_t backawayTrace; |
|
Vector vTestBackaway = GetCurWaypointPos() - GetLocalOrigin(); |
|
VectorNormalize( vTestBackaway ); |
|
vTestBackaway *= -GetHullWidth(); |
|
vTestBackaway += GetLocalOrigin(); |
|
|
|
int flags = ( GetNavType() == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT; |
|
|
|
if ( GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), vTestBackaway, |
|
MASK_NPCSOLID, GetNavTargetEntity(), |
|
100.0, |
|
flags, &backawayTrace ) ) |
|
{ |
|
vStart = backawayTrace.vEndPosition; |
|
pAvoidanceRoute = new AI_Waypoint_t( vStart, 0, GetNavType(), bits_WP_TO_DETOUR, NO_NODE ); |
|
} |
|
} |
|
|
|
AI_Waypoint_t *pTriangulation = GetPathfinder()->BuildTriangulationRoute( |
|
vStart, |
|
GetCurWaypointPos(), |
|
GetNavTargetEntity(), |
|
bits_WP_TO_DETOUR, |
|
NO_NODE, |
|
0.0, |
|
distObstacle, |
|
GetNavType() ); |
|
|
|
if ( !pTriangulation ) |
|
{ |
|
delete pAvoidanceRoute; |
|
return false; |
|
} |
|
|
|
if ( pAvoidanceRoute ) |
|
pAvoidanceRoute->SetNext( pTriangulation ); |
|
else |
|
pAvoidanceRoute = pTriangulation; |
|
|
|
// @TODO (toml 02-04-04): it would be better to do this on each triangulation test to |
|
// improve the odds of success. however, difficult in current structure |
|
float moveThisInterval = GetMotor()->CalcIntervalMove(); |
|
Vector dir = pAvoidanceRoute->GetPos() - GetLocalOrigin(); |
|
float dist = VectorNormalize( dir ); |
|
Vector testPos; |
|
if ( dist > moveThisInterval ) |
|
{ |
|
dist = moveThisInterval; |
|
testPos = GetLocalOrigin() + dir * dist; |
|
} |
|
else |
|
{ |
|
testPos = pAvoidanceRoute->GetPos(); |
|
} |
|
|
|
int flags = ( GetNavType() == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT; |
|
|
|
if ( !GetMoveProbe()->MoveLimit( GetNavType(), GetLocalOrigin(), testPos, |
|
MASK_NPCSOLID, GetNavTargetEntity(), |
|
100.0, |
|
flags ) ) |
|
{ |
|
DeleteAll( pAvoidanceRoute ); |
|
return false; |
|
} |
|
|
|
// FIXME: should the route get simplified? |
|
DbgNavMsg( GetOuter(), "Adding triangulation\n" ); |
|
GetPath()->PrependWaypoints( pAvoidanceRoute ); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::PrependWaypoint( const Vector &newPoint, Navigation_t navType, unsigned waypointFlags ) |
|
{ |
|
GetPath()->PrependWaypoint( newPoint, navType, waypointFlags ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
const Vector &CAI_Navigator::GetGoalPos() const |
|
{ |
|
return GetPath()->BaseGoalPosition(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
CBaseEntity *CAI_Navigator::GetGoalTarget() |
|
{ |
|
return GetPath()->GetTarget(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_Navigator::GetGoalTolerance() const |
|
{ |
|
return GetPath()->GetGoalTolerance(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetGoalTolerance(float tolerance) |
|
{ |
|
GetPath()->SetGoalTolerance(tolerance); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::RefindPathToGoal( bool fSignalTaskStatus, bool bDontIgnoreBadLinks ) |
|
{ |
|
return FindPath( fSignalTaskStatus, bDontIgnoreBadLinks ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::UpdateGoalPos( const Vector &goalPos ) |
|
{ |
|
// Queue this up if we're in the middle of a frame |
|
if ( PostFrameNavigationSystem()->IsGameFrameRunning() ) |
|
{ |
|
// Send off the query for queuing |
|
PostFrameNavigationSystem()->EnqueueEntityNavigationQuery( GetOuter(), CreateFunctor( this, &CAI_Navigator::UpdateGoalPos, RefToVal( goalPos ) ) ); |
|
|
|
// For now, always succeed -- we need to deal with failures on the next frame |
|
return true; |
|
} |
|
|
|
DbgNavMsg( GetOuter(), "Updating goal pos\n" ); |
|
|
|
if ( GetNavType() == NAV_JUMP ) |
|
{ |
|
DevMsg( "Updating goal pos while jumping!\n" ); |
|
AssertOnce( 0 ); |
|
return false; |
|
} |
|
|
|
// FindPath should be finding the goal position if the goal type is |
|
// one of these two... We could just ignore the suggested position |
|
// in these two cases I suppose! |
|
Assert( (GetPath()->GoalType() != GOALTYPE_ENEMY) && (GetPath()->GoalType() != GOALTYPE_TARGETENT) ); |
|
|
|
GetPath()->ResetGoalPosition( goalPos ); |
|
if ( FindPath( !GetOuter()->IsCurTaskContinuousMove() ) ) |
|
{ |
|
SimplifyPath( true ); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
Activity CAI_Navigator::SetMovementActivity(Activity activity) |
|
{ |
|
return GetPath()->SetMovementActivity( activity ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::StopMoving( bool bImmediate ) |
|
{ |
|
DbgNavMsg1( GetOuter(), "CAI_Navigator::StopMoving( %d )\n", bImmediate ); |
|
if ( IsGoalSet() ) |
|
{ |
|
if ( bImmediate || !SetGoalFromStoppingPath() ) |
|
{ |
|
OnNavComplete(); |
|
} |
|
} |
|
else |
|
ClearGoal(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::ClearGoal() |
|
{ |
|
DbgNavMsg( GetOuter(), "CAI_Navigator::ClearGoal()\n" ); |
|
ClearPath(); |
|
OnNewGoal(); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
int CAI_Navigator::GetMovementSequence( ) |
|
{ |
|
int sequence = GetPath()->GetMovementSequence( ); |
|
if (sequence == ACT_INVALID) |
|
{ |
|
Activity activity = GetPath()->GetMovementActivity(); |
|
Assert( activity != ACT_INVALID ); |
|
|
|
sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ) ); |
|
if ( sequence == ACT_INVALID ) |
|
{ |
|
DevMsg( GetOuter(), "No appropriate sequence for movement activity %s (%d)\n", GetOuter()->GetActivityName( GetPath()->GetArrivalActivity() ), GetPath()->GetArrivalActivity() ); |
|
|
|
if ( activity == ACT_SCRIPT_CUSTOM_MOVE ) |
|
{ |
|
sequence = GetOuter()->GetScriptCustomMoveSequence(); |
|
} |
|
else |
|
{ |
|
sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( ACT_WALK ) ); |
|
} |
|
} |
|
Assert( sequence != ACT_INVALID ); |
|
GetPath()->SetMovementSequence( sequence ); |
|
} |
|
return sequence; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetMovementSequence( int sequence ) |
|
{ |
|
GetPath()->SetMovementSequence( sequence ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
Activity CAI_Navigator::GetMovementActivity() const |
|
{ |
|
return GetPath()->GetMovementActivity(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetArrivalActivity(Activity activity) |
|
{ |
|
GetPath()->SetArrivalActivity(activity); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
int CAI_Navigator::GetArrivalSequence( int curSequence ) |
|
{ |
|
int sequence = GetPath()->GetArrivalSequence( ); |
|
if (sequence == ACT_INVALID) |
|
{ |
|
Activity activity = GetOuter()->GetStoppedActivity(); |
|
|
|
Assert( activity != ACT_INVALID ); |
|
if (activity == ACT_INVALID) |
|
{ |
|
activity = ACT_IDLE; |
|
} |
|
|
|
sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), curSequence ); |
|
|
|
if ( sequence == ACT_INVALID ) |
|
{ |
|
DevMsg( GetOuter(), "No appropriate sequence for arrival activity %s (%d)\n", GetOuter()->GetActivityName( GetPath()->GetArrivalActivity() ), GetPath()->GetArrivalActivity() ); |
|
sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( ACT_IDLE ), curSequence ); |
|
} |
|
Assert( sequence != ACT_INVALID ); |
|
GetPath()->SetArrivalSequence( sequence ); |
|
} |
|
return sequence; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetArrivalSequence( int sequence ) |
|
{ |
|
GetPath()->SetArrivalActivity( ACT_INVALID ); |
|
GetPath()->SetArrivalSequence( sequence ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
Activity CAI_Navigator::GetArrivalActivity( ) const |
|
{ |
|
return GetPath()->GetArrivalActivity( ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetArrivalDirection( const Vector &goalDirection ) |
|
{ |
|
GetPath()->SetGoalDirection( goalDirection ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetArrivalDirection( const QAngle &goalAngle ) |
|
{ |
|
Vector goalDirection; |
|
|
|
AngleVectors( goalAngle, &goalDirection ); |
|
|
|
GetPath()->SetGoalDirection( goalDirection ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetArrivalDirection( CBaseEntity * pTarget ) |
|
{ |
|
GetPath()->SetGoalDirection( pTarget ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
Vector CAI_Navigator::GetArrivalDirection( ) |
|
{ |
|
return GetPath()->GetGoalDirection( GetAbsOrigin() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetArrivalSpeed( float flSpeed ) |
|
{ |
|
GetPath()->SetGoalSpeed( flSpeed ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_Navigator::GetArrivalSpeed( void ) |
|
{ |
|
float flSpeed = GetPath()->GetGoalSpeed( GetAbsOrigin() ); |
|
|
|
if (flSpeed >= 0.0) |
|
{ |
|
return flSpeed; |
|
} |
|
|
|
int sequence = GetArrivalSequence( ACT_INVALID ); |
|
|
|
if (sequence != ACT_INVALID) |
|
{ |
|
flSpeed = GetOuter()->GetEntryVelocity( sequence ); |
|
SetArrivalSpeed( flSpeed ); |
|
} |
|
else |
|
{ |
|
flSpeed = 0.0; |
|
} |
|
|
|
return flSpeed; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SetArrivalDistance( float flDistance ) |
|
{ |
|
GetPath()->SetGoalStoppingDistance( flDistance ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_Navigator::GetArrivalDistance() const |
|
{ |
|
return GetPath()->GetGoalStoppingDistance(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
const Vector &CAI_Navigator::GetCurWaypointPos() const |
|
{ |
|
return GetPath()->CurWaypointPos(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
int CAI_Navigator::GetCurWaypointFlags() const |
|
{ |
|
return GetPath()->CurWaypointFlags(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
GoalType_t CAI_Navigator::GetGoalType() const |
|
{ |
|
return GetPath()->GoalType(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
int CAI_Navigator::GetGoalFlags() const |
|
{ |
|
return GetPath()->GoalFlags(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::CurWaypointIsGoal() const |
|
{ |
|
return GetPath()->CurWaypointIsGoal(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::IsGoalSet() const |
|
{ |
|
return ( GetPath()->GoalType() != GOALTYPE_NONE ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::IsGoalActive() const |
|
{ |
|
return ( GetPath() && !( const_cast<CAI_Path *>(GetPath())->IsEmpty() ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::GetPointAlongPath( Vector *pResult, float distance, bool fReducibleOnly ) |
|
{ |
|
if ( !GetPath()->GetCurWaypoint() ) |
|
return false; |
|
|
|
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); |
|
AI_Waypoint_t *pEndPoint = pCurWaypoint; |
|
float distRemaining = distance; |
|
float distToNext; |
|
Vector vPosPrev = GetLocalOrigin(); |
|
|
|
while ( pEndPoint->GetNext() ) |
|
{ |
|
distToNext = ComputePathDistance( GetNavType(), vPosPrev, pEndPoint->GetPos() ); |
|
|
|
if ( distToNext > distRemaining) |
|
break; |
|
|
|
distRemaining -= distToNext; |
|
vPosPrev = pEndPoint->GetPos(); |
|
if ( fReducibleOnly && !pEndPoint->IsReducible() ) |
|
break; |
|
pEndPoint = pEndPoint->GetNext(); |
|
} |
|
|
|
Vector &result = *pResult; |
|
float distToEnd = ComputePathDistance( GetNavType(), vPosPrev, pEndPoint->GetPos() ); |
|
if ( distToEnd - distRemaining < 0.1 ) |
|
{ |
|
result = pEndPoint->GetPos(); |
|
} |
|
else |
|
{ |
|
result = pEndPoint->GetPos() - vPosPrev; |
|
VectorNormalize( result ); |
|
result *= distRemaining; |
|
result += vPosPrev; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_Navigator::GetPathDistanceToGoal() |
|
{ |
|
return GetPath()->GetPathDistanceToGoal(GetAbsOrigin()); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_Navigator::GetPathTimeToGoal() |
|
{ |
|
if ( GetOuter()->m_flGroundSpeed ) |
|
return (GetPathDistanceToGoal() / GetOuter()->m_flGroundSpeed); |
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
AI_PathNode_t CAI_Navigator::GetNearestNode() |
|
{ |
|
#ifdef WIN32 |
|
COMPILE_TIME_ASSERT( (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 ¶ms, |
|
AI_Waypoint_t *pCurWaypoint, const Vector &curPoint, |
|
float distRemaining, bool skipTest, bool passedDetour, int *pTestCount ) |
|
{ |
|
AI_Waypoint_t *pNextWaypoint = pCurWaypoint->GetNext(); |
|
|
|
if ( !passedDetour ) |
|
passedDetour = ( ( pCurWaypoint->Flags() & bits_WP_TO_DETOUR) != 0 ); |
|
|
|
if ( distRemaining > 0) |
|
{ |
|
if ( pCurWaypoint->IsReducible() ) |
|
{ |
|
// Walk out to test point, or next waypoint |
|
AI_Waypoint_t *pRecursionWaypoint; |
|
Vector nextPoint; |
|
|
|
float distToNext = ComputePathDistance( GetNavType(), curPoint, pNextWaypoint->GetPos() ); |
|
if (distToNext < params.increment * 1.10 ) |
|
{ |
|
nextPoint = pNextWaypoint->GetPos(); |
|
distRemaining -= distToNext; |
|
pRecursionWaypoint = pNextWaypoint; |
|
} |
|
else |
|
{ |
|
Vector offset = pNextWaypoint->GetPos() - pCurWaypoint->GetPos(); |
|
VectorNormalize( offset ); |
|
offset *= params.increment; |
|
nextPoint = curPoint + offset; |
|
distRemaining -= params.increment; |
|
pRecursionWaypoint = pCurWaypoint; |
|
} |
|
|
|
bool skipTestNext = ( ComputePathDistance( GetNavType(), GetLocalOrigin(), nextPoint ) > params.radius + 0.1 ); |
|
|
|
if ( SimplifyPathForwardScan( params, pRecursionWaypoint, nextPoint, distRemaining, skipTestNext, passedDetour, pTestCount ) ) |
|
return true; |
|
} |
|
} |
|
|
|
if ( !skipTest && *pTestCount < params.maxSamples && ShouldAttemptSimplifyTo( curPoint ) ) |
|
{ |
|
(*pTestCount)++; |
|
|
|
if ( ShouldSimplifyTo( passedDetour, curPoint ) ) |
|
{ |
|
SimplifyPathInsertSimplification( pCurWaypoint, curPoint ); |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
bool CAI_Navigator::SimplifyPathForwardScan( const CAI_Navigator::SimplifyForwardScanParams ¶ms ) |
|
{ |
|
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); |
|
float distRemaining = params.scanDist - GetPathDistToCurWaypoint(); |
|
int testCount = 0; |
|
|
|
if ( distRemaining < 0.1 ) |
|
return false; |
|
|
|
if ( SimplifyPathForwardScan( params, pCurWaypoint, pCurWaypoint->GetPos(), distRemaining, true, false, &testCount ) ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
bool CAI_Navigator::SimplifyPathForward( float maxDist ) |
|
{ |
|
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); |
|
AI_Waypoint_t *pNextWaypoint = pCurWaypoint->GetNext(); |
|
|
|
if ( !pNextWaypoint ) |
|
return false; |
|
|
|
AI_PROFILE_SCOPE(CAI_Navigator_SimplifyPathForward); |
|
|
|
static SimplifyForwardScanParams fullScanParams = |
|
{ |
|
32 * 12, // Distance to move out path |
|
12 * 12, // Radius within which a point must be to be valid |
|
3 * 12, // Increment to move out on |
|
4, // maximum number of point samples |
|
}; |
|
|
|
SimplifyForwardScanParams scanParams = fullScanParams; |
|
if ( maxDist > fullScanParams.radius ) |
|
{ |
|
float ratio = (maxDist / fullScanParams.radius); |
|
|
|
fullScanParams.radius = maxDist; |
|
fullScanParams.scanDist *= ratio; |
|
fullScanParams.increment *= ratio; |
|
} |
|
|
|
if ( SimplifyPathForwardScan( scanParams ) ) |
|
return true; |
|
|
|
if ( ShouldAttemptSimplifyTo( pNextWaypoint->GetPos() ) && |
|
ComputePathDistance( GetNavType(), GetLocalOrigin(), pNextWaypoint->GetPos() ) < scanParams.scanDist && |
|
ShouldSimplifyTo( ( ( pCurWaypoint->Flags() & bits_WP_TO_DETOUR ) != 0 ), pNextWaypoint->GetPos() ) ) // @TODO (toml 04-25-03): need to handle this better. this is here because forward scan may look out so far that a close obvious solution is skipped (due to test limiting) |
|
{ |
|
delete pCurWaypoint; |
|
GetPath()->SetWaypoints(pNextWaypoint); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
bool CAI_Navigator::SimplifyPathBacktrack() |
|
{ |
|
AI_PROFILE_SCOPE(CAI_Navigator_SimplifyPathBacktrack); |
|
|
|
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); |
|
AI_Waypoint_t *pNextWaypoint = pCurWaypoint->GetNext(); |
|
|
|
// ------------------------------------------------------------------------ |
|
// If both waypoints are ground waypoints and my path sends me back tracking |
|
// more than 24 inches, try to aim for (roughly) the nearest point on the line |
|
// connecting the first two waypoints |
|
// ------------------------------------------------------------------------ |
|
if (pCurWaypoint->GetNext() && |
|
(pNextWaypoint->Flags() & bits_WP_TO_NODE) && |
|
(pNextWaypoint->NavType() == NAV_GROUND) && |
|
(pCurWaypoint->NavType() == NAV_GROUND) && |
|
(pCurWaypoint->Flags() & bits_WP_TO_NODE) ) |
|
{ |
|
|
|
Vector firstToMe = (GetLocalOrigin() - pCurWaypoint->GetPos()); |
|
Vector firstToNext = pNextWaypoint->GetPos() - pCurWaypoint->GetPos(); |
|
VectorNormalize(firstToNext); |
|
firstToMe.z = 0; |
|
firstToNext.z = 0; |
|
float firstDist = firstToMe.Length(); |
|
float firstProj = DotProduct(firstToMe,firstToNext); |
|
float goalTolerance = GetPath()->GetGoalTolerance(); |
|
if (firstProj>0.5*firstDist) |
|
{ |
|
Vector targetPos = pCurWaypoint->GetPos() + (firstProj * firstToNext); |
|
|
|
// Only use a local or jump move |
|
int buildFlags = 0; |
|
if (CapabilitiesGet() & bits_CAP_MOVE_GROUND) |
|
buildFlags |= bits_BUILD_GROUND; |
|
if (CapabilitiesGet() & bits_CAP_MOVE_JUMP) |
|
buildFlags |= bits_BUILD_JUMP; |
|
|
|
// Make sure I can get to the new point |
|
AI_Waypoint_t *route1 = GetPathfinder()->BuildLocalRoute(GetLocalOrigin(), targetPos, GetPath()->GetTarget(), bits_WP_TO_DETOUR, NO_NODE, buildFlags, goalTolerance); |
|
if (!route1) |
|
return false; |
|
|
|
// Make sure the new point gets me to the target location |
|
AI_Waypoint_t *route2 = GetPathfinder()->BuildLocalRoute(targetPos, pNextWaypoint->GetPos(), GetPath()->GetTarget(), bits_WP_TO_DETOUR, NO_NODE, buildFlags, goalTolerance); |
|
if (!route2) |
|
{ |
|
DeleteAll(route1); |
|
return false; |
|
} |
|
|
|
// Add the two route together |
|
AddWaypointLists(route1,route2); |
|
|
|
// Now add the rest of the old route to the new one |
|
AddWaypointLists(route1,pNextWaypoint->GetNext()); |
|
|
|
// Now advance the route linked list, putting the finished waypoints back in the waypoint pool |
|
AI_Waypoint_t *freeMe = pCurWaypoint->GetNext(); |
|
delete pCurWaypoint; |
|
delete freeMe; |
|
|
|
GetPath()->SetWaypoints(route1); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
bool CAI_Navigator::SimplifyPathQuick() |
|
{ |
|
AI_PROFILE_SCOPE(CAI_Navigator_SimplifyPathQuick); |
|
|
|
static SimplifyForwardScanParams quickScanParams[2] = |
|
{ |
|
{ |
|
(12.0 * 12.0) - 0.1, // Distance to move out path |
|
12 * 12, // Radius within which a point must be to be valid |
|
0.5 * 12, // Increment to move out on |
|
1, // maximum number of point samples |
|
}, |
|
// Strong optimization version |
|
{ |
|
(6.0 * 12.0) - 0.1, // Distance to move out path |
|
8 * 12, // Radius within which a point must be to be valid |
|
1.0 * 12, // Increment to move out on |
|
1, // maximum number of point samples |
|
} |
|
}; |
|
|
|
if ( SimplifyPathForwardScan( quickScanParams[AIStrongOpt()] ) ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
// Second entry is the strong opt version |
|
const float ROUTE_SIMPLIFY_TIME_DELAY[2] = { 0.5, 1.0f }; |
|
const float NO_PVS_ROUTE_SIMPLIFY_TIME_DELAY[2] = { 1.0, 2.0f }; |
|
const float QUICK_SIMPLIFY_TIME_DELAY[2] = { FLT_MIN, 0.3f }; |
|
|
|
int g_iFrameLastSimplified; |
|
|
|
bool CAI_Navigator::SimplifyPath( bool bFirstForPath, float scanDist ) |
|
{ |
|
AI_PROFILE_SCOPE(CAI_Navigator_SimplifyPath); |
|
|
|
if ( TestingSteering() || IsSimplifyPathDisabled() ) |
|
return false; |
|
|
|
bool bInPVS = GetOuter()->HasCondition( COND_IN_PVS ); |
|
bool bRetVal = false; |
|
|
|
Navigation_t navType = GetOuter()->GetNavType(); |
|
if (navType == NAV_GROUND || navType == NAV_FLY) |
|
{ |
|
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); |
|
if ( !pCurWaypoint || !pCurWaypoint->IsReducible() ) |
|
return false; |
|
|
|
//----------------------------- |
|
|
|
bool bFullSimplify; |
|
|
|
bFullSimplify = ( m_flNextSimplifyTime <= gpGlobals->curtime ); |
|
|
|
if ( bFirstForPath && !bFullSimplify ) |
|
{ |
|
bFullSimplify = bInPVS; |
|
} |
|
|
|
if ( AIStrongOpt() && bFullSimplify ) |
|
{ |
|
if ( g_iFrameLastSimplified != gpGlobals->framecount ) |
|
{ |
|
g_iFrameLastSimplified = gpGlobals->framecount; |
|
} |
|
else |
|
{ |
|
bFullSimplify = false; |
|
} |
|
} |
|
|
|
m_bForcedSimplify = bFirstForPath; |
|
|
|
//----------------------------- |
|
|
|
if ( bFullSimplify ) |
|
{ |
|
float simplifyDelay = ( bInPVS ) ? ROUTE_SIMPLIFY_TIME_DELAY[AIStrongOpt()] : NO_PVS_ROUTE_SIMPLIFY_TIME_DELAY[AIStrongOpt()]; |
|
|
|
if ( GetOuter()->GetMoveEfficiency() > AIME_NORMAL ) |
|
simplifyDelay *= 2; |
|
|
|
m_flNextSimplifyTime = gpGlobals->curtime + simplifyDelay; |
|
|
|
if ( SimplifyPathForward( scanDist ) ) |
|
bRetVal = true; |
|
else if ( SimplifyPathBacktrack() ) |
|
bRetVal = true; |
|
else if ( SimplifyPathQuick() ) |
|
bRetVal = true; |
|
} |
|
else if ( bFirstForPath || ( bInPVS && GetOuter()->GetMoveEfficiency() == AIME_NORMAL ) ) |
|
{ |
|
if ( !AIStrongOpt() || gpGlobals->curtime - m_flLastSuccessfulSimplifyTime > QUICK_SIMPLIFY_TIME_DELAY[AIStrongOpt()] ) |
|
{ |
|
if ( SimplifyPathQuick() ) |
|
bRetVal = true; |
|
} |
|
} |
|
} |
|
|
|
if ( bRetVal ) |
|
{ |
|
m_flLastSuccessfulSimplifyTime = gpGlobals->curtime; |
|
DbgNavMsg( GetOuter(), "Simplified path\n" ); |
|
} |
|
|
|
return bRetVal; |
|
} |
|
//----------------------------------------------------------------------------- |
|
|
|
AI_NavPathProgress_t CAI_Navigator::ProgressFlyPath( const AI_ProgressFlyPathParams_t ¶ms ) |
|
{ |
|
if ( IsGoalActive() ) |
|
{ |
|
float waypointDist = ( GetCurWaypointPos() - GetLocalOrigin() ).Length(); |
|
|
|
if ( CurWaypointIsGoal() ) |
|
{ |
|
float tolerance = MAX( params.goalTolerance, GetPath()->GetGoalTolerance() ); |
|
if ( waypointDist <= tolerance ) |
|
return AINPP_COMPLETE; |
|
} |
|
else |
|
{ |
|
bool bIsStrictWaypoint = ( (GetPath()->CurWaypointFlags() & (bits_WP_TO_PATHCORNER|bits_WP_DONT_SIMPLIFY) ) != 0 ); |
|
float tolerance = (bIsStrictWaypoint) ? params.strictPointTolerance : params.waypointTolerance; |
|
if ( waypointDist <= tolerance ) |
|
{ |
|
trace_t tr; |
|
AI_TraceLine( GetAbsOrigin(), GetPath()->GetCurWaypoint()->GetNext()->GetPos(), MASK_NPCSOLID, GetOuter(), COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.fraction == 1.0f ) |
|
{ |
|
AdvancePath(); |
|
return AINPP_ADVANCED; |
|
} |
|
} |
|
|
|
if ( SimplifyFlyPath( params ) ) |
|
return AINPP_ADVANCED; |
|
} |
|
} |
|
|
|
return AINPP_NO_CHANGE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::SimplifyFlyPath( unsigned collisionMask, const CBaseEntity *pTarget, |
|
float strictPointTolerance, float blockTolerance, |
|
AI_NpcBlockHandling_t blockHandling) |
|
{ |
|
AI_ProgressFlyPathParams_t params( collisionMask, strictPointTolerance, blockTolerance, |
|
0, 0, blockHandling ); |
|
params.SetCurrent( pTarget ); |
|
SimplifyFlyPath( params ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
#define FLY_ROUTE_SIMPLIFY_TIME_DELAY 0.3 |
|
#define FLY_ROUTE_SIMPLIFY_LOOK_DIST (12.0*12.0) |
|
|
|
bool CAI_Navigator::SimplifyFlyPath( const AI_ProgressFlyPathParams_t ¶ms ) |
|
{ |
|
if ( !GetPath()->GetCurWaypoint() ) |
|
return false; |
|
|
|
if ( m_flNextSimplifyTime > gpGlobals->curtime) |
|
return false; |
|
|
|
m_flNextSimplifyTime = gpGlobals->curtime + FLY_ROUTE_SIMPLIFY_TIME_DELAY; |
|
|
|
if ( params.bTrySimplify && SimplifyPathForward( FLY_ROUTE_SIMPLIFY_LOOK_DIST ) ) |
|
return true; |
|
|
|
// don't shorten path_corners |
|
bool bIsStrictWaypoint = ( !params.bTrySimplify || ( (GetPath()->CurWaypointFlags() & (bits_WP_TO_PATHCORNER|bits_WP_DONT_SIMPLIFY) ) != 0 ) ); |
|
|
|
Vector dir = GetCurWaypointPos() - GetLocalOrigin(); |
|
float length = VectorNormalize( dir ); |
|
|
|
if ( !bIsStrictWaypoint || length < params.strictPointTolerance ) |
|
{ |
|
// FIXME: This seems strange... Why should this condition ever be met? |
|
// Don't advance your waypoint if you don't have one! |
|
if (GetPath()->CurWaypointIsGoal()) |
|
return false; |
|
|
|
AIMoveTrace_t moveTrace; |
|
GetMoveProbe()->MoveLimit( NAV_FLY, GetLocalOrigin(), GetPath()->NextWaypointPos(), |
|
params.collisionMask, params.pTarget, &moveTrace); |
|
|
|
if ( moveTrace.flDistObstructed - params.blockTolerance < 0.01 || |
|
( ( params.blockHandling == AISF_IGNORE) && ( moveTrace.fStatus == AIMR_BLOCKED_NPC ) ) ) |
|
{ |
|
AdvancePath(); |
|
return true; |
|
} |
|
else if ( moveTrace.pObstruction && params.blockHandling == AISF_AVOID ) |
|
{ |
|
PrependLocalAvoidance( params.blockTolerance - moveTrace.flDistObstructed, moveTrace ); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
float CAI_Navigator::MovementCost( int moveType, Vector &vecStart, Vector &vecEnd ) |
|
{ |
|
float cost; |
|
|
|
cost = (vecStart - vecEnd).Length(); |
|
|
|
if ( moveType == bits_CAP_MOVE_JUMP || moveType == bits_CAP_MOVE_CLIMB ) |
|
{ |
|
cost *= 2.0; |
|
} |
|
|
|
// Allow the NPC to override the movement cost |
|
GetOuter()->MovementCost( moveType, vecStart, vecEnd, &cost ); |
|
|
|
return cost; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Will the entities hull fit at the given node |
|
// Input : |
|
// Output : Returns true if hull fits at node |
|
//----------------------------------------------------------------------------- |
|
bool CAI_Navigator::CanFitAtNode(int nodeNum, unsigned int collisionMask ) |
|
{ |
|
// Make sure I have a network |
|
if (!GetNetwork()) |
|
{ |
|
DevMsg("CanFitAtNode() called with no network!\n"); |
|
return false; |
|
} |
|
|
|
CAI_Node* pTestNode = GetNetwork()->GetNode(nodeNum); |
|
Vector startPos = pTestNode->GetPosition(GetHullType()); |
|
|
|
// ------------------------------------------------------------------- |
|
// Check ground nodes for standable bottom |
|
// ------------------------------------------------------------------- |
|
if (pTestNode->GetType() == NODE_GROUND) |
|
{ |
|
if (!GetMoveProbe()->CheckStandPosition( startPos, collisionMask )) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
|
|
// ------------------------------------------------------------------- |
|
// Check climb exit nodes for standable bottom |
|
// ------------------------------------------------------------------- |
|
if ((pTestNode->GetType() == NODE_CLIMB) && |
|
(pTestNode->m_eNodeInfo & (bits_NODE_CLIMB_BOTTOM | bits_NODE_CLIMB_EXIT ))) |
|
{ |
|
if (!GetMoveProbe()->CheckStandPosition( startPos, collisionMask )) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
|
|
// ------------------------------------------------------------------- |
|
// Check that hull fits at position of node |
|
// ------------------------------------------------------------------- |
|
if (!CanFitAtPosition( startPos, collisionMask )) |
|
{ |
|
startPos.z += GetOuter()->StepHeight(); |
|
if (!CanFitAtPosition( startPos, collisionMask )) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns true if NPC's hull fits in the given spot with the |
|
// given collision mask |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
bool CAI_Navigator::CanFitAtPosition( const Vector &vStartPos, unsigned int collisionMask, bool bIgnoreTransients, bool bAllowPlayerAvoid ) |
|
{ |
|
CTraceFilterNav traceFilter( const_cast<CAI_BaseNPC *>(GetOuter()), bIgnoreTransients, GetOuter(), COLLISION_GROUP_NONE, bAllowPlayerAvoid ); |
|
|
|
Vector vEndPos = vStartPos; |
|
vEndPos.z += 0.01; |
|
trace_t tr; |
|
AI_TraceHull( vStartPos, vEndPos, |
|
GetHullMins(), GetHullMaxs(), |
|
collisionMask, |
|
&traceFilter, |
|
&tr ); |
|
|
|
if (tr.startsolid) |
|
{ |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_Navigator::GetPathDistToCurWaypoint() const |
|
{ |
|
return ( GetPath()->GetCurWaypoint() ) ? |
|
ComputePathDistance( GetNavType(), GetLocalOrigin(), GetPath()->CurWaypointPos() ) : |
|
0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the distance to our goal, rebuilding waypoint distances if necessary. |
|
// Returns -1 if we still don't have a valid path length after rebuilding. |
|
// |
|
// NOTE: this should really be part of GetPathDistToGoal below, but I didn't |
|
// want to affect OnFailedSteer this close to shipping! (dvs: 8/16/07) |
|
//----------------------------------------------------------------------------- |
|
float CAI_Navigator::BuildAndGetPathDistToGoal() |
|
{ |
|
if ( !GetPath() ) |
|
return -1; |
|
|
|
// Make sure it's fresh. |
|
GetPath()->GetPathLength(); |
|
|
|
if ( GetPath()->GetCurWaypoint() && ( GetPath()->GetCurWaypoint()->flPathDistGoal >= 0 ) ) |
|
return GetPathDistToGoal(); |
|
|
|
return -1; |
|
} |
|
|
|
|
|
// FIXME: this ignores the fact that flPathDistGoal might be -1, yielding nonsensical results. |
|
// See BuildAndGetPathDistToGoal above. |
|
float CAI_Navigator::GetPathDistToGoal() const |
|
{ |
|
return ( GetPath()->GetCurWaypoint() ) ? |
|
( GetPathDistToCurWaypoint() + GetPath()->GetCurWaypoint()->flPathDistGoal ) : |
|
0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Attempts to build a route |
|
// Input : |
|
// Output : True if successful / false if fail |
|
//----------------------------------------------------------------------------- |
|
bool CAI_Navigator::FindPath( bool fSignalTaskStatus, bool bDontIgnoreBadLinks ) |
|
{ |
|
// Test to see if we're resolving spiking problems via threading |
|
if ( ai_navigator_generate_spikes.GetBool() ) |
|
{ |
|
unsigned int nLargeCount = (INT_MAX>>(ai_navigator_generate_spikes_strength.GetInt())); |
|
while ( nLargeCount-- ) {} |
|
} |
|
|
|
bool bRetrying = (HasMemory(bits_MEMORY_PATH_FAILED) && m_timePathRebuildMax != 0 ); |
|
if ( bRetrying ) |
|
{ |
|
// If I've passed by fail time, fail this task |
|
if (m_timePathRebuildFail < gpGlobals->curtime) |
|
{ |
|
if ( fSignalTaskStatus ) |
|
OnNavFailed( FAIL_NO_ROUTE ); |
|
else |
|
OnNavFailed(); |
|
return false; |
|
} |
|
else if ( m_timePathRebuildNext > gpGlobals->curtime ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
bool bFindResult = DoFindPath(); |
|
|
|
if ( !bDontIgnoreBadLinks && !bFindResult && GetOuter()->IsNavigationUrgent() ) |
|
{ |
|
GetPathfinder()->SetIgnoreBadLinks(); |
|
bFindResult = DoFindPath(); |
|
} |
|
|
|
if (bFindResult) |
|
{ |
|
Forget(bits_MEMORY_PATH_FAILED); |
|
|
|
if (fSignalTaskStatus) |
|
{ |
|
TaskComplete(); |
|
} |
|
return true; |
|
} |
|
|
|
if (m_timePathRebuildMax == 0) |
|
{ |
|
if ( fSignalTaskStatus ) |
|
OnNavFailed( FAIL_NO_ROUTE ); |
|
else |
|
OnNavFailed(); |
|
return false; |
|
} |
|
else |
|
{ |
|
if ( !bRetrying ) |
|
{ |
|
Remember(bits_MEMORY_PATH_FAILED); |
|
m_timePathRebuildFail = gpGlobals->curtime + m_timePathRebuildMax; |
|
} |
|
m_timePathRebuildNext = gpGlobals->curtime + m_timePathRebuildDelay; |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when route fails. Marks last link on which that failure |
|
// occured as stale so when then next node route is build that link |
|
// will be explictly checked for blockages |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
bool CAI_Navigator::MarkCurWaypointFailedLink( void ) |
|
{ |
|
if ( TestingSteering() ) |
|
return false; |
|
|
|
if ( !m_fRememberStaleNodes ) |
|
return false; |
|
|
|
// Prevent a crash in release |
|
if( !GetPath() || !GetPath()->GetCurWaypoint() ) |
|
return false; |
|
|
|
bool didMark = false; |
|
|
|
int startID = GetPath()->GetLastNodeReached(); |
|
int endID = GetPath()->GetCurWaypoint()->iNodeID; |
|
|
|
if ( endID != NO_NODE ) |
|
{ |
|
bool bBlockAll = false; |
|
|
|
if ( m_hLastBlockingEnt != NULL && |
|
!m_hLastBlockingEnt->IsPlayer() && !m_hLastBlockingEnt->IsNPC() && |
|
m_hLastBlockingEnt->GetMoveType() == MOVETYPE_VPHYSICS && |
|
m_hLastBlockingEnt->VPhysicsGetObject() && |
|
( !m_hLastBlockingEnt->VPhysicsGetObject()->IsMoveable() || |
|
m_hLastBlockingEnt->VPhysicsGetObject()->GetMass() > 200 ) ) |
|
{ |
|
// Make sure it's a "large" object |
|
// - One dimension is >40 |
|
// - Other 2 dimensions are >30 |
|
CCollisionProperty *pCollisionProp = m_hLastBlockingEnt->CollisionProp(); |
|
bool bFoundLarge = false; |
|
bool bFoundSmall = false; |
|
Vector vecSize = pCollisionProp->OBBMaxs() - pCollisionProp->OBBMins(); |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
if ( vecSize[i] > 40 ) |
|
{ |
|
bFoundLarge = true; |
|
} |
|
|
|
if ( vecSize[i] < 30 ) |
|
{ |
|
bFoundSmall = true; |
|
break; |
|
} |
|
} |
|
|
|
if ( bFoundLarge && !bFoundSmall ) |
|
{ |
|
Vector vStartPos = GetNetwork()->GetNode( endID )->GetPosition( GetHullType() ); |
|
Vector vEndPos = vStartPos; |
|
vEndPos.z += 0.01; |
|
trace_t tr; |
|
|
|
UTIL_TraceModel( vStartPos, vEndPos, GetHullMins(), GetHullMaxs(), m_hLastBlockingEnt, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.startsolid ) |
|
bBlockAll = true; |
|
} |
|
} |
|
|
|
if ( bBlockAll ) |
|
{ |
|
CAI_Node *pDestNode = GetNetwork()->GetNode( endID ); |
|
for ( int i = 0; i < pDestNode->NumLinks(); i++ ) |
|
{ |
|
CAI_Link *pLink = pDestNode->GetLinkByIndex( i ); |
|
pLink->m_LinkInfo |= bits_LINK_STALE_SUGGESTED; |
|
pLink->m_timeStaleExpires = gpGlobals->curtime + 4.0; |
|
didMark = true; |
|
} |
|
|
|
} |
|
else if ( startID != NO_NODE ) |
|
{ |
|
// Find link and mark it as stale |
|
CAI_Node *pNode = GetNetwork()->GetNode(startID); |
|
CAI_Link *pLink = pNode->GetLink( endID ); |
|
if ( pLink ) |
|
{ |
|
pLink->m_LinkInfo |= bits_LINK_STALE_SUGGESTED; |
|
pLink->m_timeStaleExpires = gpGlobals->curtime + 4.0; |
|
didMark = true; |
|
} |
|
} |
|
} |
|
|
|
return didMark; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Builds a route to the given vecGoal using either local movement |
|
// or nodes |
|
// Input : |
|
// Output : True is route was found, false otherwise |
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::DoFindPathToPos(void) |
|
{ |
|
CAI_Path * pPath = GetPath(); |
|
CAI_Pathfinder *pPathfinder = GetPathfinder(); |
|
const Vector & actualGoalPos = pPath->ActualGoalPosition(); |
|
CBaseEntity * pTarget = pPath->GetTarget(); |
|
float tolerance = pPath->GetGoalTolerance(); |
|
Vector origin; |
|
|
|
if ( gpGlobals->curtime - m_flTimeClipped > 0.11 || m_bLastNavFailed ) |
|
m_pClippedWaypoints->RemoveAll(); |
|
|
|
if ( m_pClippedWaypoints->IsEmpty() ) |
|
origin = GetLocalOrigin(); |
|
else |
|
{ |
|
AI_Waypoint_t *pLastClipped = m_pClippedWaypoints->GetLast(); |
|
origin = pLastClipped->GetPos(); |
|
} |
|
|
|
m_bLastNavFailed = false; |
|
|
|
pPath->ClearWaypoints(); |
|
|
|
AI_Waypoint_t *pFirstWaypoint = pPathfinder->BuildRoute( origin, actualGoalPos, pTarget, tolerance, GetNavType(), m_bLocalSucceedOnWithinTolerance ); |
|
|
|
if (!pFirstWaypoint) |
|
{ |
|
// Sorry no path |
|
return false; |
|
} |
|
|
|
pPath->SetWaypoints( pFirstWaypoint ); |
|
|
|
if ( ShouldTestFailPath() ) |
|
{ |
|
pPath->ClearWaypoints(); |
|
return false; |
|
} |
|
|
|
if ( !m_pClippedWaypoints->IsEmpty() ) |
|
{ |
|
AI_Waypoint_t *pFirstClipped = m_pClippedWaypoints->GetFirst(); |
|
m_pClippedWaypoints->Set( NULL ); |
|
pFirstClipped->ModifyFlags( bits_WP_DONT_SIMPLIFY, true ); |
|
pPath->PrependWaypoints( pFirstClipped ); |
|
pFirstWaypoint = pFirstClipped; |
|
} |
|
|
|
if ( pFirstWaypoint->IsReducible() && pFirstWaypoint->GetNext() && pFirstWaypoint->GetNext()->NavType() == GetNavType() && |
|
ShouldOptimizeInitialPathSegment( pFirstWaypoint ) ) |
|
{ |
|
// If we're seemingly beyond the waypoint, and our hull is over the line, move on |
|
const float EPS = 0.1; |
|
Vector vClosest; |
|
CalcClosestPointOnLineSegment( origin, |
|
pFirstWaypoint->GetPos(), pFirstWaypoint->GetNext()->GetPos(), |
|
vClosest ); |
|
if ( ( pFirstWaypoint->GetPos() - vClosest ).Length() > EPS && |
|
( origin - vClosest ).Length() < GetHullWidth() * 0.5 ) |
|
{ |
|
pPath->Advance(); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
CBaseEntity *CAI_Navigator::GetNextPathcorner( CBaseEntity *pPathCorner ) |
|
{ |
|
CBaseEntity *pNextPathCorner = NULL; |
|
|
|
Assert( pPathCorner ); |
|
if ( pPathCorner ) |
|
{ |
|
pNextPathCorner = pPathCorner->GetNextTarget(); |
|
|
|
CAI_Hint *pHint; |
|
if ( !pNextPathCorner && ( pHint = dynamic_cast<CAI_Hint *>( pPathCorner ) ) != NULL ) |
|
{ |
|
int targetNode = pHint->GetTargetNode(); |
|
if ( targetNode != NO_NODE ) |
|
{ |
|
CAI_Node *pTestNode = GetNetwork()->GetNode(targetNode); |
|
pNextPathCorner = pTestNode->GetHint(); |
|
} |
|
} |
|
} |
|
|
|
return pNextPathCorner; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::DoFindPathToPathcorner( CBaseEntity *pPathCorner ) |
|
{ |
|
// UNDONE: This is broken |
|
// UNDONE: Remove this and change the pathing code to be able to refresh itself and support |
|
// circular paths, etc. |
|
bool returnCode = false; |
|
Assert( GetPath()->GoalType() == GOALTYPE_PATHCORNER ); |
|
|
|
// NPC is on a path_corner loop |
|
if ( pPathCorner != NULL ) |
|
{ |
|
// Find path to first pathcorner |
|
if ( ( GetGoalFlags() & AIN_NO_PATHCORNER_PATHFINDING ) || m_bNoPathcornerPathfinds ) |
|
{ |
|
// HACKHACK: If the starting path_corner has a speed, copy that to the entity |
|
if ( pPathCorner->m_flSpeed != 0 ) |
|
SetSpeed( pPathCorner->m_flSpeed ); |
|
|
|
GetPath()->ClearWaypoints(); |
|
|
|
AI_Waypoint_t *pRoute = new AI_Waypoint_t( pPathCorner->GetLocalOrigin(), 0, GetNavType(), bits_WP_TO_PATHCORNER, NO_NODE ); |
|
pRoute->hPathCorner = pPathCorner; |
|
AI_Waypoint_t *pLast = pRoute; |
|
pPathCorner = GetNextPathcorner(pPathCorner); |
|
if ( pPathCorner ) |
|
{ |
|
pLast = new AI_Waypoint_t( pPathCorner->GetLocalOrigin(), 0, GetNavType(), bits_WP_TO_PATHCORNER, NO_NODE ); |
|
pLast->hPathCorner = pPathCorner; |
|
pRoute->SetNext(pLast); |
|
} |
|
pLast->ModifyFlags( bits_WP_TO_GOAL, true ); |
|
GetPath()->SetWaypoints( pRoute, true ); |
|
returnCode = true; |
|
} |
|
else |
|
{ |
|
Vector initPos = pPathCorner->GetLocalOrigin(); |
|
|
|
TranslateNavGoal( pPathCorner, initPos ); |
|
|
|
GetPath()->ResetGoalPosition( initPos ); |
|
|
|
float tolerance = GetPath()->GetGoalTolerance(); |
|
float outerTolerance = GetOuter()->GetDefaultNavGoalTolerance(); |
|
if ( outerTolerance > tolerance ) |
|
{ |
|
GetPath()->SetGoalTolerance( outerTolerance ); |
|
tolerance = outerTolerance; |
|
} |
|
|
|
if ( ( returnCode = DoFindPathToPos() ) != false ) |
|
{ |
|
// HACKHACK: If the starting path_corner has a speed, copy that to the entity |
|
if ( pPathCorner->m_flSpeed != 0 ) |
|
{ |
|
SetSpeed( pPathCorner->m_flSpeed ); |
|
} |
|
|
|
AI_Waypoint_t *lastWaypoint = GetPath()->GetGoalWaypoint(); |
|
Assert(lastWaypoint); |
|
|
|
lastWaypoint->ModifyFlags( bits_WP_TO_PATHCORNER, true ); |
|
lastWaypoint->hPathCorner = pPathCorner; |
|
|
|
pPathCorner = GetNextPathcorner(pPathCorner); // first already accounted for in pathfind |
|
if ( pPathCorner ) |
|
{ |
|
// Place a dummy node in that will be used to signal the next pathfind, also prevents |
|
// animation system from decellerating when approaching a pathcorner |
|
lastWaypoint->ModifyFlags( bits_WP_TO_GOAL, false ); |
|
// BRJ 10/4/02 |
|
// FIXME: I'm not certain about the navtype here |
|
AI_Waypoint_t *curWaypoint = new AI_Waypoint_t( pPathCorner->GetLocalOrigin(), 0, GetNavType(), (bits_WP_TO_PATHCORNER | bits_WP_TO_GOAL), NO_NODE ); |
|
Vector waypointPos = curWaypoint->GetPos(); |
|
TranslateNavGoal( pPathCorner, waypointPos ); |
|
curWaypoint->SetPos( waypointPos ); |
|
GetPath()->SetGoalTolerance( tolerance ); |
|
curWaypoint->hPathCorner = pPathCorner; |
|
lastWaypoint->SetNext(curWaypoint); |
|
GetPath()->ResetGoalPosition( curWaypoint->GetPos() ); |
|
} |
|
} |
|
} |
|
} |
|
return returnCode; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Attemps to find a route |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
bool CAI_Navigator::DoFindPath( void ) |
|
{ |
|
AI_PROFILE_SCOPE(CAI_Navigator_DoFindPath); |
|
|
|
DbgNavMsg( GetOuter(), "Finding new path\n" ); |
|
|
|
GetPath()->ClearWaypoints(); |
|
|
|
bool returnCode; |
|
|
|
returnCode = false; |
|
|
|
switch( GetPath()->GoalType() ) |
|
{ |
|
case GOALTYPE_PATHCORNER: |
|
{ |
|
returnCode = DoFindPathToPathcorner( GetGoalEnt() ); |
|
} |
|
break; |
|
|
|
case GOALTYPE_ENEMY: |
|
{ |
|
// NOTE: This is going to set the goal position, which was *not* |
|
// set in SetGoal for this movement type |
|
CBaseEntity *pEnemy = GetPath()->GetTarget(); |
|
if (pEnemy) |
|
{ |
|
Assert( pEnemy == GetEnemy() ); |
|
|
|
Vector newPos = GetEnemyLKP(); |
|
|
|
float tolerance = GetPath()->GetGoalTolerance(); |
|
float outerTolerance = GetOuter()->GetDefaultNavGoalTolerance(); |
|
if ( outerTolerance > tolerance ) |
|
{ |
|
GetPath()->SetGoalTolerance( outerTolerance ); |
|
tolerance = outerTolerance; |
|
} |
|
|
|
TranslateNavGoal( pEnemy, newPos ); |
|
|
|
// NOTE: Calling reset here because this can get called |
|
// any time we have to update our goal position |
|
GetPath()->ResetGoalPosition( newPos ); |
|
GetPath()->SetGoalTolerance( tolerance ); |
|
|
|
returnCode = DoFindPathToPos(); |
|
} |
|
} |
|
break; |
|
|
|
case GOALTYPE_LOCATION: |
|
case GOALTYPE_FLANK: |
|
case GOALTYPE_COVER: |
|
returnCode = DoFindPathToPos(); |
|
break; |
|
|
|
case GOALTYPE_LOCATION_NEAREST_NODE: |
|
{ |
|
int myNodeID; |
|
int destNodeID; |
|
|
|
returnCode = false; |
|
|
|
myNodeID = GetPathfinder()->NearestNodeToNPC(); |
|
if (myNodeID != NO_NODE) |
|
{ |
|
destNodeID = GetPathfinder()->NearestNodeToPoint( GetPath()->ActualGoalPosition() ); |
|
if (destNodeID != NO_NODE) |
|
{ |
|
AI_Waypoint_t *pRoute = GetPathfinder()->FindBestPath( myNodeID, destNodeID ); |
|
|
|
if ( pRoute != NULL ) |
|
{ |
|
GetPath()->SetWaypoints( pRoute ); |
|
GetPath()->SetLastNodeAsGoal(true); |
|
returnCode = true; |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
|
|
case GOALTYPE_TARGETENT: |
|
{ |
|
// NOTE: This is going to set the goal position, which was *not* |
|
// set in SetGoal for this movement type |
|
CBaseEntity *pTarget = GetPath()->GetTarget(); |
|
|
|
if ( pTarget ) |
|
{ |
|
Assert( pTarget == GetTarget() ); |
|
|
|
// NOTE: Calling reset here because this can get called |
|
// any time we have to update our goal position |
|
|
|
Vector initPos = pTarget->GetAbsOrigin(); |
|
TranslateNavGoal( pTarget, initPos ); |
|
|
|
GetPath()->ResetGoalPosition( initPos ); |
|
returnCode = DoFindPathToPos(); |
|
} |
|
} |
|
break; |
|
} |
|
|
|
return returnCode; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::ClearPath( void ) |
|
{ |
|
OnClearPath(); |
|
|
|
m_timePathRebuildMax = 0; // How long to try rebuilding path before failing task |
|
m_timePathRebuildFail = 0; // Current global time when should fail building path |
|
m_timePathRebuildNext = 0; // Global time to try rebuilding again |
|
m_timePathRebuildDelay = 0; // How long to wait before trying to rebuild again |
|
|
|
Forget( bits_MEMORY_PATH_FAILED ); |
|
|
|
AI_Waypoint_t *pWaypoint = GetPath()->GetCurWaypoint(); |
|
|
|
if ( pWaypoint ) |
|
{ |
|
SaveStoppingPath(); |
|
m_PreviousMoveActivity = GetMovementActivity(); |
|
m_PreviousArrivalActivity = GetArrivalActivity(); |
|
|
|
if( m_pClippedWaypoints && m_pClippedWaypoints->GetFirst() ) |
|
{ |
|
Assert( m_PreviousMoveActivity > ACT_RESET ); |
|
} |
|
|
|
while ( pWaypoint ) |
|
{ |
|
if ( pWaypoint->iNodeID != NO_NODE ) |
|
{ |
|
CAI_Node *pNode = GetNetwork()->GetNode(pWaypoint->iNodeID); |
|
|
|
if ( pNode ) |
|
{ |
|
if ( pNode->IsLocked() ) |
|
pNode->Unlock(); |
|
} |
|
} |
|
pWaypoint = pWaypoint->GetNext(); |
|
} |
|
} |
|
|
|
GetPath()->Clear(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::GetStoppingPath( CAI_WaypointList * pClippedWaypoints ) |
|
{ |
|
pClippedWaypoints->RemoveAll(); |
|
|
|
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); |
|
if ( pCurWaypoint ) |
|
{ |
|
bool bMustCompleteCurrent = ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP ); |
|
float distRemaining = GetMotor()->MinStoppingDist( 0 ); |
|
|
|
if ( bMustCompleteCurrent ) |
|
{ |
|
float distToCurrent = ComputePathDistance( pCurWaypoint->NavType(), GetLocalOrigin(), pCurWaypoint->GetPos() ); |
|
if ( pCurWaypoint->NavType() == NAV_CLIMB ) |
|
{ |
|
if ( pCurWaypoint->GetNext() && pCurWaypoint->GetNext()->NavType() == NAV_CLIMB ) |
|
distToCurrent += ComputePathDistance( NAV_CLIMB, pCurWaypoint->GetPos(), pCurWaypoint->GetNext()->GetPos() ); |
|
distToCurrent += GetHullWidth() * 2.0; |
|
} |
|
|
|
if ( distToCurrent > distRemaining ) |
|
distRemaining = distToCurrent; |
|
} |
|
|
|
if ( bMustCompleteCurrent || distRemaining > 0.1 ) |
|
{ |
|
Vector vPosPrev = GetLocalOrigin(); |
|
AI_Waypoint_t *pNextPoint = pCurWaypoint; |
|
AI_Waypoint_t *pSavedWaypoints = NULL; |
|
AI_Waypoint_t *pLastSavedWaypoint = NULL; |
|
AI_Waypoint_t *pNewWaypoint; |
|
|
|
while ( distRemaining > 0.01 && pNextPoint ) |
|
{ |
|
if ( ( pNextPoint->NavType() == NAV_CLIMB || pNextPoint->NavType() == NAV_JUMP ) && |
|
!bMustCompleteCurrent ) |
|
{ |
|
break; |
|
} |
|
|
|
#if PARANOID_NAV_CHECK_ON_MOMENTUM |
|
if ( !CanFitAtPosition( pNextPoint->GetPos(), MASK_NPCSOLID ) ) |
|
{ |
|
break; |
|
} |
|
#endif |
|
|
|
if ( pNextPoint->NavType() != NAV_CLIMB || !pNextPoint->GetNext() || pNextPoint->GetNext()->NavType() != NAV_CLIMB ) |
|
bMustCompleteCurrent = false; |
|
|
|
float distToNext = ComputePathDistance( pNextPoint->NavType(), vPosPrev, pNextPoint->GetPos() ); |
|
|
|
if ( distToNext <= distRemaining + 0.01 ) |
|
{ |
|
pNewWaypoint = new AI_Waypoint_t(*pNextPoint); |
|
|
|
if ( pNewWaypoint->Flags() & bits_WP_TO_PATHCORNER ) |
|
{ |
|
pNewWaypoint->ModifyFlags( bits_WP_TO_PATHCORNER, false ); |
|
pNewWaypoint->hPathCorner = NULL; |
|
} |
|
|
|
pNewWaypoint->ModifyFlags( bits_WP_TO_GOAL | bits_WP_TO_NODE, false ); |
|
pNewWaypoint->iNodeID = NO_NODE; |
|
|
|
if ( pLastSavedWaypoint ) |
|
pLastSavedWaypoint->SetNext( pNewWaypoint ); |
|
else |
|
pSavedWaypoints = pNewWaypoint; |
|
pLastSavedWaypoint = pNewWaypoint; |
|
|
|
vPosPrev = pNextPoint->GetPos(); |
|
|
|
// NDebugOverlay::Cross3D( vPosPrev, 16, 255, 255, 0, false, 10.0f ); |
|
|
|
pNextPoint = pNextPoint->GetNext(); |
|
distRemaining -= distToNext; |
|
} |
|
else |
|
{ |
|
Assert( !( pNextPoint->NavType() == NAV_CLIMB || pNextPoint->NavType() == NAV_JUMP ) ); |
|
Vector remainder = pNextPoint->GetPos() - vPosPrev; |
|
VectorNormalize( remainder ); |
|
float yaw = UTIL_VecToYaw( remainder ); |
|
remainder *= distRemaining; |
|
remainder += vPosPrev; |
|
|
|
AIMoveTrace_t trace; |
|
if ( GetMoveProbe()->MoveLimit( pNextPoint->NavType(), vPosPrev, remainder, MASK_NPCSOLID, NULL, 100, AIMLF_DEFAULT | AIMLF_2D, &trace ) ) |
|
{ |
|
pNewWaypoint = new AI_Waypoint_t( trace.vEndPosition, yaw, pNextPoint->NavType(), bits_WP_TO_GOAL, 0); |
|
|
|
if ( pLastSavedWaypoint ) |
|
pLastSavedWaypoint->SetNext( pNewWaypoint ); |
|
else |
|
pSavedWaypoints = pNewWaypoint; |
|
} |
|
|
|
distRemaining = 0; |
|
} |
|
|
|
} |
|
|
|
if ( pSavedWaypoints ) |
|
{ |
|
pClippedWaypoints->Set( pSavedWaypoints ); |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CAI_Navigator::IgnoreStoppingPath( void ) |
|
{ |
|
if( m_pClippedWaypoints && m_pClippedWaypoints->GetFirst() ) |
|
{ |
|
AI_Waypoint_t *pWaypoint = m_pClippedWaypoints->GetFirst(); |
|
|
|
if( pWaypoint->NavType() != NAV_JUMP && pWaypoint->NavType() != NAV_CLIMB ) |
|
{ |
|
m_pClippedWaypoints->RemoveAll(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
ConVar ai_use_clipped_paths( "ai_use_clipped_paths", "1" ); |
|
|
|
void CAI_Navigator::SaveStoppingPath( void ) |
|
{ |
|
m_flTimeClipped = -1; |
|
|
|
m_pClippedWaypoints->RemoveAll(); |
|
AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); |
|
if ( pCurWaypoint ) |
|
{ |
|
if ( ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP ) || ai_use_clipped_paths.GetBool() ) |
|
{ |
|
if ( GetStoppingPath( m_pClippedWaypoints ) ) |
|
m_flTimeClipped = gpGlobals->curtime; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_Navigator::SetGoalFromStoppingPath() |
|
{ |
|
if ( m_pClippedWaypoints && m_pClippedWaypoints->IsEmpty() ) |
|
SaveStoppingPath(); |
|
if ( m_pClippedWaypoints && !m_pClippedWaypoints->IsEmpty() ) |
|
{ |
|
if ( m_PreviousMoveActivity <= ACT_RESET && GetMovementActivity() <= ACT_RESET ) |
|
{ |
|
m_pClippedWaypoints->RemoveAll(); |
|
DevWarning( 2, "%s has a stopping path and no valid. Movement activity: %s (prev %s)\n", GetOuter()->GetDebugName(), ActivityList_NameForIndex(GetMovementActivity()), ActivityList_NameForIndex(m_PreviousMoveActivity) ); |
|
return false; |
|
} |
|
|
|
if ( ( m_pClippedWaypoints->GetFirst()->NavType() == NAV_CLIMB || m_pClippedWaypoints->GetFirst()->NavType() == NAV_JUMP ) ) |
|
{ |
|
const Task_t *pCurTask = GetOuter()->GetTask(); |
|
if ( pCurTask && pCurTask->iTask == TASK_STOP_MOVING ) |
|
{ |
|
// Clipped paths are used for 2 reasons: Prepending movement that must be finished in the case of climbing/jumping, |
|
// and bringing an NPC to a stop. In the second case, we should never be starting climb or jump movement. |
|
m_pClippedWaypoints->RemoveAll(); |
|
return false; |
|
} |
|
} |
|
|
|
if ( !GetPath()->IsEmpty() ) |
|
GetPath()->ClearWaypoints(); |
|
GetPath()->SetWaypoints( m_pClippedWaypoints->GetFirst(), true ); |
|
m_pClippedWaypoints->Set( NULL ); |
|
GetPath()->SetGoalType( GOALTYPE_NONE ); |
|
GetPath()->SetGoalType( GOALTYPE_LOCATION ); |
|
GetPath()->SetGoalTolerance( NAV_STOP_MOVING_TOLERANCE ); |
|
Assert( GetPath()->GetCurWaypoint() ); |
|
|
|
Assert( m_PreviousMoveActivity != ACT_INVALID ); |
|
|
|
|
|
if ( m_PreviousMoveActivity != ACT_RESET ) |
|
GetPath()->SetMovementActivity( m_PreviousMoveActivity ); |
|
if ( m_PreviousArrivalActivity != ACT_RESET ) |
|
GetPath()->SetArrivalActivity( m_PreviousArrivalActivity ); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
static Vector GetRouteColor(Navigation_t navType, int waypointFlags) |
|
{ |
|
if (navType == NAV_JUMP) |
|
{ |
|
return Vector(255,0,0); |
|
} |
|
|
|
if (waypointFlags & bits_WP_TO_GOAL) |
|
{ |
|
return Vector(200,0,255); |
|
} |
|
|
|
if (waypointFlags & bits_WP_TO_DETOUR) |
|
{ |
|
return Vector(0,200,255); |
|
} |
|
|
|
if (waypointFlags & bits_WP_TO_NODE) |
|
{ |
|
return Vector(0,0,255); |
|
} |
|
|
|
else //if (waypointBits & bits_WP_TO_PATHCORNER) |
|
{ |
|
return Vector(0,255,150); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a color for debugging purposes |
|
//----------------------------------------------------------------------------- |
|
static Vector GetWaypointColor(Navigation_t navType) |
|
{ |
|
switch( navType ) |
|
{ |
|
case NAV_JUMP: |
|
return Vector(255,90,90); |
|
|
|
case NAV_GROUND: |
|
return Vector(0,255,255); |
|
|
|
case NAV_CLIMB: |
|
return Vector(90,255,255); |
|
|
|
case NAV_FLY: |
|
return Vector(90,90,255); |
|
|
|
default: |
|
return Vector(255,0,0); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_Navigator::DrawDebugRouteOverlay(void) |
|
{ |
|
AI_Waypoint_t *waypoint = GetPath()->GetCurWaypoint(); |
|
|
|
if (waypoint) |
|
{ |
|
Vector RGB = GetRouteColor(waypoint->NavType(), waypoint->Flags()); |
|
NDebugOverlay::Line(GetLocalOrigin(), waypoint->GetPos(), RGB[0],RGB[1],RGB[2], true,0); |
|
} |
|
|
|
while (waypoint) |
|
{ |
|
Vector RGB = GetWaypointColor(waypoint->NavType()); |
|
NDebugOverlay::Box(waypoint->GetPos(), Vector(-3,-3,-3),Vector(3,3,3), RGB[0],RGB[1],RGB[2], true,0); |
|
|
|
if (waypoint->GetNext()) |
|
{ |
|
Vector RGB = GetRouteColor(waypoint->GetNext()->NavType(), waypoint->GetNext()->Flags()); |
|
NDebugOverlay::Line(waypoint->GetPos(), waypoint->GetNext()->GetPos(),RGB[0],RGB[1],RGB[2], true,0); |
|
} |
|
waypoint = waypoint->GetNext(); |
|
} |
|
|
|
if ( GetPath()->GoalType() != GOALTYPE_NONE ) |
|
{ |
|
Vector vecGoalPos = GetPath()->ActualGoalPosition(); |
|
Vector vecGoalDir = GetPath()->GetGoalDirection( GetOuter()->GetAbsOrigin() ); |
|
NDebugOverlay::Line( vecGoalPos, vecGoalPos + vecGoalDir * 32, 0,0,255, true, 2.0 ); |
|
|
|
float flYaw = UTIL_VecToYaw( vecGoalDir ); |
|
NDebugOverlay::Text( vecGoalPos, CFmtStr("yaw: %f", flYaw), true, 1 ); |
|
} |
|
} |
|
|
|
//-----------------------------------------------------------------------------
|
|
|