summaryrefslogtreecommitdiff
path: root/game/server/NextBot/Path/NextBotChasePath.h
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/NextBot/Path/NextBotChasePath.h')
-rw-r--r--game/server/NextBot/Path/NextBotChasePath.h376
1 files changed, 376 insertions, 0 deletions
diff --git a/game/server/NextBot/Path/NextBotChasePath.h b/game/server/NextBot/Path/NextBotChasePath.h
new file mode 100644
index 0000000..1929648
--- /dev/null
+++ b/game/server/NextBot/Path/NextBotChasePath.h
@@ -0,0 +1,376 @@
+// 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, &center, &halfWidth );
+
+ *crossPos = center;
+ }
+
+ void NotifyVictim( INextBot *me, CBaseEntity *victim );
+};
+
+
+
+
+#endif // _NEXT_BOT_CHASE_PATH_