diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/ai_behavior_follow.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/ai_behavior_follow.cpp')
| -rw-r--r-- | mp/src/game/server/ai_behavior_follow.cpp | 3123 |
1 files changed, 3123 insertions, 0 deletions
diff --git a/mp/src/game/server/ai_behavior_follow.cpp b/mp/src/game/server/ai_behavior_follow.cpp new file mode 100644 index 00000000..44e67028 --- /dev/null +++ b/mp/src/game/server/ai_behavior_follow.cpp @@ -0,0 +1,3123 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tier1/utllinkedlist.h"
+#include "bitstring.h"
+#include "utlvector.h"
+#include "ai_navigator.h"
+#include "scripted.h"
+#include "ai_hint.h"
+#include "ai_behavior_follow.h"
+#include "ai_memory.h"
+#include "ai_squad.h"
+#include "ai_tacticalservices.h"
+#include "ndebugoverlay.h"
+#include "ai_senses.h"
+
+#ifdef HL2_EPISODIC
+ #include "info_darknessmode_lightsource.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar ai_debug_follow( "ai_debug_follow", "0" );
+ConVar ai_follow_use_points( "ai_follow_use_points", "1" );
+ConVar ai_follow_use_points_when_moving( "ai_follow_use_points_when_moving", "1" );
+#define FollowMsg(s) if ( !GetOuter() || !ai_debug_follow.GetBool() ) ; else DevMsg( GetOuter(), "Follow: " s )
+
+#define WAIT_HINT_MIN_DIST (16*16) // Was: Square(GetHullWidth())
+
+//-----------------------------------------------------------------------------
+//
+// Purpose: Formation management
+//
+// Right now, this is in a very preliminary sketch state. (toml 03-03-03)
+//-----------------------------------------------------------------------------
+
+struct AI_FollowSlot_t;
+struct AI_FollowFormation_t;
+struct AI_FollowGroup_t;
+
+struct AI_Follower_t
+{
+ AI_Follower_t()
+ {
+ slot = -1;
+ memset( &navInfo, 0, sizeof(navInfo) );
+ pGroup = NULL;
+ }
+
+ AIHANDLE hFollower;
+ int slot;
+ AI_FollowNavInfo_t navInfo;
+ AI_FollowGroup_t * pGroup; // backpointer for efficiency
+};
+
+struct AI_FollowGroup_t
+{
+ AI_FollowFormation_t * pFormation;
+ EHANDLE hFollowTarget;
+ CUtlFixedLinkedList<AI_Follower_t> followers;
+ CVarBitVec slotUsage;
+};
+
+
+//-------------------------------------
+
+class CAI_FollowManager
+{
+public:
+ ~CAI_FollowManager()
+ {
+ for ( int i = 0; i < m_groups.Count(); i++ )
+ delete m_groups[i];
+ }
+
+ bool AddFollower( CBaseEntity *pTarget, CAI_BaseNPC *pFollower, AI_Formations_t formation, AI_FollowManagerInfoHandle_t *pHandle );
+ void ChangeFormation( AI_FollowManagerInfoHandle_t &handle, AI_Formations_t formation );
+ void RemoveFollower( AI_FollowManagerInfoHandle_t &handle );
+ bool CalcFollowPosition( AI_FollowManagerInfoHandle_t &handle, AI_FollowNavInfo_t *pNavInfo );
+
+ int CountFollowersInGroup( CAI_BaseNPC *pMember )
+ {
+ AI_FollowGroup_t *pGroup = FindFollowerGroup( pMember );
+
+ if( !pGroup )
+ {
+ return 0;
+ }
+
+ return pGroup->followers.Count();
+ }
+
+ int CountFollowers( CBaseEntity *pFollowTarget, string_t iszClassname )
+ {
+ AI_FollowGroup_t *pGroup = FindGroup( pFollowTarget );
+
+ if( !pGroup )
+ {
+ return 0;
+ }
+
+ if ( iszClassname == NULL_STRING )
+ {
+ return pGroup->followers.Count();
+ }
+ else
+ {
+ int result = 0;
+ for ( int i = pGroup->followers.Head(); i != pGroup->followers.InvalidIndex(); i = pGroup->followers.Next( i ) )
+ {
+ if ( pGroup->followers[i].hFollower && pGroup->followers[i].hFollower->ClassMatches( iszClassname ) )
+ {
+ result++;
+ }
+ }
+ return result;
+ }
+ }
+
+ int GetFollowerSlot( CAI_BaseNPC *pFollower )
+ {
+ AI_FollowGroup_t *pGroup = FindFollowerGroup( pFollower );
+
+ if( !pGroup )
+ {
+ return 0;
+ }
+
+ int h = pGroup->followers.Head();
+
+ while( h != pGroup->followers.InvalidIndex() )
+ {
+ AI_Follower_t *it = &pGroup->followers[h];
+ if ( it->hFollower.Get() == pFollower )
+ {
+ return it->slot;
+ }
+
+ h = pGroup->followers.Next( h );
+ }
+
+ return 0;
+ }
+
+private:
+ bool RedistributeSlots( AI_FollowGroup_t *pGroup );
+ int FindBestSlot( AI_FollowGroup_t *pGroup );
+ void CalculateFieldsFromSlot( AI_FollowSlot_t *pSlot, AI_FollowNavInfo_t *pFollowerInfo );
+
+ AI_FollowGroup_t *FindCreateGroup( CBaseEntity *pTarget, AI_Formations_t formation );
+ AI_FollowGroup_t *FindGroup( CBaseEntity *pTarget );
+ AI_FollowGroup_t *FindFollowerGroup( CBaseEntity *pFollower );
+ void RemoveGroup( AI_FollowGroup_t * );
+
+ //---------------------------------
+
+ CUtlVector<AI_FollowGroup_t *> m_groups;
+};
+
+//-------------------------------------
+
+CAI_FollowManager g_AIFollowManager;
+
+//-----------------------------------------------------------------------------
+
+int AIGetNumFollowers( CBaseEntity *pEntity, string_t iszClassname )
+{
+ return g_AIFollowManager.CountFollowers( pEntity, iszClassname );
+}
+
+//-----------------------------------------------------------------------------
+//
+// CAI_FollowBehavior
+//
+//-----------------------------------------------------------------------------
+
+BEGIN_SIMPLE_DATADESC( AI_FollowNavInfo_t )
+ DEFINE_FIELD( flags, FIELD_INTEGER ),
+ DEFINE_FIELD( position, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( range, FIELD_FLOAT ),
+ DEFINE_FIELD( Zrange, FIELD_FLOAT ),
+ DEFINE_FIELD( tolerance, FIELD_FLOAT ),
+ DEFINE_FIELD( followPointTolerance, FIELD_FLOAT ),
+ DEFINE_FIELD( targetMoveTolerance, FIELD_FLOAT ),
+ DEFINE_FIELD( repathOnRouteTolerance, FIELD_FLOAT ),
+ DEFINE_FIELD( walkTolerance, FIELD_FLOAT ),
+ DEFINE_FIELD( coverTolerance, FIELD_FLOAT ),
+ DEFINE_FIELD( enemyLOSTolerance, FIELD_FLOAT ),
+ DEFINE_FIELD( chaseEnemyTolerance, FIELD_FLOAT ),
+END_DATADESC();
+
+BEGIN_SIMPLE_DATADESC( AI_FollowParams_t )
+ DEFINE_FIELD( formation, FIELD_INTEGER ),
+ DEFINE_FIELD( bNormalMemoryDiscard, FIELD_BOOLEAN ),
+
+END_DATADESC();
+
+BEGIN_DATADESC( CAI_FollowBehavior )
+ DEFINE_FIELD( m_hFollowTarget, FIELD_EHANDLE ),
+ DEFINE_EMBEDDED( m_FollowNavGoal ),
+ DEFINE_FIELD( m_flTimeUpdatedFollowPosition, FIELD_TIME ),
+ DEFINE_FIELD( m_bFirstFacing, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flTimeFollowTargetVisible, FIELD_TIME ),
+ DEFINE_EMBEDDED( m_TargetMonitor ),
+ DEFINE_FIELD( m_bTargetUnreachable, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bFollowNavFailed, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bMovingToCover, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flOriginalEnemyDiscardTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_SavedDistTooFar, FIELD_FLOAT ),
+ DEFINE_EMBEDDED( m_FollowDelay ),
+ DEFINE_EMBEDDED( m_RepathOnFollowTimer ),
+ DEFINE_CUSTOM_FIELD( m_CurrentFollowActivity, ActivityDataOps() ),
+ DEFINE_EMBEDDED( m_TimeBlockUseWaitPoint ),
+ DEFINE_EMBEDDED( m_TimeCheckForWaitPoint ),
+ DEFINE_FIELD( m_pInterruptWaitPoint, FIELD_CLASSPTR ),
+ DEFINE_EMBEDDED( m_TimeBeforeSpreadFacing ),
+ DEFINE_EMBEDDED( m_TimeNextSpreadFacing ),
+ // m_hFollowManagerInfo (reset on load)
+ DEFINE_EMBEDDED( m_params ),
+ DEFINE_FIELD( m_hFollowGoalEnt, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_nFailedFollowAttempts, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flTimeFailFollowStarted, FIELD_TIME ),
+ DEFINE_FIELD( m_vFollowMoveAnchor, FIELD_POSITION_VECTOR ),
+END_DATADESC();
+
+//-------------------------------------
+
+CAI_FollowBehavior::CAI_FollowBehavior( const AI_FollowParams_t ¶ms )
+{
+ memset( &m_FollowNavGoal, 0, sizeof( m_FollowNavGoal ) );
+
+ m_FollowDelay.Set( 1.0, 3.0 );
+ m_hFollowManagerInfo.m_pGroup = NULL;
+ m_hFollowManagerInfo.m_hFollower = 0;
+
+ m_TimeBlockUseWaitPoint.Set( 0.5, 1.5 );
+ m_TimeCheckForWaitPoint.Set( 1.0 );
+ m_pInterruptWaitPoint = NULL;
+
+ m_TimeBeforeSpreadFacing.Set( 2.0, 4.0 );
+ m_TimeNextSpreadFacing.Set( 3.0, 12.0 );
+
+ m_params = params;
+
+ NoteSuccessfulFollow();
+}
+
+//-------------------------------------
+
+CAI_FollowBehavior::~CAI_FollowBehavior()
+{
+ Assert( !m_hFollowManagerInfo.m_pGroup );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw any text overlays
+// Input : Previous text offset from the top
+// Output : Current text offset from the top
+//-----------------------------------------------------------------------------
+int CAI_FollowBehavior::DrawDebugTextOverlays( int text_offset )
+{
+ char tempstr[ 512 ];
+ int offset;
+ CBaseEntity * followEnt;
+
+ offset = BaseClass::DrawDebugTextOverlays( text_offset );
+ if ( GetOuter()->m_debugOverlays & OVERLAY_TEXT_BIT )
+ {
+ followEnt = GetFollowTarget();
+ if ( followEnt != NULL )
+ {
+ Q_snprintf( tempstr, sizeof(tempstr), "Follow: (%d) %s (%s)", followEnt->entindex(), followEnt->GetDebugName(), followEnt->GetClassname() );
+ }
+ else
+ {
+ Q_snprintf( tempstr, sizeof(tempstr), "Follow: NULL" );
+ }
+ GetOuter()->EntityText( offset, tempstr, 0 );
+ offset++;
+ }
+
+ return offset;
+}
+
+
+void CAI_FollowBehavior::DrawDebugGeometryOverlays()
+{
+ if ( GetFollowTarget() )
+ {
+ Vector vecFollowPos = GetGoalPosition();
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecFollowPos, 16.0f, 0, 255, 0, 0, true, 0 );
+ }
+}
+
+
+//-------------------------------------
+
+void CAI_FollowBehavior::SetParameters( const AI_FollowParams_t ¶ms )
+{
+ m_params = params;
+
+ if ( m_hFollowManagerInfo.m_pGroup )
+ {
+ g_AIFollowManager.ChangeFormation( m_hFollowManagerInfo, params.formation );
+ m_flTimeUpdatedFollowPosition = 0;
+ }
+}
+
+//-------------------------------------
+
+CBaseEntity * CAI_FollowBehavior::GetFollowTarget()
+{
+ return m_hFollowTarget;
+}
+
+//-------------------------------------
+
+// Returns true if the NPC is actively following a target.
+bool CAI_FollowBehavior::IsActive( void )
+{
+ if ( IsRunning() && GetFollowTarget() )
+ {
+ // Only true if we're running a follow schedule
+ return IsCurScheduleFollowSchedule();
+ }
+
+ return false;
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::SetFollowTarget( CBaseEntity *pLeader, bool fFinishCurSchedule )
+{
+ if ( pLeader == m_hFollowTarget )
+ return;
+
+ if ( !GetOuter()->IsAlive() )
+ {
+ return;
+ }
+
+ m_flTimeUpdatedFollowPosition = 0;
+
+ if ( m_hFollowTarget )
+ {
+ g_AIFollowManager.RemoveFollower( m_hFollowManagerInfo );
+ m_hFollowTarget = NULL;
+ m_hFollowManagerInfo.m_pGroup = NULL;
+ if ( IsRunning() )
+ {
+ if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT )
+ {
+ GetNavigator()->StopMoving(); // Stop him from walking toward the player
+ }
+
+ if ( GetEnemy() != NULL )
+ {
+ GetOuter()->SetIdealState( NPC_STATE_COMBAT );
+ }
+ }
+ }
+
+ if ( pLeader )
+ {
+ if ( g_AIFollowManager.AddFollower( pLeader, GetOuter(), m_params.formation, &m_hFollowManagerInfo ) )
+ {
+ m_hFollowTarget = pLeader;
+ m_bFirstFacing = true;
+ m_flTimeFollowTargetVisible = 0;
+ SetCondition( COND_TARGET_MOVED_FROM_MARK );
+ m_TargetMonitor.ClearMark();
+ NoteSuccessfulFollow();
+ }
+ }
+
+ NotifyChangeBehaviorStatus(fFinishCurSchedule);
+}
+
+//-------------------------------------
+void CAI_FollowBehavior::SetFollowGoalDirect( CAI_FollowGoal *pGoal )
+{
+ m_hFollowGoalEnt = pGoal;
+ m_flTimeUpdatedFollowPosition = 0;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::SetFollowGoal( CAI_FollowGoal *pGoal, bool fFinishCurSchedule )
+{
+ if ( GetOuter()->ShouldAcceptGoal( this, pGoal ) )
+ {
+ GetOuter()->ClearCommandGoal();
+
+ if( hl2_episodic.GetBool() )
+ {
+ // Poke the NPC to interrupt any stubborn schedules
+ GetOuter()->SetCondition(COND_PROVOKED);
+ }
+
+ SetFollowTarget( pGoal->GetGoalEntity() );
+ Assert( pGoal->m_iFormation == AIF_SIMPLE || pGoal->m_iFormation == AIF_WIDE || pGoal->m_iFormation == AIF_MEDIUM || pGoal->m_iFormation == AIF_SIDEKICK || pGoal->m_iFormation == AIF_VORTIGAUNT );
+ SetParameters( AI_FollowParams_t( (AI_Formations_t)pGoal->m_iFormation ) );
+ m_hFollowGoalEnt = pGoal;
+ m_flTimeUpdatedFollowPosition = 0;
+ return true;
+ }
+ return false;
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::ClearFollowGoal( CAI_FollowGoal *pGoal )
+{
+ GetOuter()->OnClearGoal( this, pGoal );
+ if ( pGoal == m_hFollowGoalEnt )
+ {
+ SetFollowTarget( NULL );
+ m_hFollowGoalEnt = NULL;
+ m_flTimeUpdatedFollowPosition = 0;
+ }
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::UpdateFollowPosition()
+{
+ AI_PROFILE_SCOPE( CAI_FollowBehavior_UpdateFollowPosition );
+
+ if ( m_flTimeUpdatedFollowPosition == gpGlobals->curtime )
+ {
+ return true;
+ }
+
+ if (m_hFollowTarget == NULL)
+ return false;
+
+ if ( !g_AIFollowManager.CalcFollowPosition( m_hFollowManagerInfo, &m_FollowNavGoal ) )
+ {
+ return false;
+ }
+
+ CBaseEntity *pFollowTarget = GetFollowTarget();
+
+ if ( pFollowTarget->GetParent() )
+ {
+ if ( pFollowTarget->GetParent()->GetServerVehicle() )
+ {
+ m_FollowNavGoal.targetMoveTolerance *= 1.5;
+ m_FollowNavGoal.range += pFollowTarget->GetParent()->BoundingRadius() * 0.333;
+ }
+ }
+
+#if TODO
+ // @TODO (toml 07-27-03): this is too simplistic. fails when the new point is an inappropriate target
+ CBasePlayer *pPlayer = dynamic_cast<CBasePlayer *>(m_hFollowTarget.Get());
+ Vector targetVelocity = pPlayer->GetSmoothedVelocity();
+ m_FollowNavGoal.position += targetVelocity * 0.5;
+#endif
+
+ m_flTimeUpdatedFollowPosition = gpGlobals->curtime;
+
+ return true;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsMovingToFollowTarget()
+{
+ return ( IsRunning() && ( IsCurSchedule(SCHED_FOLLOW, false) || IsCurSchedule(SCHED_FOLLOWER_GO_TO_WAIT_POINT, false) ) );
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::CanSelectSchedule()
+{
+ if ( !GetOuter()->IsInterruptable() )
+ return false;
+
+ if ( !ShouldFollow() )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::PlayerIsPushing()
+{
+ return (m_hFollowTarget && m_hFollowTarget->IsPlayer() && HasCondition( COND_PLAYER_PUSHING ) );
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsFollowTargetInRange( float rangeMultiplier )
+{
+ if ( !GetFollowTarget()->IsPlayer() && HasCondition( COND_RECEIVED_ORDERS ) )
+ return false;
+
+ if( GetNpcState() == NPC_STATE_COMBAT )
+ {
+ if( IsFollowGoalInRange( MAX( m_FollowNavGoal.coverTolerance, m_FollowNavGoal.enemyLOSTolerance ) * rangeMultiplier, GetGoalZRange(), GetGoalFlags() ) )
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if( IsFollowGoalInRange( MAX( m_FollowNavGoal.tolerance, GetGoalRange() ) * rangeMultiplier, GetGoalZRange(), GetGoalFlags() ) )
+ {
+ if ( m_FollowNavGoal.flags & AIFF_REQUIRE_LOS_OUTSIDE_COMBAT )
+ {
+ //trace_t tr;
+ //AI_TraceLOS( vecStart, vecStart + vecDir * 8192, m_hFollowTarget, &tr );
+ //if ( AI_TraceLOS m_FollowNavGoal.position
+ if ( !HasCondition(COND_SEE_PLAYER) )
+ return false;
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsFollowGoalInRange( float tolerance, float zTolerance, int flags )
+{
+ const Vector &origin = WorldSpaceCenter();
+ const Vector &goal = GetGoalPosition();
+ if ( zTolerance == -1 )
+ zTolerance = GetHullHeight();
+ float distanceSq = ( goal.AsVector2D() - origin.AsVector2D() ).LengthSqr();
+ tolerance += 0.1;
+
+ // Increase Z tolerance slightly as XY distance decreases
+ float flToleranceSq = (tolerance*tolerance);
+ float flIncreaseRange = flToleranceSq * 0.25;
+ zTolerance += zTolerance * clamp((distanceSq / flIncreaseRange), 0.f, 1.f );
+ if ( fabs( origin.z - goal.z ) > zTolerance )
+ return false;
+
+ if ( distanceSq > flToleranceSq )
+ return false;
+
+ if ( flags & AIFF_REQUIRE_LOS_OUTSIDE_COMBAT && m_hFollowTarget.Get() )
+ {
+ if ( !GetOuter()->GetSenses()->DidSeeEntity( m_hFollowTarget ) )
+ return false;
+ }
+
+ return true;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsChaseGoalInRange()
+{
+ if ( GetEnemy() && ( GetEnemy()->WorldSpaceCenter() - m_FollowNavGoal.position ).LengthSqr() > Square( m_FollowNavGoal.chaseEnemyTolerance ) )
+ return false;
+
+ return true;
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::NoteFailedFollow()
+{
+ m_nFailedFollowAttempts++;
+ if ( m_flTimeFailFollowStarted == FLT_MAX )
+ m_flTimeFailFollowStarted = gpGlobals->curtime;
+
+ if ( GetOuter() && ai_debug_follow.GetBool() )
+ DevMsg( GetOuter(), "Follow: NoteFailedFollow() (%d, %f)\n", m_nFailedFollowAttempts, m_flTimeFailFollowStarted );
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::NoteSuccessfulFollow()
+{
+ m_nFailedFollowAttempts = 0;
+ m_flTimeFailFollowStarted = FLT_MAX;
+ FollowMsg( "NoteSuccessfulFollow()\n" );
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::BeginScheduleSelection()
+{
+ if ( GetOuter()->m_hCine )
+ GetOuter()->m_hCine->CancelScript();
+
+ m_TimeBeforeSpreadFacing.Reset();
+
+ SetCondition( COND_TARGET_MOVED_FROM_MARK );
+ m_TargetMonitor.ClearMark();
+ NoteSuccessfulFollow();
+
+ if ( !m_params.bNormalMemoryDiscard )
+ {
+ // Forget about enemies that I haven't seen for >5 seconds
+ m_flOriginalEnemyDiscardTime = GetOuter()->GetEnemies()->GetEnemyDiscardTime();
+ GetOuter()->GetEnemies()->SetEnemyDiscardTime( 5.0f );
+ }
+
+ m_SavedDistTooFar = GetOuter()->m_flDistTooFar;
+ if ( GetFollowTarget() && GetFollowTarget()->IsPlayer() )
+ {
+ GetOuter()->m_flDistTooFar = FLT_MAX;
+ }
+
+ BaseClass::BeginScheduleSelection();
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::EndScheduleSelection()
+{
+ if ( !m_params.bNormalMemoryDiscard )
+ {
+ // Restore our original enemy discard time
+ GetOuter()->GetEnemies()->SetEnemyDiscardTime( m_flOriginalEnemyDiscardTime );
+ }
+
+ if ( m_SavedDistTooFar > 0.1 ) // backward savefile compatability
+ {
+ GetOuter()->m_flDistTooFar = m_SavedDistTooFar;
+ }
+
+ BaseClass::EndScheduleSelection();
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput )
+{
+ if ( m_hFollowManagerInfo.m_pGroup )
+ {
+ g_AIFollowManager.RemoveFollower( m_hFollowManagerInfo );
+ m_hFollowManagerInfo.m_pGroup = NULL;
+ m_hFollowTarget = NULL;
+ }
+ BaseClass::CleanupOnDeath( pCulprit, bFireDeathOutput );
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::Precache()
+{
+ if ( m_hFollowTarget != NULL && m_hFollowManagerInfo.m_pGroup == NULL )
+ {
+ // Post load fixup
+ if ( !g_AIFollowManager.AddFollower( m_hFollowTarget, GetOuter(), m_params.formation, &m_hFollowManagerInfo ) )
+ {
+ m_hFollowTarget = NULL;
+ }
+ }
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ if ( !GetFollowTarget() )
+ {
+ ClearCondition( COND_FOLLOW_PLAYER_IS_LIT );
+ ClearCondition( COND_FOLLOW_PLAYER_IS_NOT_LIT );
+ ClearCondition( COND_FOLLOW_TARGET_VISIBLE );
+ ClearCondition( COND_FOLLOW_TARGET_NOT_VISIBLE );
+ ClearCondition( COND_FOLLOW_DELAY_EXPIRED );
+ ClearCondition( COND_TARGET_MOVED_FROM_MARK );
+ ClearFollowPoint();
+ m_pInterruptWaitPoint = NULL;
+ m_bTargetUnreachable = false;
+ m_flTimeFollowTargetVisible = 0;
+
+ if ( IsRunning() )
+ {
+ GetOuter()->ClearSchedule( "Follow target gone" );
+ }
+ return;
+ }
+
+ if ( !m_TargetMonitor.IsMarkSet() )
+ {
+ FollowMsg( "No mark set\n" );
+ }
+
+ if ( m_FollowDelay.IsRunning() && m_FollowDelay.Expired())
+ {
+ SetCondition( COND_FOLLOW_DELAY_EXPIRED );
+ m_FollowDelay.Stop();
+ }
+
+ if ( m_TargetMonitor.TargetMoved2D( GetFollowTarget() ) )
+ {
+ FollowMsg( "Target moved\n" );
+ m_TargetMonitor.ClearMark();
+ SetCondition( COND_TARGET_MOVED_FROM_MARK );
+ m_bTargetUnreachable = false;
+ }
+
+ if ( !m_TargetMonitor.IsMarkSet() )
+ m_bTargetUnreachable = false;
+
+ m_pInterruptWaitPoint = NULL;
+
+ if ( GetHintNode() == NULL )
+ {
+ if ( ShouldUseFollowPoints() && m_TimeBlockUseWaitPoint.Expired() && m_TimeCheckForWaitPoint.Expired() )
+ {
+ m_TimeCheckForWaitPoint.Reset();
+ m_pInterruptWaitPoint = FindFollowPoint();
+ if ( m_pInterruptWaitPoint )
+ SetCondition( COND_FOUND_WAIT_POINT );
+ }
+ }
+
+ if ( m_flTimeUpdatedFollowPosition == 0 || gpGlobals->curtime - m_flTimeUpdatedFollowPosition > 2.0 )
+ UpdateFollowPosition();
+
+ if ( IsFollowTargetInRange() )
+ {
+ NoteSuccessfulFollow();
+ }
+ else if ( GetOuter()->GetTask() && !IsCurScheduleFollowSchedule() )
+ {
+ if ( !m_FollowDelay.IsRunning() || m_FollowDelay.Expired() )
+ {
+ switch ( GetOuter()->GetTask()->iTask )
+ {
+ case TASK_WAIT_RANDOM:
+ case TASK_WAIT_INDEFINITE:
+ case TASK_WAIT:
+ case TASK_WAIT_FACE_ENEMY:
+ case TASK_WAIT_FACE_ENEMY_RANDOM:
+ {
+ m_TargetMonitor.ClearMark();
+ if ( !HasCondition(COND_FOLLOW_PLAYER_IS_NOT_LIT) )
+ {
+ SetCondition( COND_TARGET_MOVED_FROM_MARK );
+ }
+ }
+ }
+ }
+ }
+
+#if 0
+ else if ( !IsFollowPointInRange() )
+ {
+ GetHintNode()->Unlock();
+ SetHintNode( NULL );
+ }
+#endif
+
+#ifdef HL2_EPISODIC
+ // Let followers know if the player is lit in the darkness
+ if ( GetFollowTarget()->IsPlayer() && HL2GameRules()->IsAlyxInDarknessMode() )
+ {
+ if ( LookerCouldSeeTargetInDarkness( GetOuter(), GetFollowTarget() ) )
+ {
+ SetCondition( COND_FOLLOW_PLAYER_IS_LIT );
+ ClearCondition( COND_FOLLOW_PLAYER_IS_NOT_LIT );
+ }
+ else
+ {
+ SetCondition( COND_FOLLOW_PLAYER_IS_NOT_LIT );
+ ClearCondition( COND_FOLLOW_PLAYER_IS_LIT );
+ }
+ }
+#endif
+
+ // Set our follow target visibility state
+ if ( (GetFollowTarget()->IsPlayer() && HasCondition( COND_SEE_PLAYER )) || GetOuter()->FVisible( GetFollowTarget()) )
+ {
+ SetCondition( COND_FOLLOW_TARGET_VISIBLE );
+ ClearCondition( COND_FOLLOW_TARGET_NOT_VISIBLE );
+ m_flTimeFollowTargetVisible = gpGlobals->curtime;
+ }
+ else
+ {
+ ClearCondition( COND_FOLLOW_TARGET_VISIBLE );
+ SetCondition( COND_FOLLOW_TARGET_NOT_VISIBLE );
+ }
+
+ if ( HasFollowPoint() && ( m_flTimeFollowTargetVisible != 0 && gpGlobals->curtime - m_flTimeFollowTargetVisible > 5.0 ) )
+ SetCondition( COND_FOLLOW_WAIT_POINT_INVALID );
+ else
+ ClearCondition( COND_FOLLOW_WAIT_POINT_INVALID );
+}
+
+//-------------------------------------
+
+int CAI_FollowBehavior::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ if ( failedTask == TASK_MOVE_TO_FOLLOW_POSITION || failedTask == TASK_GET_PATH_TO_FOLLOW_POSITION )
+ {
+ if ( m_hFollowTarget )
+ {
+ m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance * 0.5 );
+ m_FollowDelay.Start();
+ NoteFailedFollow();
+ }
+ }
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::ShouldFollow()
+{
+ if ( !GetFollowTarget() )
+ return false;
+
+ if ( GetFollowTarget()->GetFlags() & FL_NOTARGET )
+ return false;
+
+ // If we recently failed to build a follow path, wait a while to
+ // give other schedules a chance to run.
+ if ( m_bFollowNavFailed && m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() )
+ {
+ return false;
+ }
+
+ m_bFollowNavFailed = false;
+
+ return true;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::ShouldMoveToFollowTarget()
+{
+ if ( GetFollowTarget() == NULL )
+ return false;
+
+ if( m_bTargetUnreachable )
+ return false;
+
+#ifdef HL2_EPISODIC
+ if ( HL2GameRules()->IsAlyxInDarknessMode() )
+ {
+ // If we're in darkness mode, the player needs to be lit by
+ // darkness, but we don't need line of sight to him.
+ if ( HasCondition(COND_FOLLOW_PLAYER_IS_NOT_LIT) )
+ return false;
+ }
+#endif
+
+ if ( HasFollowPoint() )
+ {
+ if ( IsFollowPointInRange() )
+ return false;
+ }
+ else if ( IsFollowTargetInRange() )
+ return false;
+
+ if( m_FollowDelay.IsRunning() && !m_FollowDelay.Expired() && !HasCondition( COND_TARGET_MOVED_FROM_MARK ) )
+ return false;
+
+ return true;
+}
+
+//-------------------------------------
+
+int CAI_FollowBehavior::SelectScheduleManagePosition()
+{
+ if ( PlayerIsPushing() )
+ return SCHED_MOVE_AWAY;
+
+ if ( !UpdateFollowPosition() )
+ return SCHED_FAIL;
+
+ return SCHED_NONE;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::ShouldUseFollowPoints()
+{
+ if ( !ai_follow_use_points.GetBool() || GetEnemy() != NULL )
+ return false;
+
+ return true;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::HasFollowPoint()
+{
+ return ( GetHintNode() && GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT );
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::ClearFollowPoint()
+{
+ if ( GetHintNode() && GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT )
+ {
+ GetHintNode()->Unlock();
+ SetHintNode( NULL );
+ }
+}
+
+//-------------------------------------
+
+const Vector &CAI_FollowBehavior::GetFollowPoint()
+{
+ static Vector invalid = vec3_invalid;
+ if ( GetHintNode() && GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT )
+ return GetHintNode()->GetAbsOrigin();
+ return invalid;
+}
+
+//-------------------------------------
+
+CAI_Hint *CAI_FollowBehavior::FindFollowPoint()
+{
+ if ( !m_TimeBlockUseWaitPoint.Expired() )
+ return NULL;
+
+ CHintCriteria hintCriteria;
+ hintCriteria.SetHintType( HINT_FOLLOW_WAIT_POINT );
+ hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE | bits_HINT_NODE_NEAREST );
+
+ // Add the search position
+ hintCriteria.AddIncludePosition( GetGoalPosition(), MAX( m_FollowNavGoal.followPointTolerance, GetGoalRange() ) );
+ hintCriteria.AddExcludePosition( GetGoalPosition(), (GetFollowTarget()->WorldAlignMins().AsVector2D() - GetFollowTarget()->WorldAlignMaxs().AsVector2D()).Length());
+
+ return CAI_HintManager::FindHint( GetOuter(), hintCriteria );
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsFollowPointInRange()
+{
+ return ( GetHintNode() &&
+ GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT &&
+ (GetHintNode()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() < Square(MAX(m_FollowNavGoal.followPointTolerance, GetGoalRange())) );
+}
+
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::ShouldIgnoreFollowPointFacing()
+{
+ if ( !GetHintNode() )
+ return true;
+
+ HintIgnoreFacing_t hintSetting = GetHintNode()->GetIgnoreFacing();
+
+ if ( hintSetting == HIF_DEFAULT )
+ return ( GetHintNode()->HintActivityName() == NULL_STRING );
+
+ return ( hintSetting == HIF_YES );
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::SetFollowPoint( CAI_Hint *pHintNode )
+{
+ if ( !pHintNode )
+ return;
+
+ Assert( pHintNode->HintType() == HINT_FOLLOW_WAIT_POINT );
+
+ if ( GetHintNode() == pHintNode )
+ return;
+
+ if ( GetHintNode() )
+ GetHintNode()->Unlock();
+
+ if ( !pHintNode->Lock( GetOuter() ) )
+ {
+ SetHintNode( NULL );
+ m_TimeBlockUseWaitPoint.Reset();
+ }
+ else
+ SetHintNode( pHintNode );
+}
+
+//-------------------------------------
+
+int CAI_FollowBehavior::SelectScheduleFollowPoints()
+{
+ bool bShouldUseFollowPoints = ( ShouldUseFollowPoints() && IsFollowGoalInRange( m_FollowNavGoal.followPointTolerance + 0.1, GetGoalZRange(), GetGoalFlags() ) );
+ float distSqToPoint = FLT_MAX;
+ bool bHasFollowPoint = HasFollowPoint();
+
+ if ( bHasFollowPoint )
+ {
+ distSqToPoint = (GetHintNode()->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
+ if ( !bShouldUseFollowPoints ||
+ distSqToPoint > Square(2.0 * GetHullWidth()) ||
+ HasCondition( COND_FOLLOW_WAIT_POINT_INVALID ) )
+ {
+ GetHintNode()->Unlock();
+ SetHintNode( NULL );
+ m_TimeBlockUseWaitPoint.Reset();
+ bShouldUseFollowPoints = false;
+ }
+ }
+
+ if ( bShouldUseFollowPoints )
+ {
+ bool bNewHint = false;
+ if ( GetHintNode() && !bHasFollowPoint )
+ {
+ GetHintNode()->Unlock();
+ SetHintNode( NULL );
+ }
+
+ if (!GetHintNode())
+ {
+ bNewHint = true;
+ SetFollowPoint( ( m_pInterruptWaitPoint ) ? m_pInterruptWaitPoint : FindFollowPoint() );
+
+ if ( GetHintNode() )
+ distSqToPoint = (GetHintNode()->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
+ }
+
+ if ( GetHintNode() )
+ {
+ if ( bNewHint || distSqToPoint > WAIT_HINT_MIN_DIST )
+ return SCHED_FOLLOWER_GO_TO_WAIT_POINT;
+ if ( !ShouldIgnoreFollowPointFacing() )
+ return SCHED_FOLLOWER_STAND_AT_WAIT_POINT;
+ }
+ }
+ else
+ ClearFollowPoint();
+
+ return SCHED_NONE;
+}
+
+//-------------------------------------
+
+int CAI_FollowBehavior::SelectScheduleMoveToFormation()
+{
+ if( ( GetNpcState() != NPC_STATE_COMBAT && !( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ))) ||
+ !IsFollowGoalInRange( GetGoalRange(), GetGoalZRange(), GetGoalFlags() ) )
+ {
+ AISquadIter_t iter;
+ CAI_Squad *pSquad = GetOuter()->GetSquad();
+ if ( pSquad )
+ {
+ for ( CAI_BaseNPC *pSquadMember = pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = pSquad->GetNextMember( &iter ) )
+ {
+ if ( pSquadMember->HasCondition( COND_PLAYER_PUSHING ) )
+ {
+ return SCHED_NONE;
+ }
+ }
+ }
+ if ( ShouldMoveToFollowTarget() || m_bFirstFacing )
+ {
+ return SCHED_TARGET_FACE; // Code for "SCHED_MOVE_TO_FACE_FOLLOW_TARGET". Used by Talker clients to interject comment
+ }
+ }
+ return SCHED_NONE;
+}
+
+//-------------------------------------
+
+int CAI_FollowBehavior::SelectSchedule()
+{
+ // Allow a range attack if we need to do it
+ if ( hl2_episodic.GetBool() )
+ {
+ // Range attack
+ if ( GetOuter()->ShouldMoveAndShoot() == false && HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ return SCHED_RANGE_ATTACK1;
+ }
+
+ if ( GetFollowTarget() )
+ {
+ if ( !GetFollowTarget()->IsAlive() )
+ {
+ // UNDONE: Comment about the recently dead player here?
+ SetFollowTarget( NULL );
+ }
+ else if ( ShouldFollow() )
+ {
+ int result = SCHED_NONE;
+
+ result = SelectScheduleManagePosition();
+ if ( result != SCHED_NONE )
+ return result;
+
+ result = SelectScheduleFollowPoints();
+ if ( result != SCHED_NONE )
+ return result;
+
+ result = SelectScheduleMoveToFormation();
+ if ( result != SCHED_NONE )
+ return result;
+
+ if ( HasCondition ( COND_NO_PRIMARY_AMMO ) && HaveSequenceForActivity( GetOuter()->TranslateActivity( ACT_RUN_AIM ) ) )
+ return SCHED_HIDE_AND_RELOAD;
+ }
+
+ if ( PlayerIsPushing() )
+ return SCHED_MOVE_AWAY;
+ }
+ else
+ {
+ // Should not have landed here. Follow target ent must have been destroyed
+ NotifyChangeBehaviorStatus();
+ }
+
+ if ( HasCondition( COND_TARGET_MOVED_FROM_MARK ) )
+ {
+ m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance * 0.5 );
+ }
+
+ return FollowCallBaseSelectSchedule();
+}
+
+//-------------------------------------
+
+int CAI_FollowBehavior::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_FOLLOWER_IDLE_STAND:
+ // If we have an enemy, at least face them!
+ if ( GetEnemy() )
+ return SCHED_FOLLOWER_COMBAT_FACE;
+
+ break;
+
+ case SCHED_IDLE_STAND:
+ {
+ if ( ShouldMoveToFollowTarget() && !IsFollowGoalInRange( GetGoalRange(), GetGoalZRange(), GetGoalFlags() ) )
+ {
+ return SCHED_MOVE_TO_FACE_FOLLOW_TARGET;
+ }
+ if ( HasFollowPoint() && !ShouldIgnoreFollowPointFacing() )
+ return SCHED_FOLLOWER_GO_TO_WAIT_POINT;
+
+ // If we have an enemy, at least face them!
+ if ( GetEnemy() )
+ return SCHED_FOLLOWER_COMBAT_FACE;
+
+ return SCHED_FOLLOWER_IDLE_STAND;
+ }
+
+ case SCHED_COMBAT_STAND:
+ case SCHED_ALERT_STAND:
+ {
+ if ( ShouldMoveToFollowTarget() && !IsFollowGoalInRange( GetGoalRange(), GetGoalZRange(), GetGoalFlags() ) )
+ {
+ return SCHED_MOVE_TO_FACE_FOLLOW_TARGET;
+ }
+ break;
+ }
+
+ case SCHED_TARGET_FACE:
+ {
+ if ( ( ShouldMoveToFollowTarget() || m_bFirstFacing ) && !IsFollowGoalInRange( GetGoalRange(), GetGoalZRange(), GetGoalFlags() ) )
+ {
+ return SCHED_MOVE_TO_FACE_FOLLOW_TARGET;
+ }
+ if ( HasFollowPoint() && !ShouldIgnoreFollowPointFacing() )
+ return SCHED_FOLLOWER_GO_TO_WAIT_POINT;
+ if ( !m_TargetMonitor.IsMarkSet() )
+ m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
+ return SCHED_FACE_FOLLOW_TARGET; // @TODO (toml 03-03-03): should select a facing sched
+ }
+
+ case SCHED_TARGET_CHASE:
+ {
+ return SCHED_FOLLOW;
+ }
+
+ // SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK just tells the NPC to chase their enemy, so
+ // forbid this unless the destination is acceptable within the parameters of the follow behavior.
+ case SCHED_CHASE_ENEMY:
+ case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
+ {
+ if ( IsChaseGoalInRange() == false )
+ return SCHED_FOLLOWER_IDLE_STAND;
+ break;
+ }
+
+ case SCHED_RANGE_ATTACK1:
+ {
+ if ( GetOuter()->GetShotRegulator()->IsInRestInterval() )
+ {
+ if ( GetEnemy() )
+ return SCHED_FOLLOWER_COMBAT_FACE;
+
+ return SCHED_FOLLOWER_IDLE_STAND; // @TODO (toml 07-02-03): Should do something more tactically sensible
+ }
+ break;
+ }
+
+ case SCHED_CHASE_ENEMY_FAILED:
+ {
+ if (HasMemory(bits_MEMORY_INCOVER))
+ {
+ // Make sure I don't get too far from the player
+ if ( GetFollowTarget() )
+ {
+ float fDist = (GetLocalOrigin() - GetFollowTarget()->GetAbsOrigin()).Length();
+ if (fDist > 500)
+ {
+ return SCHED_FOLLOW;
+ }
+ }
+ }
+ break;
+ }
+
+ case SCHED_MOVE_AWAY_FAIL:
+ {
+ return SCHED_FOLLOWER_MOVE_AWAY_FAIL;
+ }
+ case SCHED_MOVE_AWAY_END:
+ {
+ return SCHED_FOLLOWER_MOVE_AWAY_END;
+ }
+ }
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::OnStartSchedule( int scheduleType )
+{
+ if ( !IsRunning() && HasFollowPoint() )
+ {
+ ClearHintNode( 0.5 );
+ }
+
+ if ( !m_TargetMonitor.IsMarkSet() && !IsCurScheduleFollowSchedule() )
+ {
+ m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
+ }
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::GetFollowTargetViewLoc( Vector *pResult )
+{
+ if ( !dynamic_cast<CPointEntity *>(m_hFollowTarget.Get()) )
+ {
+ trace_t tr;
+ Vector vecStart, vecDir;
+
+ ASSERT( m_hFollowTarget != NULL );
+
+ vecStart = m_hFollowTarget->EyePosition();
+
+ CBasePlayer *pPlayer;
+
+ pPlayer = dynamic_cast<CBasePlayer *>(m_hFollowTarget.Get());
+
+ if( pPlayer )
+ {
+ // Follow target is a player.
+ pPlayer->EyeVectors( &vecDir, NULL, NULL );
+ }
+ else
+ {
+ // Not a player.
+ m_hFollowTarget->GetVectors( &vecDir, NULL, NULL );
+ }
+
+ AI_TraceLOS( vecStart, vecStart + vecDir * 8192, m_hFollowTarget, &tr );
+
+ *pResult = tr.endpos;
+ }
+ else
+ *pResult = m_hFollowTarget->GetAbsOrigin();
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::ValidateFaceTarget( Vector *pFaceTarget )
+{
+ if ( *pFaceTarget == vec3_invalid )
+ {
+ if ( m_hFollowTarget != NULL )
+ {
+ *pFaceTarget = m_hFollowTarget->GetAbsOrigin();
+ }
+ return false;
+ }
+
+ Vector testPoint = *pFaceTarget - GetAbsOrigin();
+ testPoint.z = 0;
+ VectorNormalize( testPoint );
+ testPoint *= 48;
+ testPoint += GetOuter()->EyePosition();
+
+ trace_t tr;
+ AI_TraceLine( GetOuter()->EyePosition(), testPoint, MASK_BLOCKLOS, m_hFollowTarget, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0 )
+ {
+ *pFaceTarget = m_hFollowTarget->GetAbsOrigin();
+ return false;
+ }
+ return true;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::FindCoverFromEnemyAtFollowTarget( float coverRadius, Vector *pResult )
+{
+ CBaseEntity *pEntity = GetEnemy();
+
+ return GetOuter()->FindCoverPosInRadius( pEntity, m_FollowNavGoal.position, coverRadius, pResult );
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::StartTask( const Task_t *pTask )
+{
+ AI_PROFILE_SCOPE( CAI_FollowBehavior_StartTask );
+
+ switch ( pTask->iTask )
+ {
+ case TASK_RANGE_ATTACK1:
+ BaseClass::StartTask( pTask );
+ break;
+
+ case TASK_GET_PATH_TO_FOLLOW_POSITION:
+ {
+ if ( !UpdateFollowPosition() )
+ {
+ TaskFail(FAIL_NO_TARGET);
+ }
+ else
+ {
+ m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
+ m_bMovingToCover = false;
+ GetOuter()->m_vInterruptSavePosition = vec3_invalid;
+ }
+
+ break;
+ }
+
+ case TASK_CANT_FOLLOW:
+ {
+ SetFollowTarget( NULL, true );
+ TaskComplete();
+ break;
+ }
+
+ case TASK_FOLLOWER_FACE_TACTICAL:
+ case TASK_FACE_FOLLOW_TARGET:
+ {
+ if ( !m_TimeBeforeSpreadFacing.Expired() )
+ {
+ m_TimeNextSpreadFacing.Reset();
+ }
+
+ Vector faceTarget = vec3_invalid;
+ bool bFollowingPoint = ( dynamic_cast<CPointEntity *>(m_hFollowTarget.Get()) != NULL );
+ if ( GetNpcState() == NPC_STATE_COMBAT )
+ {
+ if( gpGlobals->curtime - GetOuter()->GetEnemyLastTimeSeen() < 5.0 )
+ {
+ faceTarget = GetEnemyLKP();
+ }
+ else if ( !bFollowingPoint )
+ {
+ GetFollowTargetViewLoc( &faceTarget );
+ }
+ }
+ else if ( m_hFollowTarget && !bFollowingPoint )
+ {
+ if ( m_bFirstFacing && m_hFollowTarget->IsPlayer() )
+ {
+ faceTarget = m_hFollowTarget->GetAbsOrigin();
+ }
+ else if ( m_TimeNextSpreadFacing.Expired() )
+ {
+ m_TimeNextSpreadFacing.Reset();
+
+ bool bIsEpisodicVitalAlly;
+
+#ifdef HL2_DLL
+ bIsEpisodicVitalAlly = (hl2_episodic.GetBool() && GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL);
+#else
+ bIsEpisodicVitalAlly = false;
+#endif//HL2_DLL
+
+ if( bIsEpisodicVitalAlly )
+ {
+ faceTarget = m_hFollowTarget->GetAbsOrigin();
+ }
+ else
+ {
+ int roll = random->RandomInt(1, 4);
+ if ( roll == 1 )
+ {
+ GetFollowTargetViewLoc( &faceTarget );
+ }
+ else if ( roll == 2 )
+ {
+ faceTarget = m_hFollowTarget->GetAbsOrigin();
+ }
+ else
+ {
+ // Fan out and face to cover all directions.
+ int count = g_AIFollowManager.CountFollowersInGroup( GetOuter() );
+
+ if( count > 0 )
+ {
+ // Slice up the directions among followers and leader. ( +1 because we count the leader!)
+ float flSlice = 360.0 / (count + 1);
+
+ // Add one to slots so then are 1 to N instead of 0 to N - 1.
+ int slot = random->RandomInt( 0, count );
+
+ QAngle angle = m_hFollowTarget->GetAbsAngles();
+
+ // split up the remaining angles among followers in my group.
+ angle.y = UTIL_AngleMod( angle.y + ( flSlice * slot ) );
+
+ Vector vecDir;
+ AngleVectors( angle, &vecDir );
+
+ faceTarget = GetOuter()->GetAbsOrigin() + vecDir * 128;
+ }
+ }
+ }
+ }
+ else
+ {
+ // Stay where we are
+ TaskComplete();
+ break;
+ }
+ }
+
+ m_bFirstFacing = false;
+
+ if ( ValidateFaceTarget( &faceTarget ) )
+ {
+ Assert( faceTarget != vec3_invalid );
+
+ if ( !GetOuter()->FInAimCone( faceTarget ) )
+ {
+ GetMotor()->SetIdealYawToTarget( faceTarget, 30 );
+ GetOuter()->SetTurnActivity();
+ }
+ else
+ TaskComplete();
+ }
+ else
+ ChainStartTask( TASK_FACE_REASONABLE );
+
+ break;
+ }
+
+ case TASK_MOVE_TO_FOLLOW_POSITION:
+ {
+ if ( m_hFollowTarget == NULL)
+ {
+ TaskFail(FAIL_NO_TARGET);
+ }
+ else if ( (m_hFollowTarget->GetAbsOrigin() - GetAbsOrigin()).Length() < 1 )
+ {
+ TaskComplete();
+ }
+ else if ( !GetNavigator()->IsGoalActive() )
+ {
+ TaskFail(FAIL_NO_ROUTE);
+ }
+ else
+ {
+ m_vFollowMoveAnchor = GetAbsOrigin();
+ m_CurrentFollowActivity = ACT_INVALID;
+ m_RepathOnFollowTimer.Force();
+ }
+ break;
+ }
+
+ case TASK_SET_FOLLOW_TARGET_MARK:
+ {
+ if ( m_hFollowTarget == NULL)
+ {
+ TaskFail(FAIL_NO_TARGET);
+ }
+ else
+ {
+ FollowMsg( "TASK_SET_FOLLOW_TARGET_MARK\n" );
+ m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_SET_FOLLOW_DELAY:
+ {
+ m_FollowDelay.Start( pTask->flTaskData );
+ TaskComplete();
+ break;
+ }
+
+ case TASK_FIND_COVER_FROM_ENEMY:
+ {
+ CBaseEntity *pLeader = GetFollowTarget();
+ if ( pLeader )
+ {
+ Vector coverPos = vec3_invalid;
+ float coverRadius = MIN( GetOuter()->CoverRadius(), m_FollowNavGoal.coverTolerance );
+
+ if ( FindCoverFromEnemyAtFollowTarget( coverRadius, &coverPos ) )
+ {
+ AI_NavGoal_t goal(GOALTYPE_COVER, coverPos, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS);
+ GetNavigator()->SetGoal( goal );
+
+ GetOuter()->m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
+ TaskComplete();
+ }
+ else
+ TaskFail(FAIL_NO_COVER);
+ }
+ else
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+ case TASK_GET_PATH_TO_FOLLOW_POINT:
+ {
+ ChainStartTask( TASK_GET_PATH_TO_HINTNODE, ShouldIgnoreFollowPointFacing() );
+ break;
+ }
+
+ case TASK_ARRIVE_AT_FOLLOW_POINT:
+ {
+ if ( GetHintNode() && !ShouldIgnoreFollowPointFacing() )
+ ChainStartTask( TASK_FACE_HINTNODE, 0 );
+ else
+ TaskComplete();
+ break;
+ }
+
+ case TASK_SET_FOLLOW_POINT_STAND_SCHEDULE:
+ {
+ if ( GetHintNode() && !ShouldIgnoreFollowPointFacing() )
+ {
+ float distSqToPoint = (GetHintNode()->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
+ if ( distSqToPoint < WAIT_HINT_MIN_DIST )
+ {
+ GetOuter()->SetSchedule( SCHED_FOLLOWER_STAND_AT_WAIT_POINT );
+ }
+ else
+ {
+ GetHintNode()->Unlock();
+ SetHintNode( NULL );
+ m_TimeBlockUseWaitPoint.Reset();
+ TaskFail("Couldn't get to wait node." );
+ }
+ }
+ else
+ {
+ GetOuter()->SetSchedule( SCHED_FACE_FOLLOW_TARGET );
+ }
+ break;
+ }
+
+ case TASK_BEGIN_STAND_AT_WAIT_POINT:
+ {
+ if ( !m_TargetMonitor.IsMarkSet() && IsFollowPointInRange() )
+ m_TargetMonitor.SetMark( m_hFollowTarget, m_FollowNavGoal.targetMoveTolerance );
+ if ( GetHintNode() && !ShouldIgnoreFollowPointFacing() )
+ ChainStartTask( TASK_FACE_HINTNODE, 0 );
+ else
+ TaskComplete();
+ break;
+ }
+
+ default:
+ BaseClass::StartTask( pTask );
+ }
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_GET_PATH_TO_FOLLOW_POSITION:
+ {
+ switch( GetOuter()->GetTaskInterrupt() )
+ {
+ case 0:
+ {
+ if ( GetEnemy() )
+ {
+ Assert( GetOuter()->m_vInterruptSavePosition == vec3_invalid );
+ Vector coverPos = vec3_invalid;
+ float coverRadius = MIN( (float)12*12, m_FollowNavGoal.coverTolerance );
+ if ( FindCoverFromEnemyAtFollowTarget( coverRadius, &coverPos ) )
+ {
+ GetOuter()->m_vInterruptSavePosition = coverPos;
+ }
+ GetOuter()->TaskInterrupt();
+ break;
+ }
+ }
+ // Fall through...
+
+ case 1:
+ {
+ if ( GetOuter()->m_vInterruptSavePosition != vec3_invalid )
+ {
+ AI_NavGoal_t goal(GOALTYPE_COVER, GetOuter()->m_vInterruptSavePosition, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS);
+ if ( GetNavigator()->SetGoal( goal, AIN_NO_PATH_TASK_FAIL ) )
+ {
+ TaskComplete();
+ m_bMovingToCover = true;
+ }
+ else
+ {
+ GetOuter()->TaskInterrupt();
+ }
+ break;
+ }
+ // Fall through...
+ }
+
+ case 2:
+ {
+ Assert( !m_bMovingToCover );
+ Vector vGoalPosition;
+ if ( HasFollowPoint() && IsFollowPointInRange() )
+ vGoalPosition = GetFollowPoint();
+ else
+ vGoalPosition = GetGoalPosition();
+
+ AI_NavGoal_t goal( vGoalPosition, AIN_DEF_ACTIVITY, GetGoalTolerance() );
+ if ( !m_hFollowTarget->GetParent() || !m_hFollowTarget->GetParent()->GetServerVehicle() )
+ {
+ goal.pTarget = m_hFollowTarget;
+ }
+ else
+ {
+ goal.pTarget = m_hFollowTarget->GetParent();
+ }
+
+ bool bSuccess = true;
+ if ( !GetNavigator()->SetGoal( goal, AIN_NO_PATH_TASK_FAIL ) )
+ {
+ const Vector &vTarget = GetFollowTarget()->WorldSpaceCenter();
+ Vector vToGoal = vGoalPosition - vTarget;
+ if ( vToGoal.Length2DSqr() > 6*12 )
+ {
+ goal.dest = vTarget + vToGoal * 0.5;
+ if ( !GetNavigator()->SetGoal( goal, AIN_NO_PATH_TASK_FAIL ) )
+ {
+ bSuccess = false;
+ m_FollowDelay.Start( 2.0, 5.0 );
+ }
+ }
+ else
+ {
+ bSuccess = false;
+ m_FollowDelay.Start( 2.0, 5.0 );
+ }
+ }
+
+ if ( !bSuccess )
+ {
+ m_bFollowNavFailed = true;
+ TaskFail( FAIL_NO_ROUTE );
+ }
+ else
+ {
+ TaskComplete();
+ }
+ }
+ }
+
+ break;
+ }
+
+ case TASK_FOLLOWER_FACE_TACTICAL:
+ case TASK_FACE_FOLLOW_TARGET:
+ {
+ ChainRunTask( TASK_FACE_REASONABLE );
+ break;
+ }
+
+ case TASK_MOVE_TO_FOLLOW_POSITION:
+ {
+ if ( m_hFollowTarget == NULL )
+ {
+ TaskFail(FAIL_NO_TARGET);
+ }
+ else
+ {
+ if ( m_bMovingToCover )
+ {
+ ChainRunTask( TASK_WAIT_FOR_MOVEMENT );
+ NoteSuccessfulFollow();
+ return;
+ }
+
+ // Re-evaluate when you think your finished, or the target has moved too far
+ if ( !UpdateFollowPosition() )
+ {
+ TaskFail(FAIL_NO_TARGET);
+ break;
+ }
+
+ if ( ShouldUseFollowPoints() && ai_follow_use_points_when_moving.GetBool() )
+ {
+ if ( HasFollowPoint() )
+ {
+ if ( !IsFollowPointInRange() )
+ {
+ ClearFollowPoint();
+ GetNavigator()->SetArrivalDirection( vec3_origin );
+ GetNavigator()->SetArrivalActivity( ACT_INVALID );
+ m_TimeBlockUseWaitPoint.Reset();
+ m_TimeCheckForWaitPoint.Reset();
+ }
+ }
+ if ( GetNavigator()->GetNavType() != NAV_JUMP && !HasFollowPoint() && m_pInterruptWaitPoint )
+ {
+ SetFollowPoint( m_pInterruptWaitPoint );
+ }
+ }
+ else
+ {
+ ClearFollowPoint();
+ if ( GetNavigator()->IsGoalActive() )
+ {
+ GetNavigator()->SetArrivalDirection( vec3_origin );
+ GetNavigator()->SetArrivalActivity( ACT_INVALID );
+ }
+ }
+
+ if ( !GetNavigator()->IsGoalActive() )
+ {
+ // What this probably means is that the navigation failed but within tolerance
+ // So for now, just call it good and block another attempt for a bit
+ TaskComplete();
+ if ( !IsFollowPointInRange() )
+ ClearFollowPoint();
+ if ( !IsFollowGoalInRange( m_FollowNavGoal.tolerance, GetGoalZRange(), GetGoalFlags() ) )
+ m_FollowDelay.Start( 0.25, 0.75 );
+ else
+ {
+ m_TargetMonitor.SetMark( GetFollowTarget(), m_FollowNavGoal.targetMoveTolerance );
+ m_bTargetUnreachable = false;
+ }
+ break;
+ }
+
+ if ( !HasFollowPoint() )
+ {
+ float range = GetGoalRange();
+
+ Vector vVelocity =- GetFollowTarget()->GetSmoothedVelocity();
+ bool bDoSlowdown = ( vVelocity.LengthSqr() < Square(4*12) );
+ if ( bDoSlowdown )
+ {
+ range += GetMotor()->MinStoppingDist(12) - 12;
+ }
+
+ if ( IsFollowGoalInRange( range, GetGoalZRange(), GetGoalFlags() ) )
+ {
+ m_TimeBeforeSpreadFacing.Reset();
+ TaskComplete();
+ GetNavigator()->StopMoving( !bDoSlowdown ); // Stop moving
+ m_TargetMonitor.SetMark( GetFollowTarget(), m_FollowNavGoal.targetMoveTolerance );
+ break;
+ }
+
+ // Update the nav goal if needed
+ if ( m_RepathOnFollowTimer.Expired() )
+ {
+ if ( (GetNavigator()->GetGoalPos() - GetGoalPosition()).LengthSqr() > Square( m_FollowNavGoal.repathOnRouteTolerance ) )
+ {
+ if ( GetNavigator()->GetNavType() != NAV_JUMP )
+ {
+ m_RepathOnFollowTimer.Set( .5 );
+ if ( !GetNavigator()->UpdateGoalPos( GetGoalPosition() ) )
+ {
+ bool bSuccess = false;
+ const Vector &vTarget = GetFollowTarget()->WorldSpaceCenter();
+ Vector vToGoal = GetGoalPosition() - vTarget;
+ if ( vToGoal.Length2DSqr() > 6*12 )
+ {
+ if ( GetNavigator()->UpdateGoalPos( vTarget + vToGoal * 0.5 ) )
+ {
+ bSuccess = true;
+ }
+ }
+
+ if ( !bSuccess )
+ {
+ TaskFail(FAIL_NO_ROUTE);
+ m_bTargetUnreachable = true;
+ }
+ break;
+ }
+ NoteSuccessfulFollow();
+ }
+ }
+ }
+ }
+ else
+ {
+ const Vector &vFollowPoint = GetFollowPoint();
+ if ( GetNavigator()->GetGoalPos() != vFollowPoint )
+ {
+ if ( !GetNavigator()->UpdateGoalPos( vFollowPoint ) )
+ {
+ TaskFail(FAIL_NO_ROUTE);
+ m_bTargetUnreachable = true;
+ break;
+ }
+ NoteSuccessfulFollow();
+
+ if ( !ShouldIgnoreFollowPointFacing() )
+ GetNavigator()->SetArrivalDirection( GetHintNode()->GetDirection() );
+ if ( GetHintNode()->HintActivityName() != NULL_STRING )
+ {
+ Activity hintActivity = (Activity)CAI_BaseNPC::GetActivityID( STRING(GetHintNode()->HintActivityName()) );
+ if ( hintActivity != ACT_INVALID )
+ {
+ GetNavigator()->SetArrivalActivity( GetOuter()->GetHintActivity(GetHintNode()->HintType(), hintActivity ) );
+ }
+ else
+ {
+ int iSequence = GetOuter()->LookupSequence(STRING(GetHintNode()->HintActivityName()));
+ if ( iSequence != ACT_INVALID )
+ {
+ GetNavigator()->SetArrivalSequence( iSequence );
+ }
+ }
+ }
+ }
+ }
+
+ // Set the appropriate activity based on an overlapping range
+ // overlap the range to prevent oscillation
+ // BUGBUG: this is checking linear distance (ie. through walls) and not path distance or even visibility
+
+ // Never stop running once started
+ if ( m_CurrentFollowActivity != ACT_RUN )
+ {
+ float distToTargetSq = ( GetNavigator()->GetGoalPos() - GetLocalOrigin() ).Length2DSqr();
+
+ // Pick the right movement activity.
+ Activity followActivity = ( distToTargetSq < Square(m_FollowNavGoal.walkTolerance) && GetOuter()->GetState() != NPC_STATE_COMBAT ) ? ACT_WALK : ACT_RUN;
+
+ // If we're supposed to have LOS, run to catch up
+ if ( m_FollowNavGoal.flags & AIFF_REQUIRE_LOS_OUTSIDE_COMBAT )
+ {
+ if ( !GetOuter()->GetSenses()->DidSeeEntity( m_hFollowTarget ) )
+ {
+ followActivity = ACT_RUN;
+ }
+ }
+
+ if ( followActivity != m_CurrentFollowActivity )
+ {
+ m_CurrentFollowActivity = followActivity;
+ GetNavigator()->SetMovementActivity(followActivity);
+ }
+ }
+
+ if ( ( m_vFollowMoveAnchor - GetAbsOrigin() ).LengthSqr() > Square( 15.0 * 12.0 ) )
+ {
+ m_vFollowMoveAnchor = GetAbsOrigin();
+ NoteSuccessfulFollow();
+ }
+
+ }
+ break;
+ }
+
+ case TASK_ARRIVE_AT_FOLLOW_POINT:
+ {
+ ChainRunTask( TASK_FACE_HINTNODE, 0 );
+ break;
+ }
+
+ case TASK_BEGIN_STAND_AT_WAIT_POINT:
+ {
+ ChainRunTask( TASK_FACE_HINTNODE, 0 );
+ break;
+ }
+
+ default:
+ BaseClass::RunTask( pTask );
+ }
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::TaskComplete( bool fIgnoreSetFailedCondition )
+{
+ const Task_t *pTask = GetCurTask();
+ if ( pTask->iTask == TASK_MOVE_TO_FOLLOW_POSITION || pTask->iTask == TASK_GET_PATH_TO_FOLLOW_POSITION )
+ NoteSuccessfulFollow();
+ BaseClass::TaskComplete( fIgnoreSetFailedCondition );
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::BuildScheduleTestBits()
+{
+ BaseClass::BuildScheduleTestBits();
+ bool bIsTakeCover = false;
+ bool bIsHideAndReload = false;
+ bool bIsReload = false;
+ bool bIgnoreMovedMark = false;
+
+ if ( ( GetOuter()->ConditionInterruptsCurSchedule( COND_GIVE_WAY ) ||
+ GetOuter()->ConditionInterruptsCurSchedule( COND_IDLE_INTERRUPT ) ||
+ ( bIsHideAndReload = IsCurSchedule(SCHED_HIDE_AND_RELOAD ) ) == true ||
+ ( bIsReload = IsCurSchedule(SCHED_RELOAD ) ) == true ||
+ IsCurSchedule(SCHED_STANDOFF ) ||
+ ( bIsTakeCover = IsCurSchedule(SCHED_TAKE_COVER_FROM_ENEMY ) ) == true ||
+ IsCurSchedule(SCHED_COMBAT_FACE ) ||
+ IsCurSchedule(SCHED_ALERT_FACE ) ||
+ IsCurSchedule(SCHED_COMBAT_STAND ) ||
+ IsCurSchedule(SCHED_ALERT_STAND) ) ||
+ IsCurSchedule(SCHED_ALERT_FACE_BESTSOUND ) )
+ {
+#ifdef HL2_EPISODIC
+ if( IsCurSchedule(SCHED_RELOAD, false) && GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // Alyx and Barney do not stop reloading because the player has moved.
+ // Citizens and other regular allies do.
+ bIgnoreMovedMark = true;
+ }
+#endif//HL2_EPISODIC
+
+ if( !bIgnoreMovedMark )
+ {
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_TARGET_MOVED_FROM_MARK ) );
+ }
+
+ if ( !bIsTakeCover && !bIsHideAndReload && !bIsReload )
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_FOLLOW_DELAY_EXPIRED) );
+ }
+
+ // Add logic for NPCs not able to move and shoot
+ if ( hl2_episodic.GetBool() )
+ {
+ if ( IsCurScheduleFollowSchedule() && GetOuter()->ShouldMoveAndShoot() == false )
+ {
+ GetOuter()->SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
+ }
+
+#ifdef HL2_EPISODIC
+ // In Alyx darkness mode, break on the player turning their flashlight off
+ if ( HL2GameRules()->IsAlyxInDarknessMode() )
+ {
+ if ( IsCurSchedule(SCHED_FOLLOW, false) || IsCurSchedule(SCHED_MOVE_TO_FACE_FOLLOW_TARGET, false) ||
+ IsCurSchedule(SCHED_FACE_FOLLOW_TARGET, false) )
+ {
+ GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_FOLLOW_PLAYER_IS_NOT_LIT ) );
+ }
+ }
+#endif // HL2_EPISODIC
+ }
+
+ if ( GetNpcState() == NPC_STATE_COMBAT && IsCurScheduleFollowSchedule() )
+ {
+ GetOuter()->ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
+ }
+}
+
+//-------------------------------------
+
+Activity CAI_FollowBehavior::NPC_TranslateActivity( Activity activity )
+{
+ if ( activity == ACT_IDLE && HasFollowPoint() && GetHintNode()->HintActivityName() != NULL_STRING )
+ {
+ return GetOuter()->GetHintActivity(GetHintNode()->HintType(), (Activity)CAI_BaseNPC::GetActivityID( STRING(GetHintNode()->HintActivityName()) ) );
+ }
+ return BaseClass::NPC_TranslateActivity( activity );
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsCurScheduleFollowSchedule()
+{
+ int curScheduleId = ( GetOuter()->GetCurSchedule() ) ? GetOuter()->GetCurSchedule()->GetId() : SCHED_NONE;
+ if ( curScheduleId >= GetClassScheduleIdSpace()->ScheduleLocalToGlobal( SCHED_FOLLOWER_MOVE_AWAY_FAIL ) &&
+ curScheduleId <= GetClassScheduleIdSpace()->ScheduleLocalToGlobal( SCHED_FOLLOWER_STAND_AT_WAIT_POINT ) )
+ {
+ return true;
+ }
+ return false;
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsCurTaskContinuousMove()
+{
+ const Task_t *pCurTask = GetCurTask();
+ if ( pCurTask && pCurTask->iTask == TASK_MOVE_TO_FOLLOW_POSITION )
+ return true;
+ return BaseClass::IsCurTaskContinuousMove();
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::OnMovementFailed()
+{
+ float acceptDist = m_FollowNavGoal.range;
+ if ( m_FollowNavGoal.tolerance > acceptDist )
+ acceptDist = m_FollowNavGoal.tolerance;
+
+ if ( GetNpcState() == NPC_STATE_COMBAT )
+ {
+ if ( m_FollowNavGoal.coverTolerance > acceptDist )
+ acceptDist = m_FollowNavGoal.coverTolerance;
+ if (m_FollowNavGoal.enemyLOSTolerance > acceptDist )
+ acceptDist = m_FollowNavGoal.enemyLOSTolerance;
+ }
+
+ float flZRange = GetGoalZRange();
+ if ( GetGoalZRange() == -1 )
+ {
+ flZRange = GetHullHeight() * 2;
+ }
+
+ if ( IsFollowGoalInRange( acceptDist * 1.5, flZRange, GetGoalFlags() ) )
+ m_bTargetUnreachable = true;
+ else
+ m_FollowDelay.Start();
+}
+
+//-------------------------------------
+
+void CAI_FollowBehavior::OnMovementComplete()
+{
+ if ( !IsCurSchedule(SCHED_FOLLOWER_GO_TO_WAIT_POINT) )
+ m_TimeBeforeSpreadFacing.Reset();
+ else
+ {
+ m_TimeBeforeSpreadFacing.Force();
+ m_TimeNextSpreadFacing.Force();
+ }
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::FValidateHintType( CAI_Hint *pHint )
+{
+ if ( pHint->HintType() == HINT_FOLLOW_WAIT_POINT )
+ {
+ if ( GetFollowTarget() && GetFollowTarget()->FVisible( pHint->GetAbsOrigin() + Vector( 0, 0, 0.1 ) ) )
+ return true;
+ else
+ return false;
+ }
+ return BaseClass::FValidateHintType( pHint );
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsValidCover( const Vector &vLocation, CAI_Hint const *pHint )
+{
+ if ( (vLocation - m_FollowNavGoal.position).LengthSqr() > Square( m_FollowNavGoal.coverTolerance + 0.1 ) )
+ return false;
+ return BaseClass::IsValidCover( vLocation, pHint );
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::IsValidShootPosition( const Vector &vLocation, CAI_Node *pNode, CAI_Hint const *pHint )
+{
+ if ( (vLocation - m_FollowNavGoal.position).LengthSqr() > Square( m_FollowNavGoal.enemyLOSTolerance + 0.1 ) )
+ return false;
+ return BaseClass::IsValidShootPosition( vLocation, pNode, pHint );
+}
+
+//-------------------------------------
+
+bool CAI_FollowBehavior::ShouldAlwaysThink()
+{
+ return ( m_hFollowTarget && m_hFollowTarget->IsPlayer() );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// CAI_FollowGoal
+//
+// Purpose: A level tool to control the follow behavior. Use is not required
+// in order to use behavior.
+//
+//-----------------------------------------------------------------------------
+
+BEGIN_DATADESC( CAI_FollowGoal )
+ DEFINE_KEYFIELD( m_iFormation, FIELD_INTEGER, "Formation" ),
+
+#ifdef HL2_EPISODIC
+ DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
+#endif
+END_DATADESC()
+
+//-------------------------------------
+
+LINK_ENTITY_TO_CLASS( ai_goal_follow, CAI_FollowGoal );
+
+//-------------------------------------
+
+void CAI_FollowGoal::EnableGoal( CAI_BaseNPC *pAI )
+{
+ CAI_FollowBehavior *pBehavior;
+ if ( !pAI->GetBehavior( &pBehavior ) )
+ return;
+
+ CBaseEntity *pGoalEntity = GetGoalEntity();
+ if ( !pGoalEntity && AI_IsSinglePlayer() )
+ {
+ if ( pAI->IRelationType(UTIL_GetLocalPlayer()) == D_LI )
+ {
+ pGoalEntity = UTIL_GetLocalPlayer();
+ SetGoalEntity( pGoalEntity );
+ }
+ }
+
+ if ( pGoalEntity )
+ pBehavior->SetFollowGoal( this );
+}
+
+//-------------------------------------
+
+void CAI_FollowGoal::DisableGoal( CAI_BaseNPC *pAI )
+{
+ CAI_FollowBehavior *pBehavior;
+ if ( !pAI || !pAI->GetBehavior( &pBehavior ) )
+ return;
+
+ pBehavior->ClearFollowGoal( this );
+}
+
+//-------------------------------------
+
+#ifdef HL2_EPISODIC
+void CAI_FollowGoal::InputOutsideTransition( inputdata_t &inputdata )
+{
+ EnterDormant();
+}
+#endif
+
+//-----------------------------------------------------------------------------
+//
+// CAI_FollowManager
+//
+//-----------------------------------------------------------------------------
+
+//-------------------------------------
+//
+// Purpose: Formation definitions
+//
+
+// @TODO (toml 11-21-03): rework follow so we don't have to have class specifc formations in this file
+
+struct AI_FollowSlot_t
+{
+ int priority;
+
+ TableVector position;
+ float positionVariability;
+
+ float rangeMin;
+ float rangeMax;
+
+ float Zrange;
+
+ float tolerance;
+
+ // @Q (toml 02-28-03): facing?
+};
+
+struct AI_FollowFormation_t
+{
+ const char * pszName;
+ unsigned flags;
+ int nSlots;
+
+ // Range within which can exit formation to seek a follow point
+ float followPointTolerance;
+
+ // Distance target must move to reset formation
+ float targetMoveTolerance;
+
+ // Distance from current move goal target must move to force a repathfind
+ float repathOnRouteTolerance;
+
+ // Distance from target within which should walk, not run to formation
+ float walkTolerance;
+
+ // Distance within which can exit formation to seek cover
+ float coverTolerance;
+
+ // Distance within which can exit formation to seek LOS to enemy
+ float enemyLOSTolerance;
+
+ // Distance within which can exit formation to chase enemy
+ float chaseEnemyTolerance;
+
+ AI_FollowSlot_t * pSlots;
+};
+
+//-------------------------------------
+
+static AI_FollowSlot_t g_SimpleFollowFormationSlots[] =
+{
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 96, 120, -1, 128 },
+};
+
+static AI_FollowFormation_t g_SimpleFollowFormation =
+{
+ "Simple",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
+ ARRAYSIZE(g_SimpleFollowFormationSlots),
+ 168, // followPointTolerance
+ 36, // targetMoveTolerance
+ 60, // repathOnRouteTolerance
+ 190, // walkTolerance
+ 300, // coverTolerance
+ 300, // enemyLOSTolerance
+ 300, // chaseEnemyTolerance
+ g_SimpleFollowFormationSlots,
+};
+
+
+//-------------------------------------
+
+static AI_FollowSlot_t g_WideFollowFormationSlots[] =
+{
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 240, -1, 128 },
+};
+
+static AI_FollowFormation_t g_WideFollowFormation =
+{
+ "Wide",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
+ ARRAYSIZE(g_WideFollowFormationSlots),
+ 168, // followPointTolerance
+ 72, // targetMoveTolerance
+ 60, // repathOnRouteTolerance
+ 190, // walkTolerance
+ 600, // coverTolerance
+ 600, // enemyLOSTolerance
+ 600, // chaseEnemyTolerance
+ g_WideFollowFormationSlots,
+};
+
+//---------------------------------------------
+// Antlion use very loose following criteria
+
+static AI_FollowSlot_t g_AntlionFollowFormationSlots[] =
+{
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 150, 250, -1, 128 },
+};
+
+static AI_FollowFormation_t g_AntlionFollowFormation =
+{
+ "Antlion",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
+ ARRAYSIZE(g_AntlionFollowFormationSlots),
+ 168, // followPointTolerance
+ 36, // targetMoveTolerance
+ 60, // repathOnRouteTolerance
+ 190, // walkTolerance
+ 1024, // coverTolerance
+ 1024, // enemyLOSTolerance
+ 1024, // chaseEnemyTolerance
+ g_AntlionFollowFormationSlots,
+};
+
+//-------------------------------------
+
+#define COMMANDER_TOLERANCE (13.0 * 1.415)
+
+static AI_FollowSlot_t g_CommanderFollowFormationSlots[] =
+{
+ { 2, { 0, 0, 0 }, 0, COMMANDER_TOLERANCE, COMMANDER_TOLERANCE, -1, 48 },
+ { 1, { 0, 0, 0 }, 0, COMMANDER_TOLERANCE, COMMANDER_TOLERANCE, -1, 48 },
+ { 1, { 0, 0, 0 }, 0, COMMANDER_TOLERANCE, COMMANDER_TOLERANCE, -1, 48 },
+ { 1, { 0, 0, 0 }, 0, COMMANDER_TOLERANCE, COMMANDER_TOLERANCE, -1, 48 },
+};
+
+static AI_FollowFormation_t g_CommanderFollowFormation =
+{
+ "Commander",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
+ ARRAYSIZE(g_CommanderFollowFormationSlots),
+ 168, // followPointTolerance
+ 6, // targetMoveTolerance
+ 60, // repathOnRouteTolerance
+ 12, // walkTolerance
+ 300, // coverTolerance
+ 300, // enemyLOSTolerance
+ 300, // chaseEnemyTolerance
+ g_CommanderFollowFormationSlots,
+};
+
+//-------------------------------------
+
+static AI_FollowSlot_t g_TightFollowFormationSlots[] =
+{
+ { 1, { 0, 0, 0 }, 0, 0, 0, -1, 48 },
+ { 1, { 0, 0, 0 }, 0, 0, 0, -1, 48 },
+ { 1, { 0, 0, 0 }, 0, 0, 0, -1, 48 },
+ { 1, { 0, 0, 0 }, 0, 0, 0, -1, 48 },
+};
+
+static AI_FollowFormation_t g_TightFollowFormation =
+{
+ "Tight",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
+ ARRAYSIZE(g_CommanderFollowFormationSlots),
+ 48, // followPointTolerance
+ 6, // targetMoveTolerance
+ 60, // repathOnRouteTolerance
+ 12, // walkTolerance
+ 300, // coverTolerance
+ 32, // enemyLOSTolerance
+ 32, // chaseEnemyTolerance
+ g_TightFollowFormationSlots,
+};
+
+//-------------------------------------
+
+static AI_FollowSlot_t g_MediumFollowFormationSlots[] =
+{
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+ { 1, { 0, 0, 0 }, 0, 156, 156, -1, 128 },
+};
+
+static AI_FollowFormation_t g_MediumFollowFormation =
+{
+ "Medium",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
+ ARRAYSIZE(g_MediumFollowFormationSlots),
+ 168, // followPointTolerance
+ 36, // targetMoveTolerance
+ 60, // repathOnRouteTolerance
+ 190, // walkTolerance
+ 300, // coverTolerance
+ 300, // enemyLOSTolerance
+ 300, // chaseEnemyTolerance
+ g_MediumFollowFormationSlots,
+};
+
+//-------------------------------------
+
+static AI_FollowSlot_t g_SidekickFollowFormationSlots[] =
+{
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+};
+
+static AI_FollowFormation_t g_SidekickFollowFormation =
+{
+ "Sidekick",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS | AIFF_REQUIRE_LOS_OUTSIDE_COMBAT,
+ ARRAYSIZE(g_SidekickFollowFormationSlots),
+ 168, // followPointTolerance
+ 36, // targetMoveTolerance
+ 60, // repathOnRouteTolerance
+ 190, // walkTolerance
+ 300, // coverTolerance
+ 300, // enemyLOSTolerance
+ 300, // chaseEnemyTolerance
+ g_SidekickFollowFormationSlots,
+};
+
+
+//-------------------------------------
+// Used for hunters following striders
+//-------------------------------------
+static AI_FollowSlot_t g_HunterFollowFormationSlots[] =
+{
+ { 3, { 480, -240, -400 }, 0, 48, 64, 1000, 60 },
+ { 3, { 480, 240, -400 }, 0, 48, 64, 1000, 60 },
+ { 2, { 480, 0, -400 }, 0, 48, 64, 1000, 60 },
+ { 1, { -240, 0, -400 }, 0, 48, 64, 1000, 60 },
+};
+
+static AI_FollowFormation_t g_HunterFollowFormation =
+{
+ "Hunter",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS,
+ ARRAYSIZE(g_HunterFollowFormationSlots),
+ 48, // followPointTolerance
+ 48, // targetMoveTolerance
+ 60,//180, // repathOnRouteTolerance
+ 0, // walkTolerance
+ 960, // coverTolerance
+ 960, // enemyLOSTolerance
+ 1920, // chaseEnemyTolerance
+ g_HunterFollowFormationSlots,
+};
+
+
+//-------------------------------------
+
+static AI_FollowSlot_t g_VortigauntFollowFormationSlots[] =
+{
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+ { 1, { 0, 0, 0 }, 0, 120, 160, 256, 128 },
+};
+
+static AI_FollowFormation_t g_VortigauntFollowFormation =
+{
+ "Vortigaunt",
+ AIFF_DEFAULT | AIFF_USE_FOLLOW_POINTS | AIFF_REQUIRE_LOS_OUTSIDE_COMBAT,
+ ARRAYSIZE(g_VortigauntFollowFormationSlots),
+ 168, // followPointTolerance
+ 36, // targetMoveTolerance
+ 60, // repathOnRouteTolerance
+ 190, // walkTolerance
+ 300, // coverTolerance
+ (50*12), // enemyLOSTolerance
+ (50*12), // chaseEnemyTolerance
+ g_VortigauntFollowFormationSlots,
+};
+
+
+//-----------------------------------------------------------------------------
+// NOTE: these must correspond with the AI_Formations_t enumeration in AI_Behavior_Follow.h!!
+//-----------------------------------------------------------------------------
+AI_FollowFormation_t *g_AI_Formations[] =
+{
+ &g_SimpleFollowFormation,
+ &g_WideFollowFormation,
+ &g_AntlionFollowFormation,
+ &g_CommanderFollowFormation,
+ &g_TightFollowFormation,
+ &g_MediumFollowFormation,
+ &g_SidekickFollowFormation,
+ &g_HunterFollowFormation,
+ &g_VortigauntFollowFormation,
+};
+
+AI_FollowFormation_t *AIGetFormation( AI_Formations_t formation )
+{
+ if ( formation < 0 )
+ formation = (AI_Formations_t)0;
+ else if ( formation >= ARRAYSIZE( g_AI_Formations ) )
+ formation = (AI_Formations_t)(ARRAYSIZE( g_AI_Formations ) - 1 );
+
+ return g_AI_Formations[formation];
+}
+
+//---------------------------------------------------------
+
+bool CAI_FollowManager::AddFollower( CBaseEntity *pTarget, CAI_BaseNPC *pFollower, AI_Formations_t formation, AI_FollowManagerInfoHandle_t *pHandle )
+{
+ AI_FollowGroup_t *pGroup = FindCreateGroup( pTarget, formation );
+ int slot = FindBestSlot( pGroup );
+
+ if ( slot != -1 )
+ {
+ MEM_ALLOC_CREDIT();
+
+ AI_FollowSlot_t *pSlot = &pGroup->pFormation->pSlots[slot];
+
+ int i = pGroup->followers.AddToTail( );
+
+ AI_Follower_t *iterNode = &pGroup->followers[i];
+ iterNode->hFollower = pFollower;
+ iterNode->slot = slot;
+ iterNode->pGroup = pGroup;
+
+ pGroup->slotUsage.Set( slot );
+
+ CalculateFieldsFromSlot( pSlot, &iterNode->navInfo );
+
+ pHandle->m_hFollower = i;
+ pHandle->m_pGroup = pGroup;
+ return true;
+ }
+
+ pHandle->m_hFollower = 0;
+ pHandle->m_pGroup = NULL;
+ return false;
+}
+
+//-------------------------------------
+
+bool CAI_FollowManager::CalcFollowPosition( AI_FollowManagerInfoHandle_t& hInfo, AI_FollowNavInfo_t *pNavInfo )
+{
+ if ( hInfo.m_pGroup && hInfo.m_hFollower )
+ {
+ AI_FollowGroup_t *pGroup = hInfo.m_pGroup;
+ Assert( pGroup->hFollowTarget.Get() );
+ CBaseEntity *pTarget = pGroup->hFollowTarget;
+
+ AI_Follower_t *iterNode = &pGroup->followers[hInfo.m_hFollower];
+ if ( iterNode->navInfo.position != vec3_origin )
+ {
+ QAngle angles = pTarget->GetLocalAngles();
+ angles.x = angles.z = 0;
+
+ matrix3x4_t fRotateMatrix;
+ AngleMatrix(angles, fRotateMatrix);
+
+ VectorRotate( iterNode->navInfo.position, fRotateMatrix, pNavInfo->position);
+ pNavInfo->position += pTarget->WorldSpaceCenter();
+ }
+ else
+ {
+ pNavInfo->position = iterNode->navInfo.position + pTarget->WorldSpaceCenter();
+ }
+
+ pNavInfo->tolerance = iterNode->navInfo.tolerance;
+ pNavInfo->range = iterNode->navInfo.range;
+ pNavInfo->Zrange = iterNode->navInfo.Zrange;
+ pNavInfo->flags = pGroup->pFormation->flags;
+ pNavInfo->followPointTolerance = pGroup->pFormation->followPointTolerance;
+ pNavInfo->targetMoveTolerance = pGroup->pFormation->targetMoveTolerance;
+ pNavInfo->repathOnRouteTolerance = pGroup->pFormation->repathOnRouteTolerance;
+ pNavInfo->walkTolerance = pGroup->pFormation->walkTolerance;
+ pNavInfo->coverTolerance = pGroup->pFormation->coverTolerance;
+ pNavInfo->enemyLOSTolerance = pGroup->pFormation->enemyLOSTolerance;
+ pNavInfo->chaseEnemyTolerance = pGroup->pFormation->chaseEnemyTolerance;
+ return true;
+ }
+ return false;
+}
+
+//-------------------------------------
+
+bool CAI_FollowManager::RedistributeSlots( AI_FollowGroup_t *pGroup )
+{
+ bool result = false;
+
+ CUtlRBTree<CBaseEntity *> movedFollowers;
+ SetDefLessFunc( movedFollowers );
+
+ const Vector &originFollowed = pGroup->hFollowTarget->GetAbsOrigin();
+ int bestSlot;
+
+ while ( ( bestSlot = FindBestSlot( pGroup ) ) != -1 && ((int)movedFollowers.Count() < pGroup->followers.Count()) )
+ {
+ AI_FollowSlot_t * pSlot = &pGroup->pFormation->pSlots[bestSlot];
+ Vector slotPos = originFollowed + pSlot->position;
+ int h = pGroup->followers.Head();
+ int hBest = pGroup->followers.InvalidIndex();
+ float distSqBest = FLT_MAX;
+
+ while ( h != pGroup->followers.InvalidIndex() )
+ {
+ AI_Follower_t *p = &pGroup->followers[h];
+
+ if ( movedFollowers.Find( p->hFollower ) == movedFollowers.InvalidIndex() &&
+ ( p->slot == -1 || pSlot->priority > pGroup->pFormation->pSlots[p->slot].priority ) )
+ {
+ float distSqCur = ( p->hFollower->GetAbsOrigin() - slotPos ).LengthSqr();
+ if ( distSqCur < distSqBest )
+ {
+ hBest = h;
+ }
+ }
+
+ h = pGroup->followers.Next( h );
+ }
+
+ if ( hBest == pGroup->followers.InvalidIndex() )
+ break;
+
+ AI_Follower_t *pBest = &pGroup->followers[hBest];
+ if ( pBest->slot != -1 )
+ {
+ pGroup->slotUsage.Clear( pBest->slot );
+ }
+ pBest->slot = bestSlot;
+ CalculateFieldsFromSlot( pSlot, &pBest->navInfo );
+ pGroup->slotUsage.Set( bestSlot );
+ movedFollowers.Insert( pBest->hFollower );
+ result = true;
+ }
+ return result;
+}
+
+//-------------------------------------
+
+void CAI_FollowManager::ChangeFormation( AI_FollowManagerInfoHandle_t& hInfo, AI_Formations_t formation )
+{
+ if ( !hInfo.m_pGroup || !hInfo.m_hFollower )
+ return;
+
+ AI_FollowGroup_t *pGroup = hInfo.m_pGroup;
+ AI_FollowFormation_t *pNewFormation = AIGetFormation( formation );
+ if ( pNewFormation == pGroup->pFormation )
+ return;
+
+ int h = pGroup->followers.Head();
+
+ while ( h != pGroup->followers.InvalidIndex() )
+ {
+ CAI_FollowBehavior *pFollowBehavior;
+
+ AI_Follower_t *p = &pGroup->followers[h];
+ p->slot = -1;
+ p->hFollower->GetBehavior( &pFollowBehavior );
+ Assert( pFollowBehavior );
+ if ( pFollowBehavior )
+ {
+ pFollowBehavior->m_params.formation = formation;
+ pFollowBehavior->m_TargetMonitor.ClearMark();
+ pFollowBehavior->SetCondition( CAI_FollowBehavior::COND_TARGET_MOVED_FROM_MARK );
+ pFollowBehavior->m_bTargetUnreachable = false;
+ }
+
+ h = pGroup->followers.Next( h );
+ }
+
+ pGroup->slotUsage.ClearAll();
+ pGroup->pFormation = pNewFormation;
+ pGroup->slotUsage.Resize( pGroup->pFormation->nSlots );
+
+ RedistributeSlots( pGroup );
+
+#ifdef DEBUG
+ h = pGroup->followers.Head();
+ while ( h != pGroup->followers.InvalidIndex() )
+ {
+ AI_Follower_t *p = &pGroup->followers[h];
+ Assert( p->slot != -1 );
+ h = pGroup->followers.Next( h );
+ }
+#endif
+}
+
+//-------------------------------------
+
+void CAI_FollowManager::RemoveFollower( AI_FollowManagerInfoHandle_t& hInfo )
+{
+ if ( hInfo.m_pGroup && hInfo.m_hFollower )
+ {
+ AI_FollowGroup_t *pGroup = hInfo.m_pGroup;
+ AI_Follower_t* iterNode = &pGroup->followers[hInfo.m_hFollower];
+
+ int slot = iterNode->slot;
+ pGroup->slotUsage.Clear( slot );
+ pGroup->followers.Remove( hInfo.m_hFollower );
+ if ( pGroup->followers.Count() == 0 )
+ {
+ RemoveGroup( pGroup );
+ }
+ else
+ {
+ if ( pGroup->hFollowTarget != NULL ) // NULL on level unload
+ {
+ RedistributeSlots( pGroup );
+ }
+ }
+ }
+}
+
+//-------------------------------------
+
+int CAI_FollowManager::FindBestSlot( AI_FollowGroup_t *pGroup )
+{
+ // @TODO (toml 02-28-03): crude placeholder
+ int nSlots = pGroup->pFormation->nSlots;
+
+ int best = -1;
+ int bestPriority = -1;
+
+ for ( int i = 0; i < nSlots; i++ )
+ {
+ if ( !pGroup->slotUsage.IsBitSet( i ) && pGroup->pFormation->pSlots[i].priority > bestPriority )
+ {
+ bestPriority = pGroup->pFormation->pSlots[i].priority;
+ best = i;
+ }
+ }
+ return best;
+}
+
+//-------------------------------------
+
+void CAI_FollowManager::CalculateFieldsFromSlot( AI_FollowSlot_t *pSlot, AI_FollowNavInfo_t *pFollowerInfo )
+{
+ // @TODO (toml 02-28-03): placeholder. Force break if someone tries to actually use
+ Assert( pSlot->positionVariability == 0.0 );
+ //Assert( pSlot->tolerance == AIN_DEF_TOLERANCE );
+
+ pFollowerInfo->position = pSlot->position;
+ pFollowerInfo->range = random->RandomFloat( pSlot->rangeMin, pSlot->rangeMax );
+ pFollowerInfo->Zrange = pSlot->Zrange;
+ pFollowerInfo->tolerance = pSlot->tolerance;
+}
+
+//-------------------------------------
+
+AI_FollowGroup_t *CAI_FollowManager::FindCreateGroup( CBaseEntity *pTarget, AI_Formations_t formation )
+{
+ AI_FollowGroup_t *pGroup = FindGroup( pTarget );
+
+ if ( !pGroup )
+ {
+ {
+ MEM_ALLOC_CREDIT();
+ pGroup = new AI_FollowGroup_t;
+ }
+
+ pGroup->pFormation = AIGetFormation( formation );
+ pGroup->slotUsage.Resize( pGroup->pFormation->nSlots );
+ pGroup->hFollowTarget = pTarget;
+
+ m_groups.AddToHead( pGroup );
+ }
+
+ return pGroup;
+}
+
+//-------------------------------------
+
+void CAI_FollowManager::RemoveGroup( AI_FollowGroup_t *pGroup )
+{
+ for ( int i = 0; i < m_groups.Count(); i++ )
+ {
+ if ( m_groups[i] == pGroup )
+ {
+ delete m_groups[i];
+ m_groups.FastRemove(i);
+ return;
+ }
+ }
+}
+
+//-------------------------------------
+
+AI_FollowGroup_t *CAI_FollowManager::FindGroup( CBaseEntity *pTarget )
+{
+ for ( int i = 0; i < m_groups.Count(); i++ )
+ {
+ if ( m_groups[i]->hFollowTarget == pTarget )
+ return m_groups[i];
+ }
+ return NULL;
+}
+
+//-------------------------------------
+
+AI_FollowGroup_t *CAI_FollowManager::FindFollowerGroup( CBaseEntity *pFollower )
+{
+ for ( int i = 0; i < m_groups.Count(); i++ )
+ {
+ int h = m_groups[i]->followers.Head();
+ while( h != m_groups[i]->followers.InvalidIndex() )
+ {
+ AI_Follower_t *p = &m_groups[i]->followers[h];
+ if ( p->hFollower.Get() == pFollower )
+ return m_groups[i];
+ h = m_groups[i]->followers.Next( h );
+ }
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER(CAI_FollowBehavior)
+
+ DECLARE_TASK(TASK_CANT_FOLLOW)
+ DECLARE_TASK(TASK_FACE_FOLLOW_TARGET)
+ DECLARE_TASK(TASK_MOVE_TO_FOLLOW_POSITION)
+ DECLARE_TASK(TASK_GET_PATH_TO_FOLLOW_POSITION)
+ DECLARE_TASK(TASK_SET_FOLLOW_TARGET_MARK)
+ DECLARE_TASK(TASK_FOLLOWER_FACE_TACTICAL)
+ DECLARE_TASK(TASK_SET_FOLLOW_DELAY)
+ DECLARE_TASK(TASK_GET_PATH_TO_FOLLOW_POINT)
+ DECLARE_TASK(TASK_ARRIVE_AT_FOLLOW_POINT)
+ DECLARE_TASK(TASK_BEGIN_STAND_AT_WAIT_POINT)
+ DECLARE_TASK(TASK_SET_FOLLOW_POINT_STAND_SCHEDULE)
+
+ DECLARE_CONDITION(COND_TARGET_MOVED_FROM_MARK)
+ DECLARE_CONDITION(COND_FOUND_WAIT_POINT)
+ DECLARE_CONDITION(COND_FOLLOW_DELAY_EXPIRED)
+ DECLARE_CONDITION(COND_FOLLOW_TARGET_VISIBLE)
+ DECLARE_CONDITION(COND_FOLLOW_TARGET_NOT_VISIBLE)
+ DECLARE_CONDITION(COND_FOLLOW_WAIT_POINT_INVALID)
+ DECLARE_CONDITION(COND_FOLLOW_PLAYER_IS_LIT)
+ DECLARE_CONDITION(COND_FOLLOW_PLAYER_IS_NOT_LIT)
+
+ //=========================================================
+ // > SCHED_FOLLOWER_MOVE_AWAY_END
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FOLLOWER_MOVE_AWAY_END,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FOLLOWER_MOVE_AWAY_FAIL "
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_FOLLOW_TARGET 0"
+ " TASK_SET_FOLLOW_DELAY 2"
+ ""
+ " Interrupts"
+ " COND_PLAYER_PUSHING"
+ )
+
+ //=========================================================
+ // > SCHED_FOLLOWER_MOVE_AWAY_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FOLLOWER_MOVE_AWAY_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_FOLLOW_TARGET 0"
+ " TASK_SET_FOLLOW_DELAY 2"
+ ""
+ " Interrupts"
+ " COND_PLAYER_PUSHING"
+ )
+
+ //=========================================================
+ // > SCHED_FOLLOW
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FOLLOW,
+
+ " Tasks"
+ " TASK_GET_PATH_TO_FOLLOW_POSITION 0"
+ " TASK_MOVE_TO_FOLLOW_POSITION 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_TARGET_FACE "
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_PROVOKED"
+ " COND_PLAYER_PUSHING"
+ " COND_BETTER_WEAPON_AVAILABLE"
+ );
+
+ //=========================================================
+ // > SCHED_MOVE_TO_FACE_FOLLOW_TARGET
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_MOVE_TO_FACE_FOLLOW_TARGET,
+
+ " Tasks"
+// " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+// " TASK_FACE_FOLLOW_TARGET 0"
+// " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_FOLLOW"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_PROVOKED"
+ " COND_PLAYER_PUSHING"
+ )
+
+ //=========================================================
+ // > SCHED_FACE_FOLLOW_TARGET
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FACE_FOLLOW_TARGET,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_FOLLOW_TARGET 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_FOLLOWER_IDLE_STAND "
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_PROVOKED"
+ " COND_PLAYER_PUSHING"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_FOLLOWER_GO_TO_WAIT_POINT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FOLLOWER_GO_TO_WAIT_POINT,
+
+ " Tasks"
+ " TASK_LOCK_HINTNODE 0 " // this will fail the schedule if no hint node or not already lockable
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL"
+ " TASK_SET_TOLERANCE_DISTANCE 4"
+ " TASK_GET_PATH_TO_FOLLOW_POINT 0"
+ " TASK_SET_FOLLOW_TARGET_MARK 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_ARRIVE_AT_FOLLOW_POINT 0"
+ " TASK_SET_FOLLOW_POINT_STAND_SCHEDULE 0"
+
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_PROVOKED"
+ " COND_PLAYER_PUSHING"
+ " COND_TARGET_MOVED_FROM_MARK"
+ )
+
+ //=========================================================
+ // > SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FOLLOWER_GO_TO_WAIT_POINT_FAIL,
+
+ " Tasks"
+ " TASK_CLEAR_HINTNODE .5"
+ " TASK_SET_FOLLOW_DELAY 1"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_FOLLOWER_STAND_AT_WAIT_POINT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FOLLOWER_STAND_AT_WAIT_POINT,
+
+ " Tasks"
+ " TASK_BEGIN_STAND_AT_WAIT_POINT 0"
+ " TASK_PLAY_HINT_ACTIVITY 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_FOLLOWER_STAND_AT_WAIT_POINT "
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_PROVOKED"
+ " COND_PLAYER_PUSHING"
+ " COND_TARGET_MOVED_FROM_MARK"
+ " COND_GIVE_WAY"
+ " COND_FOLLOW_WAIT_POINT_INVALID"
+// " COND_IDLE_INTERRUPT"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_FOLLOWER_IDLE_STAND,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+// " TASK_SET_FOLLOW_TARGET_MARK 0"
+ " TASK_WAIT 2.5"
+ " TASK_FACE_FOLLOW_TARGET 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 3"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_FEAR"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_NO_PRIMARY_AMMO"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_SMELL"
+ " COND_PROVOKED"
+ " COND_GIVE_WAY"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_BULLET_IMPACT"
+ " COND_PLAYER_PUSHING"
+ " COND_TARGET_MOVED_FROM_MARK"
+ " COND_FOLLOW_DELAY_EXPIRED"
+ " COND_FOUND_WAIT_POINT"
+ " COND_IDLE_INTERRUPT"
+ " COND_BETTER_WEAPON_AVAILABLE"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_FOLLOWER_COMBAT_FACE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_FEAR"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_NO_PRIMARY_AMMO"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_SMELL"
+ " COND_PROVOKED"
+ " COND_GIVE_WAY"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_BULLET_IMPACT"
+ " COND_PLAYER_PUSHING"
+ " COND_TARGET_MOVED_FROM_MARK"
+ " COND_FOLLOW_DELAY_EXPIRED"
+ " COND_FOUND_WAIT_POINT"
+ " COND_BETTER_WEAPON_AVAILABLE"
+ )
+
+ AI_END_CUSTOM_SCHEDULE_PROVIDER()
+
+//=============================================================================
|