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.
448 lines
12 KiB
448 lines
12 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
|
|
#include "ai_localnavigator.h" |
|
|
|
#include "ai_basenpc.h" |
|
#include "ai_planesolver.h" |
|
#include "ai_moveprobe.h" |
|
#include "ai_motor.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar ai_debug_directnavprobe("ai_debug_directnavprobe", "0"); |
|
|
|
const float TIME_DELAY_FULL_DIRECT_PROBE[2] = { 0.25, 0.35 }; |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
BEGIN_SIMPLE_DATADESC(CAI_LocalNavigator) |
|
// m_fLastWasClear (not saved) |
|
// m_LastMoveGoal (not saved) |
|
// m_FullDirectTimer (not saved) |
|
// m_pPlaneSolver (not saved) |
|
// m_pMoveProbe (not saved) |
|
END_DATADESC(); |
|
|
|
//------------------------------------- |
|
|
|
CAI_LocalNavigator::CAI_LocalNavigator(CAI_BaseNPC *pOuter) : CAI_Component( pOuter ) |
|
{ |
|
m_pMoveProbe = NULL; |
|
m_pPlaneSolver = new CAI_PlaneSolver( pOuter ); |
|
|
|
m_fLastWasClear = false; |
|
memset( &m_LastMoveGoal, 0, sizeof(m_LastMoveGoal) ); |
|
} |
|
|
|
//------------------------------------- |
|
|
|
CAI_LocalNavigator::~CAI_LocalNavigator() |
|
{ |
|
delete m_pPlaneSolver; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
void CAI_LocalNavigator::Init( IAI_MovementSink *pMovementServices ) |
|
{ |
|
CAI_ProxyMovementSink::Init( pMovementServices ); |
|
m_pMoveProbe = GetOuter()->GetMoveProbe(); // @TODO (toml 03-30-03): this is a "bad" way to grab this pointer. Components should have an explcit "init" phase. |
|
} |
|
|
|
//------------------------------------- |
|
|
|
void CAI_LocalNavigator::ResetMoveCalculations() |
|
{ |
|
m_FullDirectTimer.Force(); |
|
m_pPlaneSolver->Reset(); |
|
} |
|
|
|
//------------------------------------- |
|
|
|
void CAI_LocalNavigator::AddObstacle( const Vector &pos, float radius, AI_MoveSuggType_t type ) |
|
{ |
|
m_pPlaneSolver->AddObstacle( pos, radius, NULL, type ); |
|
} |
|
|
|
//------------------------------------- |
|
|
|
bool CAI_LocalNavigator::HaveObstacles() |
|
{ |
|
return m_pPlaneSolver->HaveObstacles(); |
|
} |
|
|
|
//------------------------------------- |
|
|
|
bool CAI_LocalNavigator::MoveCalcDirect( AILocalMoveGoal_t *pMoveGoal, bool bOnlyCurThink, float *pDistClear, AIMoveResult_t *pResult ) |
|
{ |
|
AI_PROFILE_SCOPE(CAI_LocalNavigator_MoveCalcDirect); |
|
|
|
bool bRetVal = false; |
|
|
|
if ( pMoveGoal->speed ) |
|
{ |
|
CAI_Motor *pMotor = GetOuter()->GetMotor(); |
|
float minCheckDist = pMotor->MinCheckDist(); |
|
float probeDist = m_pPlaneSolver->CalcProbeDist( pMoveGoal->speed ); // having this match steering allows one fewer traces |
|
float checkDist = MAX( minCheckDist, probeDist ); |
|
float checkStepDist = MAX( 16.0, probeDist * 0.5 ); |
|
|
|
if ( pMoveGoal->flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) ) |
|
{ |
|
// clamp checkDist to be no farther than max distance to goal |
|
checkDist = MIN( checkDist, pMoveGoal->maxDist ); |
|
} |
|
|
|
if ( checkDist <= 0.0 ) |
|
{ |
|
*pResult = AIMR_OK; |
|
return true; |
|
} |
|
|
|
float moveThisInterval = pMotor->CalcIntervalMove(); |
|
bool bExpectingArrival = (moveThisInterval >= checkDist); |
|
|
|
if ( !m_FullDirectTimer.Expired() ) |
|
{ |
|
if ( !m_fLastWasClear || |
|
( !VectorsAreEqual(pMoveGoal->target, m_LastMoveGoal.target, 0.1) || |
|
!VectorsAreEqual(pMoveGoal->dir, m_LastMoveGoal.dir, 0.1) ) || |
|
bExpectingArrival ) |
|
{ |
|
m_FullDirectTimer.Force(); |
|
} |
|
} |
|
|
|
if ( bOnlyCurThink ) // Outer code claims to have done a validation (probably a simplify operation) |
|
{ |
|
m_FullDirectTimer.Set( TIME_DELAY_FULL_DIRECT_PROBE[AIStrongOpt()] ); |
|
} |
|
|
|
// First, check the probable move for this cycle |
|
bool bTraceClear = true; |
|
Vector testPos; |
|
|
|
if ( !bExpectingArrival ) |
|
{ |
|
testPos = GetLocalOrigin() + pMoveGoal->dir * moveThisInterval; |
|
bTraceClear = GetMoveProbe()->MoveLimit( pMoveGoal->navType, GetLocalOrigin(), testPos, |
|
MASK_NPCSOLID, pMoveGoal->pMoveTarget, |
|
100.0, |
|
( pMoveGoal->navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT, |
|
&pMoveGoal->directTrace ); |
|
|
|
if ( !bTraceClear ) |
|
{ |
|
// Adjust probe top match expected probe dist (relied on later in process) |
|
pMoveGoal->directTrace.flDistObstructed = (checkDist - moveThisInterval) + pMoveGoal->directTrace.flDistObstructed; |
|
|
|
} |
|
|
|
if ( !IsRetail() && ai_debug_directnavprobe.GetBool() ) |
|
{ |
|
if ( !bTraceClear ) |
|
{ |
|
DevMsg( GetOuter(), "Close obstruction %f\n", checkDist - pMoveGoal->directTrace.flDistObstructed ); |
|
NDebugOverlay::Line( WorldSpaceCenter(), Vector( testPos.x, testPos.y, WorldSpaceCenter().z ), 255, 0, 0, false, 0.1 ); |
|
if ( pMoveGoal->directTrace.pObstruction ) |
|
NDebugOverlay::Line( WorldSpaceCenter(), pMoveGoal->directTrace.pObstruction->WorldSpaceCenter(), 255, 0, 255, false, 0.1 ); |
|
|
|
} |
|
else |
|
{ |
|
NDebugOverlay::Line( WorldSpaceCenter(), Vector( testPos.x, testPos.y, WorldSpaceCenter().z ), 0, 255, 0, false, 0.1 ); |
|
} |
|
} |
|
|
|
pMoveGoal->thinkTrace = pMoveGoal->directTrace; |
|
} |
|
|
|
// Now project out for future obstructions |
|
if ( bTraceClear ) |
|
{ |
|
if ( m_FullDirectTimer.Expired() ) |
|
{ |
|
testPos = GetLocalOrigin() + pMoveGoal->dir * checkDist; |
|
float checkStepPct = (checkStepDist / checkDist) * 100.0; |
|
if ( checkStepPct > 100.0 ) |
|
checkStepPct = 100.0; |
|
|
|
bTraceClear = GetMoveProbe()->MoveLimit( pMoveGoal->navType, GetLocalOrigin(), testPos, |
|
MASK_NPCSOLID, pMoveGoal->pMoveTarget, |
|
checkStepPct, |
|
( pMoveGoal->navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT, |
|
&pMoveGoal->directTrace ); |
|
if ( bExpectingArrival ) |
|
pMoveGoal->thinkTrace = pMoveGoal->directTrace; |
|
|
|
if (ai_debug_directnavprobe.GetBool() ) |
|
{ |
|
if ( !bTraceClear ) |
|
{ |
|
NDebugOverlay::Line( GetOuter()->EyePosition(), Vector( testPos.x, testPos.y, GetOuter()->EyePosition().z ), 255, 0, 0, false, 0.1 ); |
|
DevMsg( GetOuter(), "Obstruction %f\n", checkDist - pMoveGoal->directTrace.flDistObstructed ); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::Line( GetOuter()->EyePosition(), Vector( testPos.x, testPos.y, GetOuter()->EyePosition().z ), 0, 255, 0, false, 0.1 ); |
|
DevMsg( GetOuter(), "No obstruction\n" ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if ( ai_debug_directnavprobe.GetBool() ) |
|
DevMsg( GetOuter(), "No obstruction (Near probe only)\n" ); |
|
} |
|
} |
|
|
|
pMoveGoal->bHasTraced = true; |
|
|
|
float distClear = checkDist - pMoveGoal->directTrace.flDistObstructed; |
|
if (distClear < 0.001) |
|
distClear = 0; |
|
|
|
if ( bTraceClear ) |
|
{ |
|
*pResult = AIMR_OK; |
|
bRetVal = true; |
|
m_fLastWasClear = true; |
|
} |
|
else if ( ( pMoveGoal->flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) ) && |
|
pMoveGoal->maxDist < distClear ) |
|
{ |
|
*pResult = AIMR_OK; |
|
bRetVal = true; |
|
m_fLastWasClear = true; |
|
} |
|
else |
|
{ |
|
*pDistClear = distClear; |
|
m_fLastWasClear = false; |
|
} |
|
} |
|
else |
|
{ |
|
// Should never end up in this function with speed of zero. Probably an activity problem. |
|
*pResult = AIMR_ILLEGAL; |
|
bRetVal = true; |
|
} |
|
|
|
m_LastMoveGoal = *pMoveGoal; |
|
if ( bRetVal && m_FullDirectTimer.Expired() ) |
|
m_FullDirectTimer.Set( TIME_DELAY_FULL_DIRECT_PROBE[AIStrongOpt()] ); |
|
|
|
return bRetVal; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
ConVar ai_no_steer( "ai_no_steer", "0" ); |
|
|
|
bool CAI_LocalNavigator::MoveCalcSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) |
|
{ |
|
if ( (pMoveGoal->flags & AILMG_NO_STEER) ) |
|
return false; |
|
|
|
if ( ai_no_steer.GetBool() ) |
|
return false; |
|
|
|
if ( GetOuter()->IsFlaggedEfficient() ) |
|
return false; |
|
|
|
AI_PROFILE_SCOPE(CAI_Motor_MoveCalcSteer); |
|
Vector moveSolution; |
|
if ( m_pPlaneSolver->Solve( *pMoveGoal, distClear, &moveSolution ) ) |
|
{ |
|
if ( moveSolution != pMoveGoal->dir ) |
|
{ |
|
float dot = moveSolution.AsVector2D().Dot( pMoveGoal->dir.AsVector2D() ); |
|
|
|
const float COS_HALF_30 = 0.966; |
|
if ( dot > COS_HALF_30 ) |
|
{ |
|
float probeDist = m_pPlaneSolver->CalcProbeDist( pMoveGoal->speed ); |
|
if ( pMoveGoal->maxDist < probeDist * 0.33333 && distClear > probeDist * 0.6666) |
|
{ |
|
// A waypoint is coming up, but there's probably time to steer |
|
// away after hitting it |
|
*pResult = AIMR_OK; |
|
return true; |
|
} |
|
} |
|
|
|
pMoveGoal->facing = pMoveGoal->dir = moveSolution; |
|
} |
|
*pResult = AIMR_OK; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
bool CAI_LocalNavigator::MoveCalcStop( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) |
|
{ |
|
if (distClear < pMoveGoal->maxDist) |
|
{ |
|
if ( distClear < 0.1 ) |
|
{ |
|
DebugNoteMovementFailure(); |
|
*pResult = AIMR_ILLEGAL; |
|
} |
|
else |
|
{ |
|
pMoveGoal->maxDist = distClear; |
|
*pResult = AIMR_OK; |
|
} |
|
|
|
return true; |
|
} |
|
*pResult = AIMR_OK; |
|
return true; |
|
} |
|
|
|
//------------------------------------- |
|
|
|
#ifdef DEBUG |
|
#define SetSolveCookie() pMoveGoal->solveCookie = __LINE__; |
|
#else |
|
#define SetSolveCookie() ((void)0) |
|
#endif |
|
|
|
|
|
AIMoveResult_t CAI_LocalNavigator::MoveCalcRaw( AILocalMoveGoal_t *pMoveGoal, bool bOnlyCurThink ) |
|
{ |
|
AI_PROFILE_SCOPE(CAI_Motor_MoveCalc); |
|
|
|
AIMoveResult_t result = AIMR_OK; // Assume success |
|
AIMoveTrace_t directTrace; |
|
float distClear; |
|
|
|
// -------------------------------------------------- |
|
|
|
bool bDirectClear = MoveCalcDirect( pMoveGoal, bOnlyCurThink, &distClear, &result); |
|
if ( OnCalcBaseMove( pMoveGoal, distClear, &result ) ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( result ); |
|
} |
|
|
|
bool bShouldSteer = ( !(pMoveGoal->flags & AILMG_NO_STEER) && ( !bDirectClear || HaveObstacles() ) ); |
|
|
|
if ( bDirectClear && !bShouldSteer ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( result ); |
|
} |
|
|
|
// -------------------------------------------------- |
|
|
|
if ( bShouldSteer ) |
|
{ |
|
if ( !bDirectClear ) |
|
{ |
|
if ( OnObstructionPreSteer( pMoveGoal, distClear, &result ) ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( result ); |
|
} |
|
} |
|
|
|
if ( MoveCalcSteer( pMoveGoal, distClear, &result ) ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( result ); |
|
} |
|
} |
|
|
|
if ( OnFailedSteer( pMoveGoal, distClear, &result ) ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( result ); |
|
} |
|
|
|
// -------------------------------------------------- |
|
|
|
if ( OnFailedLocalNavigation( pMoveGoal, distClear, &result ) ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( result ); |
|
} |
|
|
|
if ( distClear < GetOuter()->GetMotor()->MinStoppingDist() ) |
|
{ |
|
if ( OnInsufficientStopDist( pMoveGoal, distClear, &result ) ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( result ); |
|
} |
|
|
|
if ( MoveCalcStop( pMoveGoal, distClear, &result) ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( result ); |
|
} |
|
} |
|
|
|
// A hopeful result... may get in trouble at next waypoint and obstruction is still there |
|
if ( distClear > pMoveGoal->curExpectedDist ) |
|
{ |
|
SetSolveCookie(); |
|
return DbgResult( AIMR_OK ); |
|
} |
|
|
|
// -------------------------------------------------- |
|
|
|
DebugNoteMovementFailure(); |
|
SetSolveCookie(); |
|
return DbgResult( IsMoveBlocked( pMoveGoal->directTrace.fStatus ) ? pMoveGoal->directTrace.fStatus : AIMR_ILLEGAL ); |
|
} |
|
|
|
//------------------------------------- |
|
|
|
AIMoveResult_t CAI_LocalNavigator::MoveCalc( AILocalMoveGoal_t *pMoveGoal, bool bPreviouslyValidated ) |
|
{ |
|
bool bOnlyCurThink = ( bPreviouslyValidated && !HaveObstacles() ); |
|
|
|
AIMoveResult_t result = MoveCalcRaw( pMoveGoal, bOnlyCurThink ); |
|
|
|
if ( pMoveGoal->curExpectedDist > pMoveGoal->maxDist ) |
|
pMoveGoal->curExpectedDist = pMoveGoal->maxDist; |
|
|
|
// If success, try to dampen really fast turning movement |
|
if ( result == AIMR_OK) |
|
{ |
|
float interval = GetOuter()->GetMotor()->GetMoveInterval(); |
|
float currentYaw = UTIL_AngleMod( GetLocalAngles().y ); |
|
float goalYaw; |
|
float deltaYaw; |
|
float speed; |
|
float clampedYaw; |
|
|
|
// Clamp yaw |
|
goalYaw = UTIL_VecToYaw( pMoveGoal->facing ); |
|
deltaYaw = fabs( UTIL_AngleDiff( goalYaw, currentYaw ) ); |
|
if ( deltaYaw > 15 ) |
|
{ |
|
speed = deltaYaw * 4.0; // i.e., any maneuver takes a quarter a second |
|
clampedYaw = AI_ClampYaw( speed, currentYaw, goalYaw, interval ); |
|
|
|
if ( clampedYaw != goalYaw ) |
|
{ |
|
pMoveGoal->facing = UTIL_YawToVector( clampedYaw ); |
|
} |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
//-----------------------------------------------------------------------------
|
|
|