summaryrefslogtreecommitdiff
path: root/game/server/episodic/ai_behavior_alyx_injured.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/episodic/ai_behavior_alyx_injured.cpp')
-rw-r--r--game/server/episodic/ai_behavior_alyx_injured.cpp621
1 files changed, 621 insertions, 0 deletions
diff --git a/game/server/episodic/ai_behavior_alyx_injured.cpp b/game/server/episodic/ai_behavior_alyx_injured.cpp
new file mode 100644
index 0000000..a762587
--- /dev/null
+++ b/game/server/episodic/ai_behavior_alyx_injured.cpp
@@ -0,0 +1,621 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: FIXME: This will ultimately become a more generic implementation
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "ai_memory.h"
+#include "ai_speech.h"
+#include "ai_behavior.h"
+#include "ai_navigator.h"
+#include "ai_playerally.h"
+#include "ai_behavior_follow.h"
+#include "ai_moveprobe.h"
+
+#include "ai_behavior_alyx_injured.h"
+
+ConVar g_debug_injured_follow( "g_debug_injured_follow", "0" );
+ConVar injured_help_plee_range( "injured_help_plee_range", "256" );
+
+#define TLK_INJURED_FOLLOW_TOO_FAR "TLK_INJURED_FOLLOW_TOO_FAR"
+
+BEGIN_DATADESC( CAI_BehaviorAlyxInjured )
+ DEFINE_FIELD( m_flNextWarnTime, FIELD_TIME ),
+ // m_ActivityMap
+
+END_DATADESC();
+
+Activity ACT_INJURED_COWER;
+Activity ACT_GESTURE_INJURED_COWER_FLINCH;
+
+#define COVER_DISTANCE 128.0f // Distance behind target to find cover
+#define MIN_ENEMY_MOB 3 // Number of enemies considerd overwhelming
+#define MAX_DIST_FROM_FOLLOW_TARGET 256 // If the follow target is farther than this, the NPC will run to it
+
+//=============================================================================
+
+CAI_BehaviorAlyxInjured::CAI_BehaviorAlyxInjured( void ) : m_flNextWarnTime( 0.0f )
+{
+ SetDefLessFunc( m_ActivityMap );
+}
+
+struct ActivityMap_t
+{
+ Activity activity;
+ Activity translation;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::PopulateActivityMap( void )
+{
+ // Maps one activity to a translated one
+ ActivityMap_t map[] =
+ {
+ // Runs
+ { ACT_RUN, ACT_RUN_HURT },
+ { ACT_RUN_AIM, ACT_RUN_AIM }, // FIMXE: No appropriate temp anim right now!
+ { ACT_RUN_CROUCH, ACT_RUN_HURT },
+ { ACT_RUN_CROUCH_AIM, ACT_RUN_HURT },
+ { ACT_RUN_PROTECTED, ACT_RUN_HURT },
+ { ACT_RUN_RELAXED, ACT_RUN_HURT },
+ { ACT_RUN_STIMULATED, ACT_RUN_HURT },
+ { ACT_RUN_AGITATED, ACT_RUN_HURT },
+ { ACT_RUN_AIM_RELAXED, ACT_RUN_AIM_RELAXED }, // FIMXE: No appropriate temp anim right now!
+ { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_STIMULATED }, // FIMXE: No appropriate temp anim right now!
+ { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_AGITATED }, // FIMXE: No appropriate temp anim right now!
+ { ACT_RUN_HURT, ACT_RUN_HURT },
+
+ // Walks
+ { ACT_WALK, ACT_WALK_HURT },
+ { ACT_WALK_AIM, ACT_WALK_HURT },
+ { ACT_WALK_CROUCH, ACT_WALK_HURT },
+ { ACT_WALK_CROUCH_AIM, ACT_WALK_HURT },
+ { ACT_WALK_RELAXED, ACT_WALK_HURT },
+ { ACT_WALK_STIMULATED, ACT_WALK_HURT },
+ { ACT_WALK_AGITATED, ACT_WALK_HURT },
+ { ACT_WALK_AIM_RELAXED, ACT_WALK_HURT },
+ { ACT_WALK_AIM_STIMULATED, ACT_WALK_HURT },
+ { ACT_WALK_AIM_AGITATED, ACT_WALK_HURT },
+ { ACT_WALK_HURT, ACT_WALK_HURT },
+
+ { ACT_IDLE, ACT_IDLE_HURT },
+ { ACT_COVER_LOW, ACT_INJURED_COWER },
+ { ACT_COWER, ACT_INJURED_COWER },
+ };
+
+ // Clear the map
+ m_ActivityMap.RemoveAll();
+
+ // Add all translations
+ for ( int i = 0; i < ARRAYSIZE( map ); i++ )
+ {
+ Assert( m_ActivityMap.Find( map[i].activity ) == m_ActivityMap.InvalidIndex() );
+ m_ActivityMap.Insert( map[i].activity, map[i].translation );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Populate the list after save/load
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::OnRestore( void )
+{
+ PopulateActivityMap();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Populate the list on spawn
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::Spawn( void )
+{
+ PopulateActivityMap();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the flinch activity for us to play
+// Input : bHeavyDamage -
+// bGesture -
+// Output : Activity
+//-----------------------------------------------------------------------------
+Activity CAI_BehaviorAlyxInjured::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
+{
+ //
+ if ( ( bGesture == false ) || ( GetOuter()->GetActivity() != ACT_COWER ) )
+ return BaseClass::GetFlinchActivity( bHeavyDamage, bGesture );
+
+ // Translate the flinch if we're cowering
+ return ACT_GESTURE_INJURED_COWER_FLINCH;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : nActivity -
+//-----------------------------------------------------------------------------
+Activity CAI_BehaviorAlyxInjured::NPC_TranslateActivity( Activity nActivity )
+{
+ // Find out what the base class wants to do with the activity
+ Activity nNewActivity = BaseClass::NPC_TranslateActivity( nActivity );
+
+ // Look it up in the translation map
+ int nIndex = m_ActivityMap.Find( nNewActivity );
+
+ if ( m_ActivityMap.IsValidIndex( nIndex ) )
+ return m_ActivityMap[nIndex];
+
+ return nNewActivity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines if Alyx should run away from enemies or stay put
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::ShouldRunToCover( void )
+{
+ Vector vecRetreatPos;
+ float flRetreatRadius = 128.0f;
+
+ // See how far off from our cover position we are
+ if ( FindCoverFromEnemyBehindTarget( GetFollowTarget(), flRetreatRadius, &vecRetreatPos ) )
+ {
+ float flDestDistSqr = ( GetOuter()->WorldSpaceCenter() - vecRetreatPos ).LengthSqr();
+ if ( flDestDistSqr > Square( flRetreatRadius ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: See if we need to follow our goal
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::ShouldRunToFollowGoal( void )
+{
+ // If we're too far from our follow target, we need to chase after them
+ float flDistToFollowGoalSqr = ( GetOuter()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin() ).LengthSqr();
+ if ( flDistToFollowGoalSqr > Square(MAX_DIST_FROM_FOLLOW_TARGET) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Translate base schedules into overridden forms
+//-----------------------------------------------------------------------------
+int CAI_BehaviorAlyxInjured::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_RUN_FROM_ENEMY:
+ case SCHED_RUN_FROM_ENEMY_MOB:
+ {
+ // Get under cover if we're able to
+ if ( ShouldRunToCover() )
+ return SCHED_INJURED_RUN_FROM_ENEMY;
+
+ // Run to our follow goal if we're too far away from it
+ if ( ShouldRunToFollowGoal() )
+ return SCHED_FOLLOW;
+
+ // Cower if surrounded
+ if ( HasCondition( COND_INJURED_OVERWHELMED ) )
+ return SCHED_INJURED_COWER;
+
+ // Face our enemies
+ return SCHED_INJURED_FEAR_FACE;
+ }
+ break;
+
+ case SCHED_RUN_FROM_ENEMY_FALLBACK:
+ return SCHED_INJURED_COWER;
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pick up failure cases and handle them
+//-----------------------------------------------------------------------------
+int CAI_BehaviorAlyxInjured::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ // Failed schedules
+ switch( failedSchedule )
+ {
+ case SCHED_RUN_FROM_ENEMY:
+ case SCHED_RUN_FROM_ENEMY_MOB:
+ case SCHED_FOLLOW:
+ return SCHED_INJURED_COWER;
+ }
+
+ // Failed tasks
+ switch( failedTask )
+ {
+ case TASK_FIND_COVER_FROM_ENEMY:
+ case TASK_FIND_INJURED_COVER_FROM_ENEMY:
+
+ // Only cower if we're already near enough to our follow target
+ float flDistToFollowTargetSqr = ( GetOuter()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin() ).LengthSqr();
+ if ( flDistToFollowTargetSqr > Square( 256 ) )
+ return SCHED_FOLLOW;
+
+ return SCHED_INJURED_COWER;
+ break;
+ }
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the general direction enemies are coming towards us at
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::FindThreatDirection2D( const Vector &vecSource, Vector *vecOut )
+{
+ // Find the general direction our threat is coming from
+ bool bValid = false;
+ Vector vecScratch;
+ AIEnemiesIter_t iter;
+
+ // Iterate through all known enemies
+ for( AI_EnemyInfo_t *pMemory = GetOuter()->GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetOuter()->GetEnemies()->GetNext(&iter) )
+ {
+ if ( pMemory == NULL || pMemory->hEnemy == NULL )
+ continue;
+
+ vecScratch = ( vecSource - pMemory->hEnemy->WorldSpaceCenter() );
+ VectorNormalize( vecScratch );
+
+ (*vecOut) += vecScratch;
+ bValid = true;
+ }
+
+ // Find the general direction
+ (*vecOut).z = 0.0f;
+ VectorNormalize( (*vecOut) );
+ return bValid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a position that hides us from our threats while interposing the
+// target entity between us and the threat
+// Input : pTarget - entity to hide behind
+// flRadius - Radius around the target to search
+// *vecOut - position
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::FindCoverFromEnemyBehindTarget( CBaseEntity *pTarget, float flRadius, Vector *vecOut )
+{
+ if ( pTarget == NULL )
+ return false;
+
+ Vector vecTargetPos = pTarget->GetAbsOrigin();
+ Vector vecThreatDir = vec3_origin;
+
+ // Find our threat direction and base our cover on that
+ if ( FindThreatDirection2D( vecTargetPos, &vecThreatDir ) )
+ {
+ // Get a general location for taking cover
+ Vector vecTestPos = vecTargetPos + ( vecThreatDir * flRadius );
+
+ if ( g_debug_injured_follow.GetBool() )
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecTestPos, 8.0f, 255, 255, 0, 32, true, 2.0f );
+ }
+
+ // Make sure we never move towards our threat to get to cover!
+ Vector vecMoveDir = GetOuter()->GetAbsOrigin() - vecTestPos;
+ VectorNormalize( vecMoveDir );
+ float flDotToCover = DotProduct( vecMoveDir, vecThreatDir );
+ if ( flDotToCover > 0.0f )
+ {
+ if ( g_debug_injured_follow.GetBool() )
+ {
+ NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecTestPos, 8.0f, 255, 0, 0, 32, true, 2.0f );
+ }
+
+ return false;
+ }
+
+ AIMoveTrace_t moveTrace;
+ GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND,
+ GetOuter()->GetAbsOrigin(),
+ vecTestPos,
+ MASK_SOLID_BRUSHONLY,
+ NULL,
+ 0,
+ &moveTrace );
+
+ bool bWithinRangeToGoal = ( moveTrace.vEndPosition - vecTestPos ).Length2DSqr() < Square( GetOuter()->GetHullWidth() * 3.0f );
+ bool bCanStandAtGoal = GetOuter()->GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY );
+
+ if ( bWithinRangeToGoal == false || bCanStandAtGoal == false )
+ {
+ if ( g_debug_injured_follow.GetBool() )
+ {
+ NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), vecTestPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 0, 2.0f );
+ }
+
+ return false;
+ }
+
+ // Accept it
+ *vecOut = moveTrace.vEndPosition;
+
+ if ( g_debug_injured_follow.GetBool() )
+ {
+ NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), (*vecOut), GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 0, 255, 0, 0, 2.0f );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_FIND_COVER_FROM_ENEMY:
+ {
+ CBaseEntity *pLeader = GetFollowTarget();
+ if ( !pLeader )
+ {
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+ // Find a position behind our follow target
+ Vector coverPos = vec3_invalid;
+ if ( FindCoverFromEnemyBehindTarget( pLeader, COVER_DISTANCE, &coverPos ) )
+ {
+ AI_NavGoal_t goal( GOALTYPE_LOCATION, coverPos, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS );
+ GetOuter()->GetNavigator()->SetGoal( goal );
+ GetOuter()->m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
+ TaskComplete();
+ return;
+ }
+
+ // Couldn't find anything
+ TaskFail( FAIL_NO_COVER );
+ break;
+ }
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether or not Alyx is injured
+//-----------------------------------------------------------------------------
+bool CAI_BehaviorAlyxInjured::IsInjured( void ) const
+{
+ return IsAlyxInInjuredMode();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ // Always stomp over this
+ ClearCondition( COND_INJURED_TOO_FAR_FROM_PLAYER );
+ ClearCondition( COND_INJURED_OVERWHELMED );
+
+ // See if we're overwhelmed by foes
+ if ( NumKnownEnemiesInRadius( GetOuter()->GetAbsOrigin(), COVER_DISTANCE ) >= MIN_ENEMY_MOB )
+ {
+ SetCondition( COND_INJURED_OVERWHELMED );
+ }
+
+ // Determines whether we consider ourselves in danger
+ bool bInDanger = ( HasCondition( COND_LIGHT_DAMAGE ) ||
+ HasCondition( COND_HEAVY_DAMAGE ) ||
+ HasCondition( COND_INJURED_OVERWHELMED ) );
+
+ // See if we're too far away from the player and in danger
+ if ( AI_IsSinglePlayer() && bInDanger )
+ {
+ bool bWarnPlayer = false;
+
+ // This only works in single-player
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ if ( pPlayer != NULL )
+ {
+ // FIXME: This distance may need to be the length of the shortest walked path between the follower and the target
+
+ // Get our approximate distance to the player
+ float flDistToPlayer = UTIL_DistApprox2D( GetOuter()->GetAbsOrigin(), pPlayer->GetAbsOrigin() );
+ if ( flDistToPlayer > injured_help_plee_range.GetFloat() )
+ {
+ bWarnPlayer = true;
+ }
+ else if ( flDistToPlayer > (injured_help_plee_range.GetFloat()*0.5f) && HasCondition( COND_SEE_PLAYER ) == false )
+ {
+ // Cut our distance in half if we can't see the player
+ bWarnPlayer = true;
+ }
+ }
+
+ // Yell for help!
+ if ( bWarnPlayer )
+ {
+ // FIXME: This should be routed through the normal speaking code with a system to emit from the player's suit.
+
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ //float flPlayerDistSqr = ( GetOuter()->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr();
+
+ // If the player is too far away or we can't see him
+ //if ( HasCondition( COND_SEE_PLAYER ) == false || flPlayerDistSqr > Square( 128 ) )
+ {
+ if ( m_flNextWarnTime < gpGlobals->curtime )
+ {
+ pPlayer->EmitSound( "npc_alyx.injured_too_far" );
+ m_flNextWarnTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 5.0f );
+ }
+ }
+ /*
+ else
+ {
+ SpeakIfAllowed( TLK_INJURED_FOLLOW_TOO_FAR );
+ m_flNextWarnTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 5.0f );
+ }
+ */
+
+ SetCondition( COND_INJURED_TOO_FAR_FROM_PLAYER );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Speak a concept if we're able to
+//-----------------------------------------------------------------------------
+void CAI_BehaviorAlyxInjured::SpeakIfAllowed( AIConcept_t concept )
+{
+ CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
+ if ( pExpresser == NULL )
+ return;
+
+ // Must be able to speak the concept
+ if ( pExpresser->CanSpeakConcept( concept ) )
+ {
+ pExpresser->Speak( concept );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of known enemies within a radius to a point
+//-----------------------------------------------------------------------------
+int CAI_BehaviorAlyxInjured::NumKnownEnemiesInRadius( const Vector &vecSource, float flRadius )
+{
+ int nNumEnemies = 0;
+ float flRadiusSqr = Square( flRadius );
+
+ AIEnemiesIter_t iter;
+
+ // Iterate through all known enemies
+ for( AI_EnemyInfo_t *pMemory = GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetEnemies()->GetNext(&iter) )
+ {
+ if ( pMemory == NULL || pMemory->hEnemy == NULL )
+ continue;
+
+ // Must hate or fear them
+ if ( GetOuter()->IRelationType( pMemory->hEnemy ) != D_HT && GetOuter()->IRelationType( pMemory->hEnemy ) != D_FR )
+ continue;
+
+ // Count only the enemies I've seen recently
+ if ( gpGlobals->curtime - pMemory->timeLastSeen > 0.5f )
+ continue;
+
+ // Must be within the radius we've specified
+ float flEnemyDistSqr = ( vecSource - pMemory->hEnemy->GetAbsOrigin() ).Length2DSqr();
+ if ( flEnemyDistSqr < flRadiusSqr )
+ {
+ nNumEnemies++;
+ }
+ }
+
+ return nNumEnemies;
+}
+
+// ----------------------------------------------
+// Custom AI declarations
+// ----------------------------------------------
+
+AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_BehaviorAlyxInjured )
+{
+ DECLARE_ACTIVITY( ACT_GESTURE_INJURED_COWER_FLINCH )
+ DECLARE_ACTIVITY( ACT_INJURED_COWER )
+
+ DECLARE_CONDITION( COND_INJURED_TOO_FAR_FROM_PLAYER )
+ DECLARE_CONDITION( COND_INJURED_OVERWHELMED )
+
+ DECLARE_TASK( TASK_FIND_INJURED_COVER_FROM_ENEMY )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_INJURED_COWER,
+
+ " Tasks"
+ // TOOD: Announce cower
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_COWER"
+ " TASK_WAIT 2"
+ ""
+ " Interrupts"
+ " COND_GIVE_WAY"
+ " COND_PLAYER_PUSHING"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_INJURED_FEAR_FACE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // FIXME: Scared idle?
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_GIVE_WAY"
+ " COND_PLAYER_PUSHING"
+ );
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_INJURED_RUN_FROM_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_INJURED_COWER"
+ " TASK_STOP_MOVING 0"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ );
+
+ AI_END_CUSTOM_SCHEDULE_PROVIDER()
+}
+
+//-----------------------------------------------------------------------------
+// CAI_InjuredFollowGoal
+//-----------------------------------------------------------------------------
+
+BEGIN_DATADESC( CAI_InjuredFollowGoal )
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( ai_goal_injured_follow, CAI_InjuredFollowGoal );
+
+//-------------------------------------
+
+void CAI_InjuredFollowGoal::EnableGoal( CAI_BaseNPC *pAI )
+{
+ CAI_BehaviorAlyxInjured *pBehavior;
+ if ( !pAI->GetBehavior( &pBehavior ) )
+ return;
+
+ if ( GetGoalEntity() == NULL )
+ return;
+
+ pBehavior->SetFollowGoal( this );
+}
+
+//-------------------------------------
+
+void CAI_InjuredFollowGoal::DisableGoal( CAI_BaseNPC *pAI )
+{
+ CAI_BehaviorAlyxInjured *pBehavior;
+ if ( !pAI->GetBehavior( &pBehavior ) )
+ return;
+
+ pBehavior->ClearFollowGoal( this );
+}