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.
376 lines
12 KiB
376 lines
12 KiB
// NextBotChasePath.h |
|
// Maintain and follow a "chase path" to a selected Actor |
|
// Author: Michael Booth, September 2006 |
|
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
|
|
#ifndef _NEXT_BOT_CHASE_PATH_ |
|
#define _NEXT_BOT_CHASE_PATH_ |
|
|
|
#include "nav.h" |
|
#include "NextBotInterface.h" |
|
#include "NextBotLocomotionInterface.h" |
|
#include "NextBotChasePath.h" |
|
#include "NextBotUtil.h" |
|
#include "NextBotPathFollow.h" |
|
#include "tier0/vprof.h" |
|
|
|
|
|
//---------------------------------------------------------------------------------------------- |
|
/** |
|
* A ChasePath extends a PathFollower to periodically recompute a path to a chase |
|
* subject, and to move along the path towards that subject. |
|
*/ |
|
class ChasePath : public PathFollower |
|
{ |
|
public: |
|
enum SubjectChaseType |
|
{ |
|
LEAD_SUBJECT, |
|
DONT_LEAD_SUBJECT |
|
}; |
|
ChasePath( SubjectChaseType chaseHow = DONT_LEAD_SUBJECT ); |
|
|
|
virtual ~ChasePath() { } |
|
|
|
virtual void Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos = NULL ); // update path to chase target and move bot along path |
|
|
|
virtual float GetLeadRadius( void ) const; // range where movement leading begins - beyond this just head right for the subject |
|
virtual float GetMaxPathLength( void ) const; // return maximum path length |
|
virtual Vector PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const; // try to cutoff our chase subject, knowing our relative positions and velocities |
|
virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const; // return true if situation has changed enough to warrant recomputing the current path |
|
|
|
virtual float GetLifetime( void ) const; // Return duration this path is valid. Path will become invalid at its earliest opportunity once this duration elapses. Zero = infinite lifetime |
|
|
|
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid |
|
|
|
private: |
|
void RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos ); |
|
|
|
CountdownTimer m_failTimer; // throttle re-pathing if last path attempt failed |
|
CountdownTimer m_throttleTimer; // require a minimum time between re-paths |
|
CountdownTimer m_lifetimeTimer; |
|
EHANDLE m_lastPathSubject; // the subject used to compute the current/last path |
|
SubjectChaseType m_chaseHow; |
|
}; |
|
|
|
inline ChasePath::ChasePath( SubjectChaseType chaseHow ) |
|
{ |
|
m_failTimer.Invalidate(); |
|
m_throttleTimer.Invalidate(); |
|
m_lifetimeTimer.Invalidate(); |
|
m_lastPathSubject = NULL; |
|
m_chaseHow = chaseHow; |
|
} |
|
|
|
inline float ChasePath::GetLeadRadius( void ) const |
|
{ |
|
return 500.0f; // 1000.0f; |
|
} |
|
|
|
inline float ChasePath::GetMaxPathLength( void ) const |
|
{ |
|
// no limit |
|
return 0.0f; |
|
} |
|
|
|
inline float ChasePath::GetLifetime( void ) const |
|
{ |
|
// infinite duration |
|
return 0.0f; |
|
} |
|
|
|
inline void ChasePath::Invalidate( void ) |
|
{ |
|
// path is gone, repath at earliest opportunity |
|
m_throttleTimer.Invalidate(); |
|
m_lifetimeTimer.Invalidate(); |
|
|
|
// extend |
|
PathFollower::Invalidate(); |
|
} |
|
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------- |
|
/** |
|
* Maintain a path to our chase subject and move along that path |
|
*/ |
|
inline void ChasePath::Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos ) |
|
{ |
|
VPROF_BUDGET( "ChasePath::Update", "NextBot" ); |
|
|
|
// maintain the path to the subject |
|
RefreshPath( bot, subject, cost, pPredictedSubjectPos ); |
|
|
|
// move along the path towards the subject |
|
PathFollower::Update( bot ); |
|
} |
|
|
|
|
|
//---------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if situation has changed enough to warrant recomputing the current path |
|
*/ |
|
inline bool ChasePath::IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const |
|
{ |
|
// the closer we get, the more accurate our path needs to be |
|
Vector to = subject->GetAbsOrigin() - bot->GetPosition(); |
|
|
|
const float minTolerance = 0.0f; // 25.0f; |
|
const float toleranceRate = 0.33f; // 1.0f; // 0.15f; |
|
|
|
float tolerance = minTolerance + toleranceRate * to.Length(); |
|
|
|
return ( subject->GetAbsOrigin() - GetEndPosition() ).IsLengthGreaterThan( tolerance ); |
|
} |
|
|
|
|
|
//---------------------------------------------------------------------------------------------- |
|
/** |
|
* Periodically rebuild the path to our victim |
|
*/ |
|
inline void ChasePath::RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos ) |
|
{ |
|
VPROF_BUDGET( "ChasePath::RefreshPath", "NextBot" ); |
|
|
|
ILocomotion *mover = bot->GetLocomotionInterface(); |
|
|
|
// don't change our path if we're on a ladder |
|
if ( IsValid() && mover->IsUsingLadder() ) |
|
{ |
|
if ( bot->IsDebugging( NEXTBOT_PATH ) ) |
|
{ |
|
DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() ); |
|
} |
|
|
|
// don't allow repath until a moment AFTER we have left the ladder |
|
m_throttleTimer.Start( 1.0f ); |
|
|
|
return; |
|
} |
|
|
|
if ( subject == NULL ) |
|
{ |
|
if ( bot->IsDebugging( NEXTBOT_PATH ) ) |
|
{ |
|
DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No subject.\n", gpGlobals->curtime, bot->GetEntity()->entindex() ); |
|
} |
|
return; |
|
} |
|
|
|
if ( !m_failTimer.IsElapsed() ) |
|
{ |
|
// if ( bot->IsDebugging( NEXTBOT_PATH ) ) |
|
// { |
|
// DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Fail timer not elapsed.\n", gpGlobals->curtime, bot->GetEntity()->entindex() ); |
|
// } |
|
return; |
|
} |
|
|
|
// if our path subject changed, repath immediately |
|
if ( subject != m_lastPathSubject ) |
|
{ |
|
if ( bot->IsDebugging( NEXTBOT_PATH ) ) |
|
{ |
|
DevMsg( "%3.2f: bot(#%d) Chase path subject changed (from %p to %p).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_lastPathSubject.Get(), subject ); |
|
} |
|
|
|
Invalidate(); |
|
|
|
// new subject, fresh attempt |
|
m_failTimer.Invalidate(); |
|
} |
|
|
|
if ( IsValid() && !m_throttleTimer.IsElapsed() ) |
|
{ |
|
// require a minimum time between repaths, as long as we have a path to follow |
|
// if ( bot->IsDebugging( NEXTBOT_PATH ) ) |
|
// { |
|
// DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() ); |
|
// } |
|
return; |
|
} |
|
|
|
if ( IsValid() && m_lifetimeTimer.HasStarted() && m_lifetimeTimer.IsElapsed() ) |
|
{ |
|
// this path's lifetime has elapsed |
|
Invalidate(); |
|
} |
|
|
|
if ( !IsValid() || IsRepathNeeded( bot, subject ) ) |
|
{ |
|
// the situation has changed - try a new path |
|
bool isPath; |
|
Vector pathTarget = subject->GetAbsOrigin(); |
|
|
|
if ( m_chaseHow == LEAD_SUBJECT ) |
|
{ |
|
pathTarget = pPredictedSubjectPos ? *pPredictedSubjectPos : PredictSubjectPosition( bot, subject ); |
|
isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() ); |
|
} |
|
else if ( subject->MyCombatCharacterPointer() && subject->MyCombatCharacterPointer()->GetLastKnownArea() ) |
|
{ |
|
isPath = Compute( bot, subject->MyCombatCharacterPointer(), cost, GetMaxPathLength() ); |
|
} |
|
else |
|
{ |
|
isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() ); |
|
} |
|
|
|
if ( isPath ) |
|
{ |
|
if ( bot->IsDebugging( NEXTBOT_PATH ) ) |
|
{ |
|
//const float size = 20.0f; |
|
//NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, RandomInt( 0, 200 ), 255, 255, true, 30.0f ); |
|
|
|
DevMsg( "%3.2f: bot(#%d) REPATH\n", gpGlobals->curtime, bot->GetEntity()->entindex() ); |
|
} |
|
|
|
m_lastPathSubject = subject; |
|
|
|
const float minRepathInterval = 0.5f; |
|
m_throttleTimer.Start( minRepathInterval ); |
|
|
|
// track the lifetime of this new path |
|
float lifetime = GetLifetime(); |
|
if ( lifetime > 0.0f ) |
|
{ |
|
m_lifetimeTimer.Start( lifetime ); |
|
} |
|
else |
|
{ |
|
m_lifetimeTimer.Invalidate(); |
|
} |
|
} |
|
else |
|
{ |
|
// can't reach subject - throttle retry based on range to subject |
|
m_failTimer.Start( 0.005f * ( bot->GetRangeTo( subject ) ) ); |
|
|
|
// allow bot to react to path failure |
|
bot->OnMoveToFailure( this, FAIL_NO_PATH_EXISTS ); |
|
|
|
if ( bot->IsDebugging( NEXTBOT_PATH ) ) |
|
{ |
|
//const float size = 20.0f; |
|
const float dT = 90.0f; |
|
int c = RandomInt( 0, 100 ); |
|
//NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, c, c, 255, true, dT ); |
|
NDebugOverlay::HorzArrow( bot->GetPosition(), pathTarget, 5.0f, 255, c, c, 255, true, dT ); |
|
|
|
DevMsg( "%3.2f: bot(#%d) REPATH FAILED\n", gpGlobals->curtime, bot->GetEntity()->entindex() ); |
|
} |
|
|
|
Invalidate(); |
|
} |
|
} |
|
} |
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------------------------------- |
|
//---------------------------------------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Directly beeline toward victim if we have a clear shot, otherwise pathfind. |
|
*/ |
|
class DirectChasePath : public ChasePath |
|
{ |
|
public: |
|
|
|
DirectChasePath( ChasePath::SubjectChaseType chaseHow = ChasePath::DONT_LEAD_SUBJECT ) : ChasePath( chaseHow ) |
|
{ |
|
|
|
} |
|
|
|
//------------------------------------------------------------------------------------------------------- |
|
virtual void Update( INextBot *me, CBaseEntity *victim, const IPathCost &pathCost, Vector *pPredictedSubjectPos = NULL ) // update path to chase target and move bot along path |
|
{ |
|
Assert( !pPredictedSubjectPos ); |
|
bool bComputedPredictedPosition; |
|
Vector vecPredictedPosition; |
|
if ( !DirectChase( &bComputedPredictedPosition, &vecPredictedPosition, me, victim ) ) |
|
{ |
|
// path around obstacles to reach our victim |
|
ChasePath::Update( me, victim, pathCost, bComputedPredictedPosition ? &vecPredictedPosition : NULL ); |
|
} |
|
NotifyVictim( me, victim ); |
|
} |
|
|
|
//------------------------------------------------------------------------------------------------------- |
|
bool DirectChase( bool *pPredictedPositionComputed, Vector *pPredictedPos, INextBot *me, CBaseEntity *victim ) // if there is nothing between us and our victim, run directly at them |
|
{ |
|
*pPredictedPositionComputed = false; |
|
|
|
ILocomotion *mover = me->GetLocomotionInterface(); |
|
|
|
if ( me->IsImmobile() || mover->IsScrambling() ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( IsDiscontinuityAhead( me, CLIMB_UP ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( IsDiscontinuityAhead( me, JUMP_OVER_GAP ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
Vector leadVictimPos = PredictSubjectPosition( me, victim ); |
|
|
|
// Don't want to have to compute the predicted position twice. |
|
*pPredictedPositionComputed = true; |
|
*pPredictedPos = leadVictimPos; |
|
|
|
if ( !mover->IsPotentiallyTraversable( mover->GetFeet(), leadVictimPos ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
// the way is clear - move directly towards our victim |
|
mover->FaceTowards( leadVictimPos ); |
|
mover->Approach( leadVictimPos ); |
|
|
|
me->GetBodyInterface()->AimHeadTowards( victim ); |
|
|
|
// old path is no longer useful since we've moved off of it |
|
Invalidate(); |
|
|
|
return true; |
|
} |
|
|
|
//------------------------------------------------------------------------------------------------------- |
|
virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const // return true if situation has changed enough to warrant recomputing the current path |
|
{ |
|
if ( ChasePath::IsRepathNeeded( bot, subject ) ) |
|
{ |
|
return true; |
|
} |
|
|
|
return bot->GetLocomotionInterface()->IsStuck() && bot->GetLocomotionInterface()->GetStuckDuration() > 2.0f; |
|
} |
|
|
|
//------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Determine exactly where the path goes between the given two areas |
|
* on the path. Return this point in 'crossPos'. |
|
*/ |
|
virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const |
|
{ |
|
Vector center; |
|
float halfWidth; |
|
from->ComputePortal( to, dir, ¢er, &halfWidth ); |
|
|
|
*crossPos = center; |
|
} |
|
|
|
void NotifyVictim( INextBot *me, CBaseEntity *victim ); |
|
}; |
|
|
|
|
|
|
|
|
|
#endif // _NEXT_BOT_CHASE_PATH_
|
|
|