diff options
Diffstat (limited to 'game/server/NextBot/Path/NextBotChasePath.h')
| -rw-r--r-- | game/server/NextBot/Path/NextBotChasePath.h | 376 |
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, ¢er, &halfWidth ); + + *crossPos = center; + } + + void NotifyVictim( INextBot *me, CBaseEntity *victim ); +}; + + + + +#endif // _NEXT_BOT_CHASE_PATH_ |