aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/ai_behavior_lead.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/ai_behavior_lead.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/ai_behavior_lead.cpp')
-rw-r--r--mp/src/game/server/ai_behavior_lead.cpp3386
1 files changed, 1693 insertions, 1693 deletions
diff --git a/mp/src/game/server/ai_behavior_lead.cpp b/mp/src/game/server/ai_behavior_lead.cpp
index cc4e0aed..6a316c1e 100644
--- a/mp/src/game/server/ai_behavior_lead.cpp
+++ b/mp/src/game/server/ai_behavior_lead.cpp
@@ -1,1693 +1,1693 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-#undef strncpy // we use std::string below that needs a good strncpy define
-#undef sprintf // "
-#include "cbase.h"
-
-#include "ai_behavior_lead.h"
-
-#include "ai_goalentity.h"
-#include "ai_navigator.h"
-#include "ai_speech.h"
-#include "ai_senses.h"
-#include "ai_playerally.h"
-#include "ai_route.h"
-#include "ai_pathfinder.h"
-#include "sceneentity.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-// Minimum time between leader nags
-#define LEAD_NAG_TIME 3.0
-
-#define LEAD_MIN_RETRIEVEDIST_OFFSET 24
-
-//-----------------------------------------------------------------------------
-// class CAI_LeadBehavior
-//
-// Purpose:
-//
-//-----------------------------------------------------------------------------
-
-BEGIN_SIMPLE_DATADESC( AI_LeadArgs_t )
- // Only the flags needs saving
- DEFINE_FIELD( flags, FIELD_INTEGER ),
-
- //DEFINE_FIELD( pszGoal, FIELD_STRING ),
- //DEFINE_FIELD( pszWaitPoint, FIELD_STRING ),
- //DEFINE_FIELD( flWaitDistance, FIELD_FLOAT ),
- //DEFINE_FIELD( flLeadDistance, FIELD_FLOAT ),
- //DEFINE_FIELD( flRetrieveDistance, FIELD_FLOAT ),
- //DEFINE_FIELD( flSuccessDistance, FIELD_FLOAT ),
- //DEFINE_FIELD( bRun, FIELD_BOOLEAN ),
- //DEFINE_FIELD( bDontSpeakStart, FIELD_BOOLEAN ),
- //DEFINE_FIELD( bGagLeader, FIELD_BOOLEAN ),
-
- DEFINE_FIELD( iRetrievePlayer, FIELD_INTEGER ),
- DEFINE_FIELD( iRetrieveWaitForSpeak, FIELD_INTEGER ),
- DEFINE_FIELD( iComingBackWaitForSpeak, FIELD_INTEGER ),
- DEFINE_FIELD( bStopScenesWhenPlayerLost, FIELD_BOOLEAN ),
- DEFINE_FIELD( bLeadDuringCombat, FIELD_BOOLEAN ),
-
-END_DATADESC();
-
-
-BEGIN_DATADESC( CAI_LeadBehavior )
- DEFINE_EMBEDDED( m_args ),
- // m_pSink (reconnected on load)
- DEFINE_FIELD( m_hSinkImplementor, FIELD_EHANDLE ),
- DEFINE_FIELD( m_goal, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_goalyaw, FIELD_FLOAT ),
- DEFINE_FIELD( m_waitpoint, FIELD_POSITION_VECTOR ),
- DEFINE_FIELD( m_waitdistance, FIELD_FLOAT ),
- DEFINE_FIELD( m_leaddistance, FIELD_FLOAT ),
- DEFINE_FIELD( m_retrievedistance, FIELD_FLOAT ),
- DEFINE_FIELD( m_successdistance, FIELD_FLOAT ),
- DEFINE_FIELD( m_weaponname, FIELD_STRING ),
- DEFINE_FIELD( m_run, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_gagleader, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hasspokenstart, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hasspokenarrival, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_hasPausedScenes, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flSpeakNextNagTime, FIELD_TIME ),
- DEFINE_FIELD( m_flWeaponSafetyTimeOut, FIELD_TIME ),
- DEFINE_FIELD( m_flNextLeadIdle, FIELD_TIME ),
- DEFINE_FIELD( m_bInitialAheadTest, FIELD_BOOLEAN ),
- DEFINE_EMBEDDED( m_MoveMonitor ),
- DEFINE_EMBEDDED( m_LostTimer ),
- DEFINE_EMBEDDED( m_LostLOSTimer ),
-END_DATADESC();
-
-//-----------------------------------------------------------------------------
-
-
-void CAI_LeadBehavior::OnRestore()
-{
- CBaseEntity *pSinkImplementor = m_hSinkImplementor;
- if ( pSinkImplementor )
- {
- m_pSink = dynamic_cast<CAI_LeadBehaviorHandler *>(pSinkImplementor);
- if ( !m_pSink )
- {
- DevMsg( "Failed to reconnect to CAI_LeadBehaviorHandler\n" );
- m_hSinkImplementor = NULL;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Draw any text overlays
-// Input : Previous text offset from the top
-// Output : Current text offset from the top
-//-----------------------------------------------------------------------------
-int CAI_LeadBehavior::DrawDebugTextOverlays( int text_offset )
-{
- char tempstr[ 512 ];
- int offset;
-
- offset = BaseClass::DrawDebugTextOverlays( text_offset );
- if ( GetOuter()->m_debugOverlays & OVERLAY_TEXT_BIT )
- {
- if ( HasGoal() )
- {
- Q_snprintf( tempstr, sizeof(tempstr), "Goal: %s %s", m_args.pszGoal, VecToString( m_goal ) );
- GetOuter()->EntityText( offset, tempstr, 0 );
- offset++;
- }
- else
- {
- Q_snprintf( tempstr, sizeof(tempstr), "Goal: None" );
- GetOuter()->EntityText( offset, tempstr, 0 );
- offset++;
- }
- }
-
- return offset;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CAI_LeadBehavior::IsNavigationUrgent( void )
-{
-#if defined( HL2_DLL )
- if( HasGoal() && !hl2_episodic.GetBool() )
- {
- return (GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL);
- }
-#endif
- return BaseClass::IsNavigationUrgent();
-}
-
-//-------------------------------------
-
-void CAI_LeadBehavior::LeadPlayer( const AI_LeadArgs_t &leadArgs, CAI_LeadBehaviorHandler *pSink )
-{
-#ifndef CSTRIKE_DLL
- CAI_PlayerAlly *pOuter = dynamic_cast<CAI_PlayerAlly*>(GetOuter());
- if ( pOuter && AI_IsSinglePlayer() )
- {
- pOuter->SetSpeechTarget( UTIL_GetLocalPlayer() );
- }
-#endif
-
- if( SetGoal( leadArgs ) )
- {
- SetCondition( COND_PROVOKED );
- Connect( pSink );
- NotifyChangeBehaviorStatus();
- }
- else
- {
- DevMsg( "*** Warning! LeadPlayer() has a NULL Goal Ent\n" );
- }
-}
-
-//-------------------------------------
-
-void CAI_LeadBehavior::StopLeading( void )
-{
- ClearGoal();
- m_pSink = NULL;
- NotifyChangeBehaviorStatus();
-}
-
-//-------------------------------------
-
-bool CAI_LeadBehavior::CanSelectSchedule()
-{
- if ( !AI_GetSinglePlayer() || AI_GetSinglePlayer()->IsDead() )
- return false;
-
- bool fAttacked = ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) );
- bool fNonCombat = ( GetNpcState() == NPC_STATE_IDLE || GetNpcState() == NPC_STATE_ALERT );
-
- return ( !fAttacked && (fNonCombat || m_args.bLeadDuringCombat) && HasGoal() );
-}
-
-//-------------------------------------
-
-void CAI_LeadBehavior::BeginScheduleSelection()
-{
- SetTarget( AI_GetSinglePlayer() );
- CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
- if ( pExpresser )
- pExpresser->ClearSpokeConcept( TLK_LEAD_ARRIVAL );
-}
-
-//-------------------------------------
-
-bool CAI_LeadBehavior::SetGoal( const AI_LeadArgs_t &args )
-{
- CBaseEntity *pGoalEnt;
- pGoalEnt = gEntList.FindEntityByName( NULL, args.pszGoal );
-
- if ( !pGoalEnt )
- return false;
-
- m_args = args; // @Q (toml 08-13-02): need to copy string?
- m_goal = pGoalEnt->GetLocalOrigin();
- m_goalyaw = (args.flags & AILF_USE_GOAL_FACING) ? pGoalEnt->GetLocalAngles().y : -1;
- m_waitpoint = vec3_origin;
- m_waitdistance = args.flWaitDistance;
- m_leaddistance = args.flLeadDistance ? args.flLeadDistance : 64;
- m_retrievedistance = args.flRetrieveDistance ? args.flRetrieveDistance : (m_leaddistance + LEAD_MIN_RETRIEVEDIST_OFFSET);
- m_successdistance = args.flSuccessDistance ? args.flSuccessDistance : 0;
- m_run = args.bRun;
- m_gagleader = args.bGagLeader;
- m_hasspokenstart = args.bDontSpeakStart;
- m_hasspokenarrival = false;
- m_hasPausedScenes = false;
- m_flSpeakNextNagTime = 0;
- m_flWeaponSafetyTimeOut = 0;
- m_flNextLeadIdle = gpGlobals->curtime + 10;
- m_bInitialAheadTest = true;
-
- if ( args.pszWaitPoint && args.pszWaitPoint[0] )
- {
- CBaseEntity *pWaitPoint = gEntList.FindEntityByName( NULL, args.pszWaitPoint );
- if ( pWaitPoint )
- {
- m_waitpoint = pWaitPoint->GetLocalOrigin();
- }
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CAI_LeadBehavior::GetClosestPointOnRoute( const Vector &targetPos, Vector *pVecClosestPoint )
-{
- AI_Waypoint_t *waypoint = GetOuter()->GetNavigator()->GetPath()->GetCurWaypoint();
- AI_Waypoint_t *builtwaypoints = NULL;
- if ( !waypoint )
- {
- // We arrive here twice when lead behaviour starts:
- // - When the lead behaviour is first enabled. We have no schedule. We want to know if the player is ahead of us.
- // - A frame later when we've chosen to lead the player, but we still haven't built our route. We know that the
- // the player isn't lagging, so it's safe to go ahead and simply say he's ahead of us. This avoids building
- // the temp route twice.
- if ( IsCurSchedule( SCHED_LEAD_PLAYER, false ) )
- return true;
-
- // Build a temp route to the gold and use that
- builtwaypoints = GetOuter()->GetPathfinder()->BuildRoute( GetOuter()->GetAbsOrigin(), m_goal, NULL, GetOuter()->GetDefaultNavGoalTolerance(), GetOuter()->GetNavType(), true );
- if ( !builtwaypoints )
- return false;
-
- GetOuter()->GetPathfinder()->UnlockRouteNodes( builtwaypoints );
- waypoint = builtwaypoints;
- }
-
- // Find the nearest node to the target (going forward)
- float flNearestDist2D = 999999999;
- float flNearestDist = 999999999;
- float flPathDist, flPathDist2D;
-
- Vector vecNearestPoint;
- Vector vecPrevPos = GetOuter()->GetAbsOrigin();
- for ( ; (waypoint != NULL) ; waypoint = waypoint->GetNext() )
- {
- // Find the closest point on the line segment on the path
- Vector vecClosest;
- CalcClosestPointOnLineSegment( targetPos, vecPrevPos, waypoint->GetPos(), vecClosest );
- /*
- if ( builtwaypoints )
- {
- NDebugOverlay::Line( vecPrevPos, waypoint->GetPos(), 0,0,255,true, 10.0 );
- }
- */
- vecPrevPos = waypoint->GetPos();
-
- // Find the distance between this test point and our goal point
- flPathDist2D = vecClosest.AsVector2D().DistToSqr( targetPos.AsVector2D() );
- if ( flPathDist2D > flNearestDist2D )
- continue;
-
- flPathDist = vecClosest.z - targetPos.z;
- flPathDist *= flPathDist;
- flPathDist += flPathDist2D;
- if (( flPathDist2D == flNearestDist2D ) && ( flPathDist >= flNearestDist ))
- continue;
-
- flNearestDist2D = flPathDist2D;
- flNearestDist = flPathDist;
- vecNearestPoint = vecClosest;
- }
-
- if ( builtwaypoints )
- {
- //NDebugOverlay::Line( vecNearestPoint, targetPos, 0,255,0,true, 10.0 );
- DeleteAll( builtwaypoints );
- }
-
- *pVecClosestPoint = vecNearestPoint;
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return true if the player is further ahead on the lead route than I am
-//-----------------------------------------------------------------------------
-bool CAI_LeadBehavior::PlayerIsAheadOfMe( bool bForce )
-{
- // Find the nearest point on our route to the player, and see if that's further
- // ahead of us than our nearest point.
-
- // If we're not leading, our route doesn't lead to the goal, so we can't use it.
- // If we just started leading, go ahead and test, and we'll build a temp route.
- if ( !m_bInitialAheadTest && !IsCurSchedule( SCHED_LEAD_PLAYER, false ) && !bForce )
- return false;
-
- m_bInitialAheadTest = false;
-
- Vector vecClosestPoint;
- if ( GetClosestPointOnRoute( AI_GetSinglePlayer()->GetAbsOrigin(), &vecClosestPoint ) )
- {
- // If the closest point is not right next to me, then
- // the player is somewhere ahead of me on the route.
- if ( (vecClosestPoint - GetOuter()->GetAbsOrigin()).LengthSqr() > (32*32) )
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CAI_LeadBehavior::GatherConditions( void )
-{
- BaseClass::GatherConditions();
-
- if ( HasGoal() )
- {
- // Fix for bad transition case (to investigate)
- if ( ( WorldSpaceCenter() - m_goal ).LengthSqr() > (64*64) && IsCurSchedule( SCHED_LEAD_AWAIT_SUCCESS, false) )
- {
- GetOuter()->ClearSchedule( "Lead behavior - bad transition?" );
- }
-
- // We have to collect data about the person we're leading around.
- CBaseEntity *pFollower = AI_GetSinglePlayer();
-
- if( pFollower )
- {
- ClearCondition( COND_LEAD_FOLLOWER_VERY_CLOSE );
- ClearCondition( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME );
-
- // Check distance to the follower
- float flFollowerDist = ( WorldSpaceCenter() - pFollower->WorldSpaceCenter() ).Length();
- bool bLagging = flFollowerDist > (m_leaddistance*4);
- if ( bLagging )
- {
- if ( PlayerIsAheadOfMe() )
- {
- bLagging = false;
- }
- }
-
- // Player heading towards me?
- // Only factor this in if you're not too far from them
- if ( flFollowerDist < (m_leaddistance*4) )
- {
- Vector vecVelocity = pFollower->GetSmoothedVelocity();
- if ( VectorNormalize(vecVelocity) > 50 )
- {
- Vector vecToPlayer = (GetAbsOrigin() - pFollower->GetAbsOrigin());
- VectorNormalize( vecToPlayer );
- if ( DotProduct( vecVelocity, vecToPlayer ) > 0.5 )
- {
- SetCondition( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME );
- bLagging = false;
- }
- }
- }
-
- // If he's outside our lag range, consider him lagging
- if ( bLagging )
- {
- SetCondition( COND_LEAD_FOLLOWER_LAGGING );
- ClearCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );
- }
- else
- {
- ClearCondition( COND_LEAD_FOLLOWER_LAGGING );
- SetCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );
-
- // If he's really close, note that
- if ( flFollowerDist < m_leaddistance )
- {
- SetCondition( COND_LEAD_FOLLOWER_VERY_CLOSE );
- }
- }
-
- // To be considered not lagging, the follower must be visible, and within the lead distance
- if ( GetOuter()->FVisible( pFollower ) && GetOuter()->GetSenses()->ShouldSeeEntity( pFollower ) )
- {
- SetCondition( COND_LEAD_HAVE_FOLLOWER_LOS );
- m_LostLOSTimer.Stop();
- }
- else
- {
- ClearCondition( COND_LEAD_HAVE_FOLLOWER_LOS );
-
- // We don't have a LOS. But if we did have LOS, don't clear it until the timer is up.
- if ( m_LostLOSTimer.IsRunning() )
- {
- if ( m_LostLOSTimer.Expired() )
- {
- SetCondition( COND_LEAD_FOLLOWER_LAGGING );
- ClearCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );
- }
- }
- else
- {
- m_LostLOSTimer.Start();
- }
- }
-
- // Now we want to see if the follower is lost. Being lost means being (far away || out of LOS )
- // && some time has passed. Also, lagging players are considered lost if the NPC's never delivered
- // the start speech, because it means the NPC should run to the player to start the lead.
- if( HasCondition( COND_LEAD_FOLLOWER_LAGGING ) )
- {
- if ( !m_hasspokenstart )
- {
- SetCondition( COND_LEAD_FOLLOWER_LOST );
- }
- else
- {
- if ( m_args.bStopScenesWhenPlayerLost )
- {
- // Try and stop me speaking my monolog, if I am
- if ( !m_hasPausedScenes && IsRunningScriptedScene( GetOuter() ) )
- {
- //Msg("Stopping scenes.\n");
- PauseActorsScriptedScenes( GetOuter(), false );
- m_hasPausedScenes = true;
- }
- }
-
- if( m_LostTimer.IsRunning() )
- {
- if( m_LostTimer.Expired() )
- {
- SetCondition( COND_LEAD_FOLLOWER_LOST );
- }
- }
- else
- {
- m_LostTimer.Start();
- }
- }
- }
- else
- {
- // If I was speaking a monolog, resume it
- if ( m_args.bStopScenesWhenPlayerLost && m_hasPausedScenes )
- {
- if ( IsRunningScriptedScene( GetOuter() ) )
- {
- //Msg("Resuming scenes.\n");
- ResumeActorsScriptedScenes( GetOuter(), false );
- }
-
- m_hasPausedScenes = false;
- }
-
- m_LostTimer.Stop();
- ClearCondition( COND_LEAD_FOLLOWER_LOST );
- }
-
- // Evaluate for success
- // Success right now means being stationary, close to the goal, and having the player close by
- if ( !( m_args.flags & AILF_NO_DEF_SUCCESS ) )
- {
- ClearCondition( COND_LEAD_SUCCESS );
-
- // Check Z first, and only check 2d if we're within that
- bool bWithinZ = fabs(GetLocalOrigin().z - m_goal.z) < 64;
- if ( bWithinZ && (GetLocalOrigin() - m_goal).Length2D() <= 64 )
- {
- if ( HasCondition( COND_LEAD_FOLLOWER_VERY_CLOSE ) )
- {
- SetCondition( COND_LEAD_SUCCESS );
- }
- else if ( m_successdistance )
- {
- float flDistSqr = (pFollower->GetAbsOrigin() - GetLocalOrigin()).Length2DSqr();
- if ( flDistSqr < (m_successdistance*m_successdistance) )
- {
- SetCondition( COND_LEAD_SUCCESS );
- }
- }
- }
- }
- if ( m_MoveMonitor.IsMarkSet() && m_MoveMonitor.TargetMoved( pFollower ) )
- SetCondition( COND_LEAD_FOLLOWER_MOVED_FROM_MARK );
- else
- ClearCondition( COND_LEAD_FOLLOWER_MOVED_FROM_MARK );
- }
- }
-
- if( m_args.bLeadDuringCombat )
- {
- ClearCondition( COND_LIGHT_DAMAGE );
- ClearCondition( COND_HEAVY_DAMAGE );
- }
-}
-
-//-------------------------------------
-
-int CAI_LeadBehavior::SelectSchedule()
-{
- if ( HasGoal() )
- {
- if( HasCondition(COND_LEAD_SUCCESS) )
- {
- return SCHED_LEAD_SUCCEED;
- }
-
- // Player's here, but does he have the weapon we want him to have?
- if ( m_weaponname != NULL_STRING )
- {
- CBasePlayer *pFollower = AI_GetSinglePlayer();
- if ( pFollower && !pFollower->Weapon_OwnsThisType( STRING(m_weaponname) ) )
- {
- // If the safety timeout has run out, just give the player the weapon
- if ( !m_flWeaponSafetyTimeOut || (m_flWeaponSafetyTimeOut > gpGlobals->curtime) )
- return SCHED_LEAD_PLAYERNEEDSWEAPON;
-
- string_t iszItem = AllocPooledString( "weapon_bugbait" );
- pFollower->GiveNamedItem( STRING(iszItem) );
- }
- }
-
- // If we have a waitpoint, we want to wait at it for the player.
- if( HasWaitPoint() && !PlayerIsAheadOfMe( true ) )
- {
- bool bKeepWaiting = true;
-
- // If we have no wait distance, trigger as soon as the player comes in view
- if ( !m_waitdistance )
- {
- if ( HasCondition( COND_SEE_PLAYER ) )
- {
- // We've spotted the player, so stop waiting
- bKeepWaiting = false;
- }
- }
- else
- {
- // We have to collect data about the person we're leading around.
- CBaseEntity *pFollower = AI_GetSinglePlayer();
- if( pFollower )
- {
- float flFollowerDist = ( WorldSpaceCenter() - pFollower->WorldSpaceCenter() ).Length();
- if ( flFollowerDist < m_waitdistance )
- {
- bKeepWaiting = false;
- }
- }
- }
-
- // Player still not here?
- if ( bKeepWaiting )
- return SCHED_LEAD_WAITFORPLAYER;
-
- // We're finished waiting
- m_waitpoint = vec3_origin;
- Speak( TLK_LEAD_WAITOVER );
-
- // Don't speak the start line, because we've said
- m_hasspokenstart = true;
- return SCHED_WAIT_FOR_SPEAK_FINISH;
- }
-
- // If we haven't spoken our start speech, do that first
- if ( !m_hasspokenstart )
- {
- if ( HasCondition(COND_LEAD_HAVE_FOLLOWER_LOS) && HasCondition(COND_LEAD_FOLLOWER_VERY_CLOSE) )
- return SCHED_LEAD_SPEAK_START;
-
- // We haven't spoken to him, and we still need to. Go get him.
- return SCHED_LEAD_RETRIEVE;
- }
-
- if( HasCondition( COND_LEAD_FOLLOWER_LOST ) )
- {
- if( m_args.iRetrievePlayer )
- {
- // If not, we want to go get the player.
- DevMsg( GetOuter(), "Follower lost. Spoke COMING_BACK.\n");
-
- Speak( TLK_LEAD_COMINGBACK );
- m_MoveMonitor.ClearMark();
-
- // If we spoke something, wait for it to finish
- if ( m_args.iComingBackWaitForSpeak && IsSpeaking() )
- return SCHED_LEAD_SPEAK_THEN_RETRIEVE_PLAYER;
-
- return SCHED_LEAD_RETRIEVE;
- }
- else
- {
- // Just stay right here and wait.
- return SCHED_LEAD_WAITFORPLAYERIDLE;
- }
- }
-
- if( HasCondition( COND_LEAD_FOLLOWER_LAGGING ) )
- {
- DevMsg( GetOuter(), "Follower lagging. Spoke CATCHUP.\n");
-
- Speak( TLK_LEAD_CATCHUP );
- return SCHED_LEAD_PAUSE;
- }
- else
- {
- // If we're at the goal, wait for the player to get here
- if ( ( WorldSpaceCenter() - m_goal ).LengthSqr() < (64*64) )
- return SCHED_LEAD_AWAIT_SUCCESS;
-
- // If we were retrieving the player, speak the resume
- if ( IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) || IsCurSchedule( SCHED_LEAD_WAITFORPLAYERIDLE, false ) )
- {
- Speak( TLK_LEAD_RETRIEVE );
-
- // If we spoke something, wait for it to finish, if the mapmakers wants us to
- if ( m_args.iRetrieveWaitForSpeak && IsSpeaking() )
- return SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER;
- }
-
- DevMsg( GetOuter(), "Leading Follower.\n");
- return SCHED_LEAD_PLAYER;
- }
- }
- return BaseClass::SelectSchedule();
-}
-
-//-------------------------------------
-
-int CAI_LeadBehavior::TranslateSchedule( int scheduleType )
-{
- bool bInCombat = (m_args.bLeadDuringCombat && GetOuter()->GetState() == NPC_STATE_COMBAT);
- switch( scheduleType )
- {
- case SCHED_LEAD_PAUSE:
- if( bInCombat )
- return SCHED_LEAD_PAUSE_COMBAT;
- break;
- }
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-//-------------------------------------
-
-bool CAI_LeadBehavior::IsCurTaskContinuousMove()
-{
- const Task_t *pCurTask = GetCurTask();
- if ( pCurTask && pCurTask->iTask == TASK_LEAD_MOVE_TO_RANGE )
- return true;
- return BaseClass::IsCurTaskContinuousMove();
-}
-
-//-------------------------------------
-
-void CAI_LeadBehavior::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_LEAD_FACE_GOAL:
- {
- if ( m_goalyaw != -1 )
- {
- GetMotor()->SetIdealYaw( m_goalyaw );
- }
-
- TaskComplete();
- break;
- }
-
- case TASK_LEAD_SUCCEED:
- {
- Speak( TLK_LEAD_SUCCESS );
- NotifyEvent( LBE_SUCCESS );
-
- break;
- }
-
- case TASK_LEAD_ARRIVE:
- {
- // Only speak the first time we arrive
- if ( !m_hasspokenarrival )
- {
- Speak( TLK_LEAD_ARRIVAL );
- NotifyEvent( LBE_ARRIVAL );
-
- m_hasspokenarrival = true;
- }
- else
- {
- TaskComplete();
- }
-
- break;
- }
-
- case TASK_STOP_LEADING:
- {
- ClearGoal();
- TaskComplete();
- break;
- }
-
- case TASK_GET_PATH_TO_LEAD_GOAL:
- {
- if ( GetNavigator()->SetGoal( m_goal ) )
- {
- TaskComplete();
- }
- else
- {
- TaskFail("NO PATH");
- }
- break;
- }
-
- case TASK_LEAD_GET_PATH_TO_WAITPOINT:
- {
- if ( GetNavigator()->SetGoal( m_waitpoint ) )
- {
- TaskComplete();
- }
- else
- {
- TaskFail("NO PATH");
- }
- break;
- }
-
- case TASK_LEAD_WALK_PATH:
- {
- // If we're leading, and we're supposed to run, run instead of walking
- if ( m_run &&
- ( IsCurSchedule( SCHED_LEAD_WAITFORPLAYER, false ) || IsCurSchedule( SCHED_LEAD_PLAYER, false ) || IsCurSchedule( SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER, false )|| IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) ) )
- {
- ChainStartTask( TASK_RUN_PATH );
- }
- else
- {
- ChainStartTask( TASK_WALK_PATH );
- }
- break;
- }
-
- case TASK_LEAD_WAVE_TO_PLAYER:
- {
- // Wave to the player if we can see him. Otherwise, just idle.
- if ( HasCondition( COND_SEE_PLAYER ) )
- {
- Speak( TLK_LEAD_ATTRACTPLAYER );
- if ( HaveSequenceForActivity(ACT_SIGNAL1) )
- {
- SetActivity(ACT_SIGNAL1);
- }
- }
- else
- {
- SetActivity(ACT_IDLE);
- }
-
- TaskComplete();
- break;
- }
-
- case TASK_LEAD_PLAYER_NEEDS_WEAPON:
- {
- float flAvailableTime = GetOuter()->GetExpresser()->GetSemaphoreAvailableTime( GetOuter() );
-
- // if someone else is talking, don't speak
- if ( flAvailableTime <= gpGlobals->curtime )
- {
- Speak( TLK_LEAD_MISSINGWEAPON );
- }
-
- SetActivity(ACT_IDLE);
- TaskComplete();
- break;
- }
-
- case TASK_LEAD_SPEAK_START:
- {
- m_hasspokenstart = true;
-
- Speak( TLK_LEAD_START );
- SetActivity(ACT_IDLE);
- TaskComplete();
- break;
- }
-
- case TASK_LEAD_MOVE_TO_RANGE:
- {
- // If we haven't spoken our start speech, move closer
- if ( !m_hasspokenstart)
- {
- ChainStartTask( TASK_MOVE_TO_GOAL_RANGE, m_leaddistance - 24 );
- }
- else
- {
- ChainStartTask( TASK_MOVE_TO_GOAL_RANGE, m_retrievedistance );
- }
- break;
- }
-
- case TASK_LEAD_RETRIEVE_WAIT:
- {
- m_MoveMonitor.SetMark( AI_GetSinglePlayer(), 24 );
- ChainStartTask( TASK_WAIT_INDEFINITE );
- break;
- }
-
- case TASK_STOP_MOVING:
- {
- BaseClass::StartTask( pTask);
-
- if ( IsCurSchedule( SCHED_LEAD_PAUSE, false ) && pTask->flTaskData == 1 )
- {
- GetNavigator()->SetArrivalDirection( GetTarget() );
- }
- break;
- }
-
- case TASK_WAIT_FOR_SPEAK_FINISH:
- {
- BaseClass::StartTask( pTask);
-
- if( GetOuter()->GetState() == NPC_STATE_COMBAT )
- {
- // Don't stand around jabbering in combat.
- TaskComplete();
- }
-
- // If we're not supposed to wait for the player, don't wait for speech to finish.
- // Instead, just wait a wee tad, and then start moving. NPC will speak on the go.
- if ( TaskIsRunning() && !m_args.iRetrievePlayer )
- {
- if ( gpGlobals->curtime - GetOuter()->GetTimeTaskStarted() > 0.3 )
- {
- TaskComplete();
- }
- }
- break;
- }
-
- default:
- BaseClass::StartTask( pTask);
- }
-}
-
-//-------------------------------------
-
-void CAI_LeadBehavior::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_LEAD_SUCCEED:
- {
- if ( !IsSpeaking() )
- {
- TaskComplete();
- NotifyEvent( LBE_DONE );
- }
- break;
- }
- case TASK_LEAD_ARRIVE:
- {
- if ( !IsSpeaking() )
- {
- TaskComplete();
- NotifyEvent( LBE_ARRIVAL_DONE );
- }
- break;
- }
-
- case TASK_LEAD_MOVE_TO_RANGE:
- {
- // If we haven't spoken our start speech, move closer
- if ( !m_hasspokenstart)
- {
- ChainRunTask( TASK_MOVE_TO_GOAL_RANGE, m_leaddistance - 24 );
- }
- else
- {
- ChainRunTask( TASK_MOVE_TO_GOAL_RANGE, m_retrievedistance );
-
- if ( !TaskIsComplete() )
- {
- // Transition to a walk when we get near the player
- // Check Z first, and only check 2d if we're within that
- Vector vecGoalPos = GetNavigator()->GetGoalPos();
- float distance = fabs(vecGoalPos.z - GetLocalOrigin().z);
- bool bWithinZ = false;
- if ( distance < m_retrievedistance )
- {
- distance = ( vecGoalPos - GetLocalOrigin() ).Length2D();
- bWithinZ = true;
- }
-
- if ( distance > m_retrievedistance )
- {
- Activity followActivity = ACT_WALK;
- if ( GetOuter()->GetState() == NPC_STATE_COMBAT || ( (!bWithinZ || distance < (m_retrievedistance*4)) && GetOuter()->GetState() != NPC_STATE_COMBAT ) )
- {
- followActivity = ACT_RUN;
- }
-
- // Don't confuse move and shoot by resetting the activity every think
- Activity curActivity = GetNavigator()->GetMovementActivity();
- switch( curActivity )
- {
- case ACT_WALK_AIM: curActivity = ACT_WALK; break;
- case ACT_RUN_AIM: curActivity = ACT_RUN; break;
- }
-
- if ( curActivity != followActivity )
- {
- GetNavigator()->SetMovementActivity(followActivity);
- }
- GetNavigator()->SetArrivalDirection( GetOuter()->GetTarget() );
- }
- }
- }
- break;
- }
-
- case TASK_LEAD_RETRIEVE_WAIT:
- {
- ChainRunTask( TASK_WAIT_INDEFINITE );
- break;
- }
-
- case TASK_LEAD_WALK_PATH:
- {
- // If we're leading, and we're supposed to run, run instead of walking
- if ( m_run &&
- ( IsCurSchedule( SCHED_LEAD_WAITFORPLAYER, false ) || IsCurSchedule( SCHED_LEAD_PLAYER, false ) || IsCurSchedule( SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER, false )|| IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) ) )
- {
- ChainRunTask( TASK_RUN_PATH );
- }
- else
- {
- ChainRunTask( TASK_WALK_PATH );
- }
-
- // While we're walking
- if ( TaskIsRunning() && IsCurSchedule( SCHED_LEAD_PLAYER, false ) )
- {
- // If we're not speaking, and we haven't tried for a while, try to speak lead idle
- if ( m_flNextLeadIdle < gpGlobals->curtime && !IsSpeaking() )
- {
- m_flNextLeadIdle = gpGlobals->curtime + RandomFloat( 10,15 );
-
- if ( !m_args.iRetrievePlayer && HasCondition( COND_LEAD_FOLLOWER_LOST ) && HasCondition(COND_SEE_PLAYER) )
- {
- Speak( TLK_LEAD_COMINGBACK );
- }
- else
- {
- Speak( TLK_LEAD_IDLE );
- }
- }
- }
-
- break;
- }
-
- default:
- BaseClass::RunTask( pTask);
- }
-}
-
-//-------------------------------------
-
-bool CAI_LeadBehavior::Speak( AIConcept_t concept )
-{
- CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
- if ( !pExpresser )
- return false;
-
- // If the leader is gagged, don't speak any lead speech
- if ( m_gagleader )
- return false;
-
- // If we haven't said the start speech, don't nag
- bool bNag = ( FStrEq(concept,TLK_LEAD_COMINGBACK) || FStrEq(concept, TLK_LEAD_CATCHUP) || FStrEq(concept, TLK_LEAD_RETRIEVE) );
- if ( !m_hasspokenstart && bNag )
- return false;
-
- if ( hl2_episodic.GetBool() )
- {
- // If we're a player ally, only speak the concept if we're allowed to.
- // This allows the response rules to control it better (i.e. handles respeakdelay)
- // We ignore nag timers for this, because the response rules will control refire rates.
- CAI_PlayerAlly *pAlly = dynamic_cast<CAI_PlayerAlly*>(GetOuter());
- if ( pAlly )
- return pAlly->SpeakIfAllowed( concept, GetConceptModifiers( concept ) );
- }
-
- // Don't spam Nags
- if ( bNag )
- {
- if ( m_flSpeakNextNagTime > gpGlobals->curtime )
- {
- DevMsg( GetOuter(), "Leader didn't speak due to Nag timer.\n");
- return false;
- }
- }
-
- if ( pExpresser->Speak( concept, GetConceptModifiers( concept ) ) )
- {
- m_flSpeakNextNagTime = gpGlobals->curtime + LEAD_NAG_TIME;
- return true;
- }
-
- return false;
-}
-
-//-------------------------------------
-
-bool CAI_LeadBehavior::IsSpeaking()
-{
- CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
- if ( !pExpresser )
- return false;
-
- return pExpresser->IsSpeaking();
-}
-
-//-------------------------------------
-
-bool CAI_LeadBehavior::Connect( CAI_LeadBehaviorHandler *pSink )
-{
- m_pSink = pSink;
- m_hSinkImplementor = dynamic_cast<CBaseEntity *>(pSink);
-
- if ( m_hSinkImplementor == NULL )
- DevMsg( 2, "Note: CAI_LeadBehaviorHandler connected to a sink that isn't an entity. Manual fixup on load will be necessary\n" );
-
- return true;
-}
-
-//-------------------------------------
-
-bool CAI_LeadBehavior::Disconnect( CAI_LeadBehaviorHandler *pSink )
-{
- Assert( pSink == m_pSink );
- m_pSink = NULL;
- m_hSinkImplementor = NULL;
- return true;
-}
-
-//-------------------------------------
-
-AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_LeadBehavior )
-
- DECLARE_CONDITION( COND_LEAD_FOLLOWER_LOST )
- DECLARE_CONDITION( COND_LEAD_FOLLOWER_LAGGING )
- DECLARE_CONDITION( COND_LEAD_FOLLOWER_NOT_LAGGING )
- DECLARE_CONDITION( COND_LEAD_FOLLOWER_VERY_CLOSE )
- DECLARE_CONDITION( COND_LEAD_SUCCESS )
- DECLARE_CONDITION( COND_LEAD_HAVE_FOLLOWER_LOS )
- DECLARE_CONDITION( COND_LEAD_FOLLOWER_MOVED_FROM_MARK )
- DECLARE_CONDITION( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME )
-
- //---------------------------------
- //
- // Lead
- //
- DECLARE_TASK( TASK_GET_PATH_TO_LEAD_GOAL )
- DECLARE_TASK( TASK_STOP_LEADING )
- DECLARE_TASK( TASK_LEAD_ARRIVE )
- DECLARE_TASK( TASK_LEAD_SUCCEED )
- DECLARE_TASK( TASK_LEAD_FACE_GOAL )
- DECLARE_TASK( TASK_LEAD_GET_PATH_TO_WAITPOINT )
- DECLARE_TASK( TASK_LEAD_WAVE_TO_PLAYER )
- DECLARE_TASK( TASK_LEAD_PLAYER_NEEDS_WEAPON )
- DECLARE_TASK( TASK_LEAD_MOVE_TO_RANGE )
- DECLARE_TASK( TASK_LEAD_SPEAK_START )
- DECLARE_TASK( TASK_LEAD_RETRIEVE_WAIT )
- DECLARE_TASK( TASK_LEAD_WALK_PATH )
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_RETRIEVE,
-
- " Tasks"
- " TASK_GET_PATH_TO_PLAYER 0"
- " TASK_LEAD_MOVE_TO_RANGE 0"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_RETRIEVE_WAIT"
- " "
- " Interrupts"
- " COND_LEAD_FOLLOWER_VERY_CLOSE"
- " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //-------------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_SPEAK_THEN_RETRIEVE_PLAYER,
-
- " Tasks"
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_RETRIEVE"
- " "
- " Interrupts"
- " COND_LEAD_FOLLOWER_VERY_CLOSE"
- " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //-------------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_RETRIEVE_WAIT,
-
- " Tasks"
- " TASK_LEAD_RETRIEVE_WAIT 0"
- " "
- " Interrupts"
- " COND_LEAD_FOLLOWER_LOST"
- " COND_LEAD_FOLLOWER_LAGGING"
- " COND_LEAD_FOLLOWER_VERY_CLOSE"
- " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
- " COND_LEAD_FOLLOWER_MOVED_FROM_MARK"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_PLAYER,
-
- " Tasks"
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- " TASK_GET_PATH_TO_LEAD_GOAL 0"
- " TASK_LEAD_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- ""
- " Interrupts"
- " COND_LEAD_FOLLOWER_LOST"
- " COND_LEAD_FOLLOWER_LAGGING"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_AWAIT_SUCCESS,
-
- " Tasks"
- " TASK_LEAD_FACE_GOAL 0"
- " TASK_FACE_IDEAL 0"
- " TASK_LEAD_ARRIVE 0"
- " TASK_WAIT_INDEFINITE 0"
- ""
- " Interrupts"
- " COND_LEAD_FOLLOWER_LOST"
- " COND_LEAD_FOLLOWER_LAGGING"
- " COND_LEAD_SUCCESS"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_SUCCEED,
-
- " Tasks"
- " TASK_LEAD_SUCCEED 0"
- " TASK_STOP_LEADING 0"
- ""
- )
-
- //---------------------------------
- // This is the schedule Odell uses to pause the tour momentarily
- // if the player lags behind. If the player shows up in a
- // couple of seconds, the tour will resume. Otherwise, Odell
- // moves to retrieve.
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_PAUSE,
-
- " Tasks"
- " TASK_STOP_MOVING 1"
- " TASK_FACE_TARGET 0"
- " TASK_WAIT 5"
- " TASK_WAIT_RANDOM 5"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_RETRIEVE"
- ""
- " Interrupts"
- " COND_LEAD_FOLLOWER_VERY_CLOSE"
- " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
- " COND_LEAD_FOLLOWER_NOT_LAGGING"
- " COND_LEAD_FOLLOWER_LOST"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_PAUSE_COMBAT,
-
- " Tasks"
- " TASK_STOP_MOVING 1"
- " TASK_FACE_TARGET 0"
- " TASK_WAIT 1"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_RETRIEVE"
- ""
- " Interrupts"
- " COND_LEAD_FOLLOWER_VERY_CLOSE"
- " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
- " COND_LEAD_FOLLOWER_NOT_LAGGING"
- " COND_LEAD_FOLLOWER_LOST"
- " COND_HEAR_DANGER"
- )
-
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_WAITFORPLAYER,
-
- " Tasks"
- " TASK_LEAD_GET_PATH_TO_WAITPOINT 0"
- " TASK_LEAD_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT 0.5"
- " TASK_FACE_TARGET 0"
- " TASK_LEAD_WAVE_TO_PLAYER 0"
- " TASK_WAIT 5.0"
- " "
- " Interrupts"
- " COND_LEAD_FOLLOWER_VERY_CLOSE"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_WAITFORPLAYERIDLE,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT 0.5"
- " TASK_FACE_TARGET 0"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_IDLE"
- " TASK_WAIT 2"
- " "
- " Interrupts"
- " COND_LEAD_FOLLOWER_VERY_CLOSE"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_PLAYERNEEDSWEAPON,
-
- " Tasks"
- " TASK_FACE_PLAYER 0"
- " TASK_LEAD_PLAYER_NEEDS_WEAPON 0"
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- " TASK_WAIT 8"
- " "
- " Interrupts"
- )
-
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_SPEAK_START,
-
- " Tasks"
- " TASK_LEAD_SPEAK_START 0"
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- ""
- " Interrupts"
- )
-
- //---------------------------------
-
- DEFINE_SCHEDULE
- (
- SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_PLAYER"
- ""
- " Interrupts"
- " COND_LEAD_FOLLOWER_LOST"
- " COND_LEAD_FOLLOWER_LAGGING"
- " COND_LIGHT_DAMAGE"
- " COND_NEW_ENEMY"
- " COND_HEAR_DANGER"
- )
-
-AI_END_CUSTOM_SCHEDULE_PROVIDER()
-
-
-//-----------------------------------------------------------------------------
-//
-// Purpose: A level tool to control the lead behavior. Use is not required
-// in order to use behavior.
-//
-
-class CAI_LeadGoal : public CAI_GoalEntity,
- public CAI_LeadBehaviorHandler
-{
- DECLARE_CLASS( CAI_LeadGoal, CAI_GoalEntity );
-public:
- CAI_LeadGoal()
- : m_fArrived( false )
- {
- // These fields got added after existing levels shipped, so we set
- // the default values here in the constructor.
- m_iRetrievePlayer = 1;
- m_iRetrieveWaitForSpeak = 0;
- m_iComingBackWaitForSpeak = 0;
- m_bStopScenesWhenPlayerLost = false;
- m_bDontSpeakStart = false;
- m_bLeadDuringCombat = false;
- m_bGagLeader = false;
- }
-
- CAI_LeadBehavior *GetLeadBehavior();
-
- virtual const char *GetConceptModifiers( const char *pszConcept );
-
- virtual void InputActivate( inputdata_t &inputdata );
- virtual void InputDeactivate( inputdata_t &inputdata );
-
- DECLARE_DATADESC();
-private:
-
- virtual void OnEvent( int event );
- void InputSetSuccess( inputdata_t &inputdata );
- void InputSetFailure( inputdata_t &inputdata );
-
- bool m_fArrived; // @TODO (toml 08-16-02): move arrived tracking onto behavior
- float m_flWaitDistance;
- float m_flLeadDistance;
- float m_flRetrieveDistance;
- float m_flSuccessDistance;
- bool m_bRun;
- int m_iRetrievePlayer;
- int m_iRetrieveWaitForSpeak;
- int m_iComingBackWaitForSpeak;
- bool m_bStopScenesWhenPlayerLost;
- bool m_bDontSpeakStart;
- bool m_bLeadDuringCombat;
- bool m_bGagLeader;
-
- string_t m_iszWaitPointName;
-
- string_t m_iszStartConceptModifier;
- string_t m_iszAttractPlayerConceptModifier;
- string_t m_iszWaitOverConceptModifier;
- string_t m_iszArrivalConceptModifier;
- string_t m_iszPostArrivalConceptModifier;
- string_t m_iszSuccessConceptModifier;
- string_t m_iszFailureConceptModifier;
- string_t m_iszRetrieveConceptModifier;
- string_t m_iszComingBackConceptModifier;
-
- // Output handlers
- COutputEvent m_OnArrival;
- COutputEvent m_OnArrivalDone;
- COutputEvent m_OnSuccess;
- COutputEvent m_OnFailure;
- COutputEvent m_OnDone;
-};
-
-//-----------------------------------------------------------------------------
-//
-// CAI_LeadGoal implementation
-//
-
-LINK_ENTITY_TO_CLASS( ai_goal_lead, CAI_LeadGoal );
-
-BEGIN_DATADESC( CAI_LeadGoal )
-
- DEFINE_FIELD( m_fArrived, FIELD_BOOLEAN ),
-
- DEFINE_KEYFIELD(m_flWaitDistance, FIELD_FLOAT, "WaitDistance"),
- DEFINE_KEYFIELD(m_iszWaitPointName, FIELD_STRING, "WaitPointName"),
- DEFINE_KEYFIELD(m_flLeadDistance, FIELD_FLOAT, "LeadDistance"),
- DEFINE_KEYFIELD(m_flRetrieveDistance, FIELD_FLOAT, "RetrieveDistance"),
- DEFINE_KEYFIELD(m_flSuccessDistance, FIELD_FLOAT, "SuccessDistance"),
- DEFINE_KEYFIELD(m_bRun, FIELD_BOOLEAN, "Run"),
- DEFINE_KEYFIELD(m_iRetrievePlayer, FIELD_INTEGER, "Retrieve"),
- DEFINE_KEYFIELD(m_iRetrieveWaitForSpeak, FIELD_INTEGER, "RetrieveWaitForSpeak"),
- DEFINE_KEYFIELD(m_iComingBackWaitForSpeak, FIELD_INTEGER, "ComingBackWaitForSpeak"),
- DEFINE_KEYFIELD(m_bStopScenesWhenPlayerLost, FIELD_BOOLEAN, "StopScenes"),
- DEFINE_KEYFIELD(m_bDontSpeakStart, FIELD_BOOLEAN, "DontSpeakStart"),
- DEFINE_KEYFIELD(m_bLeadDuringCombat, FIELD_BOOLEAN, "LeadDuringCombat"),
- DEFINE_KEYFIELD(m_bGagLeader, FIELD_BOOLEAN, "GagLeader"),
-
- DEFINE_KEYFIELD(m_iszStartConceptModifier, FIELD_STRING, "StartConceptModifier"),
- DEFINE_KEYFIELD(m_iszAttractPlayerConceptModifier, FIELD_STRING, "AttractPlayerConceptModifier"),
- DEFINE_KEYFIELD(m_iszWaitOverConceptModifier, FIELD_STRING, "WaitOverConceptModifier"),
- DEFINE_KEYFIELD(m_iszArrivalConceptModifier, FIELD_STRING, "ArrivalConceptModifier"),
- DEFINE_KEYFIELD(m_iszPostArrivalConceptModifier, FIELD_STRING, "PostArrivalConceptModifier"),
- DEFINE_KEYFIELD(m_iszSuccessConceptModifier, FIELD_STRING, "SuccessConceptModifier"),
- DEFINE_KEYFIELD(m_iszFailureConceptModifier, FIELD_STRING, "FailureConceptModifier"),
- DEFINE_KEYFIELD(m_iszRetrieveConceptModifier, FIELD_STRING, "RetrieveConceptModifier"),
- DEFINE_KEYFIELD(m_iszComingBackConceptModifier, FIELD_STRING, "ComingBackConceptModifier"),
-
- DEFINE_OUTPUT( m_OnSuccess, "OnSuccess" ),
- DEFINE_OUTPUT( m_OnArrival, "OnArrival" ),
- DEFINE_OUTPUT( m_OnArrivalDone, "OnArrivalDone" ),
- DEFINE_OUTPUT( m_OnFailure, "OnFailure" ),
- DEFINE_OUTPUT( m_OnDone, "OnDone" ),
-
- // Inputs
- DEFINE_INPUTFUNC( FIELD_VOID, "SetSuccess", InputSetSuccess ),
- DEFINE_INPUTFUNC( FIELD_VOID, "SetFailure", InputSetFailure ),
-
-END_DATADESC()
-
-
-//-----------------------------------------------------------------------------
-
-CAI_LeadBehavior *CAI_LeadGoal::GetLeadBehavior()
-{
- CAI_BaseNPC *pActor = GetActor();
- if ( !pActor )
- return NULL;
-
- CAI_LeadBehavior *pBehavior;
- if ( !pActor->GetBehavior( &pBehavior ) )
- {
- return NULL;
- }
-
- return pBehavior;
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_LeadGoal::InputSetSuccess( inputdata_t &inputdata )
-{
- CAI_LeadBehavior *pBehavior = GetLeadBehavior();
- if ( !pBehavior )
- return;
-
- // @TODO (toml 02-14-03): Hackly!
- pBehavior->SetCondition( CAI_LeadBehavior::COND_LEAD_SUCCESS);
-}
-
-
-//-----------------------------------------------------------------------------
-
-void CAI_LeadGoal::InputSetFailure( inputdata_t &inputdata )
-{
- DevMsg( "SetFailure unimplemented\n" );
-}
-
-
-//-----------------------------------------------------------------------------
-
-void CAI_LeadGoal::InputActivate( inputdata_t &inputdata )
-{
- BaseClass::InputActivate( inputdata );
-
- CAI_LeadBehavior *pBehavior = GetLeadBehavior();
- if ( !pBehavior )
- {
- DevMsg( "Lead goal entity activated for an NPC that doesn't have the lead behavior\n" );
- return;
- }
-#ifdef HL2_EPISODIC
- if ( (m_flLeadDistance*4) < m_flRetrieveDistance )
- {
- Warning("ai_goal_lead '%s': lead distance (%.2f) * 4 is < retrieve distance (%.2f). This will make the NPC act stupid. Either reduce the retrieve distance, or increase the lead distance.\n", GetDebugName(), m_flLeadDistance, m_flRetrieveDistance );
- }
-#endif
-
- if ( m_flRetrieveDistance < (m_flLeadDistance + LEAD_MIN_RETRIEVEDIST_OFFSET) )
- {
-#ifdef HL2_EPISODIC
- Warning("ai_goal_lead '%s': retrieve distance (%.2f) < lead distance (%.2f) + %d. Retrieve distance should be at least %d greater than the lead distance, or NPC will ping-pong while retrieving.\n", GetDebugName(), m_flRetrieveDistance, m_flLeadDistance, LEAD_MIN_RETRIEVEDIST_OFFSET, LEAD_MIN_RETRIEVEDIST_OFFSET );
-#endif
- m_flRetrieveDistance = m_flLeadDistance + LEAD_MIN_RETRIEVEDIST_OFFSET;
- }
-
- AI_LeadArgs_t leadArgs = {
- GetGoalEntityName(),
- STRING(m_iszWaitPointName),
- m_spawnflags,
- m_flWaitDistance,
- m_flLeadDistance,
- m_flRetrieveDistance,
- m_flSuccessDistance,
- m_bRun,
- m_iRetrievePlayer,
- m_iRetrieveWaitForSpeak,
- m_iComingBackWaitForSpeak,
- m_bStopScenesWhenPlayerLost,
- m_bDontSpeakStart,
- m_bLeadDuringCombat,
- m_bGagLeader,
- };
-
- pBehavior->LeadPlayer( leadArgs, this );
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_LeadGoal::InputDeactivate( inputdata_t &inputdata )
-{
- BaseClass::InputDeactivate( inputdata );
-
- CAI_LeadBehavior *pBehavior = GetLeadBehavior();
- if ( !pBehavior )
- return;
-
- pBehavior->StopLeading();
-}
-
-//-----------------------------------------------------------------------------
-
-void CAI_LeadGoal::OnEvent( int event )
-{
- COutputEvent *pOutputEvent = NULL;
-
- switch ( event )
- {
- case LBE_ARRIVAL: pOutputEvent = &m_OnArrival; break;
- case LBE_ARRIVAL_DONE: pOutputEvent = &m_OnArrivalDone; break;
- case LBE_SUCCESS: pOutputEvent = &m_OnSuccess; break;
- case LBE_FAILURE: pOutputEvent = &m_OnFailure; break;
- case LBE_DONE: pOutputEvent = &m_OnDone; break;
- }
-
- // @TODO (toml 08-16-02): move arrived tracking onto behavior
- if ( event == LBE_ARRIVAL )
- m_fArrived = true;
-
- if ( pOutputEvent )
- pOutputEvent->FireOutput( this, this );
-}
-
-//-----------------------------------------------------------------------------
-
-const char *CAI_LeadGoal::GetConceptModifiers( const char *pszConcept )
-{
- if ( m_iszStartConceptModifier != NULL_STRING && *STRING(m_iszStartConceptModifier) && strcmp( pszConcept, TLK_LEAD_START) == 0 )
- return STRING( m_iszStartConceptModifier );
-
- if ( m_iszAttractPlayerConceptModifier != NULL_STRING && *STRING(m_iszAttractPlayerConceptModifier) && strcmp( pszConcept, TLK_LEAD_ATTRACTPLAYER) == 0 )
- return STRING( m_iszAttractPlayerConceptModifier );
-
- if ( m_iszWaitOverConceptModifier != NULL_STRING && *STRING(m_iszWaitOverConceptModifier) && strcmp( pszConcept, TLK_LEAD_WAITOVER) == 0 )
- return STRING( m_iszWaitOverConceptModifier );
-
- if ( m_iszArrivalConceptModifier != NULL_STRING && *STRING(m_iszArrivalConceptModifier) && strcmp( pszConcept, TLK_LEAD_ARRIVAL) == 0 )
- return STRING( m_iszArrivalConceptModifier );
-
- if ( m_iszSuccessConceptModifier != NULL_STRING && *STRING(m_iszSuccessConceptModifier) && strcmp( pszConcept, TLK_LEAD_SUCCESS) == 0 )
- return STRING( m_iszSuccessConceptModifier );
-
- if (m_iszFailureConceptModifier != NULL_STRING && *STRING(m_iszFailureConceptModifier) && strcmp( pszConcept, TLK_LEAD_FAILURE) == 0 )
- return STRING( m_iszFailureConceptModifier );
-
- if (m_iszRetrieveConceptModifier != NULL_STRING && *STRING(m_iszRetrieveConceptModifier) && strcmp( pszConcept, TLK_LEAD_RETRIEVE) == 0 )
- return STRING( m_iszRetrieveConceptModifier );
-
- if (m_iszComingBackConceptModifier != NULL_STRING && *STRING(m_iszComingBackConceptModifier) && strcmp( pszConcept, TLK_LEAD_COMINGBACK) == 0 )
- return STRING( m_iszComingBackConceptModifier );
-
- if ( m_fArrived && m_iszPostArrivalConceptModifier != NULL_STRING && *STRING(m_iszPostArrivalConceptModifier) )
- return STRING( m_iszPostArrivalConceptModifier );
-
- return NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-
-//-----------------------------------------------------------------------------
-//
-// Purpose: A custom lead goal that waits until the player has a weapon.
-//
-
-class CAI_LeadGoal_Weapon : public CAI_LeadGoal
-{
- DECLARE_CLASS( CAI_LeadGoal_Weapon, CAI_LeadGoal );
-public:
-
- virtual const char *GetConceptModifiers( const char *pszConcept );
- virtual void InputActivate( inputdata_t &inputdata );
-
-private:
- string_t m_iszWeaponName;
- string_t m_iszMissingWeaponConceptModifier;
-
- DECLARE_DATADESC();
-};
-
-//-----------------------------------------------------------------------------
-//
-// CAI_LeadGoal_Weapon implementation
-//
-
-LINK_ENTITY_TO_CLASS( ai_goal_lead_weapon, CAI_LeadGoal_Weapon );
-
-BEGIN_DATADESC( CAI_LeadGoal_Weapon )
-
- DEFINE_KEYFIELD( m_iszWeaponName, FIELD_STRING, "WeaponName"),
- DEFINE_KEYFIELD( m_iszMissingWeaponConceptModifier, FIELD_STRING, "MissingWeaponConceptModifier"),
-
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-
-const char *CAI_LeadGoal_Weapon::GetConceptModifiers( const char *pszConcept )
-{
- if ( m_iszMissingWeaponConceptModifier != NULL_STRING && *STRING(m_iszMissingWeaponConceptModifier) && strcmp( pszConcept, TLK_LEAD_MISSINGWEAPON) == 0 )
- return STRING( m_iszMissingWeaponConceptModifier );
-
- return BaseClass::GetConceptModifiers( pszConcept );
-}
-
-
-//-----------------------------------------------------------------------------
-
-void CAI_LeadGoal_Weapon::InputActivate( inputdata_t &inputdata )
-{
- BaseClass::InputActivate( inputdata );
-
- CAI_LeadBehavior *pBehavior = GetLeadBehavior();
- if ( pBehavior )
- {
- pBehavior->SetWaitForWeapon( m_iszWeaponName );
- }
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#undef strncpy // we use std::string below that needs a good strncpy define
+#undef sprintf // "
+#include "cbase.h"
+
+#include "ai_behavior_lead.h"
+
+#include "ai_goalentity.h"
+#include "ai_navigator.h"
+#include "ai_speech.h"
+#include "ai_senses.h"
+#include "ai_playerally.h"
+#include "ai_route.h"
+#include "ai_pathfinder.h"
+#include "sceneentity.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// Minimum time between leader nags
+#define LEAD_NAG_TIME 3.0
+
+#define LEAD_MIN_RETRIEVEDIST_OFFSET 24
+
+//-----------------------------------------------------------------------------
+// class CAI_LeadBehavior
+//
+// Purpose:
+//
+//-----------------------------------------------------------------------------
+
+BEGIN_SIMPLE_DATADESC( AI_LeadArgs_t )
+ // Only the flags needs saving
+ DEFINE_FIELD( flags, FIELD_INTEGER ),
+
+ //DEFINE_FIELD( pszGoal, FIELD_STRING ),
+ //DEFINE_FIELD( pszWaitPoint, FIELD_STRING ),
+ //DEFINE_FIELD( flWaitDistance, FIELD_FLOAT ),
+ //DEFINE_FIELD( flLeadDistance, FIELD_FLOAT ),
+ //DEFINE_FIELD( flRetrieveDistance, FIELD_FLOAT ),
+ //DEFINE_FIELD( flSuccessDistance, FIELD_FLOAT ),
+ //DEFINE_FIELD( bRun, FIELD_BOOLEAN ),
+ //DEFINE_FIELD( bDontSpeakStart, FIELD_BOOLEAN ),
+ //DEFINE_FIELD( bGagLeader, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( iRetrievePlayer, FIELD_INTEGER ),
+ DEFINE_FIELD( iRetrieveWaitForSpeak, FIELD_INTEGER ),
+ DEFINE_FIELD( iComingBackWaitForSpeak, FIELD_INTEGER ),
+ DEFINE_FIELD( bStopScenesWhenPlayerLost, FIELD_BOOLEAN ),
+ DEFINE_FIELD( bLeadDuringCombat, FIELD_BOOLEAN ),
+
+END_DATADESC();
+
+
+BEGIN_DATADESC( CAI_LeadBehavior )
+ DEFINE_EMBEDDED( m_args ),
+ // m_pSink (reconnected on load)
+ DEFINE_FIELD( m_hSinkImplementor, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_goal, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_goalyaw, FIELD_FLOAT ),
+ DEFINE_FIELD( m_waitpoint, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_waitdistance, FIELD_FLOAT ),
+ DEFINE_FIELD( m_leaddistance, FIELD_FLOAT ),
+ DEFINE_FIELD( m_retrievedistance, FIELD_FLOAT ),
+ DEFINE_FIELD( m_successdistance, FIELD_FLOAT ),
+ DEFINE_FIELD( m_weaponname, FIELD_STRING ),
+ DEFINE_FIELD( m_run, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_gagleader, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hasspokenstart, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hasspokenarrival, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_hasPausedScenes, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flSpeakNextNagTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flWeaponSafetyTimeOut, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextLeadIdle, FIELD_TIME ),
+ DEFINE_FIELD( m_bInitialAheadTest, FIELD_BOOLEAN ),
+ DEFINE_EMBEDDED( m_MoveMonitor ),
+ DEFINE_EMBEDDED( m_LostTimer ),
+ DEFINE_EMBEDDED( m_LostLOSTimer ),
+END_DATADESC();
+
+//-----------------------------------------------------------------------------
+
+
+void CAI_LeadBehavior::OnRestore()
+{
+ CBaseEntity *pSinkImplementor = m_hSinkImplementor;
+ if ( pSinkImplementor )
+ {
+ m_pSink = dynamic_cast<CAI_LeadBehaviorHandler *>(pSinkImplementor);
+ if ( !m_pSink )
+ {
+ DevMsg( "Failed to reconnect to CAI_LeadBehaviorHandler\n" );
+ m_hSinkImplementor = NULL;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw any text overlays
+// Input : Previous text offset from the top
+// Output : Current text offset from the top
+//-----------------------------------------------------------------------------
+int CAI_LeadBehavior::DrawDebugTextOverlays( int text_offset )
+{
+ char tempstr[ 512 ];
+ int offset;
+
+ offset = BaseClass::DrawDebugTextOverlays( text_offset );
+ if ( GetOuter()->m_debugOverlays & OVERLAY_TEXT_BIT )
+ {
+ if ( HasGoal() )
+ {
+ Q_snprintf( tempstr, sizeof(tempstr), "Goal: %s %s", m_args.pszGoal, VecToString( m_goal ) );
+ GetOuter()->EntityText( offset, tempstr, 0 );
+ offset++;
+ }
+ else
+ {
+ Q_snprintf( tempstr, sizeof(tempstr), "Goal: None" );
+ GetOuter()->EntityText( offset, tempstr, 0 );
+ offset++;
+ }
+ }
+
+ return offset;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CAI_LeadBehavior::IsNavigationUrgent( void )
+{
+#if defined( HL2_DLL )
+ if( HasGoal() && !hl2_episodic.GetBool() )
+ {
+ return (GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL);
+ }
+#endif
+ return BaseClass::IsNavigationUrgent();
+}
+
+//-------------------------------------
+
+void CAI_LeadBehavior::LeadPlayer( const AI_LeadArgs_t &leadArgs, CAI_LeadBehaviorHandler *pSink )
+{
+#ifndef CSTRIKE_DLL
+ CAI_PlayerAlly *pOuter = dynamic_cast<CAI_PlayerAlly*>(GetOuter());
+ if ( pOuter && AI_IsSinglePlayer() )
+ {
+ pOuter->SetSpeechTarget( UTIL_GetLocalPlayer() );
+ }
+#endif
+
+ if( SetGoal( leadArgs ) )
+ {
+ SetCondition( COND_PROVOKED );
+ Connect( pSink );
+ NotifyChangeBehaviorStatus();
+ }
+ else
+ {
+ DevMsg( "*** Warning! LeadPlayer() has a NULL Goal Ent\n" );
+ }
+}
+
+//-------------------------------------
+
+void CAI_LeadBehavior::StopLeading( void )
+{
+ ClearGoal();
+ m_pSink = NULL;
+ NotifyChangeBehaviorStatus();
+}
+
+//-------------------------------------
+
+bool CAI_LeadBehavior::CanSelectSchedule()
+{
+ if ( !AI_GetSinglePlayer() || AI_GetSinglePlayer()->IsDead() )
+ return false;
+
+ bool fAttacked = ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) );
+ bool fNonCombat = ( GetNpcState() == NPC_STATE_IDLE || GetNpcState() == NPC_STATE_ALERT );
+
+ return ( !fAttacked && (fNonCombat || m_args.bLeadDuringCombat) && HasGoal() );
+}
+
+//-------------------------------------
+
+void CAI_LeadBehavior::BeginScheduleSelection()
+{
+ SetTarget( AI_GetSinglePlayer() );
+ CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
+ if ( pExpresser )
+ pExpresser->ClearSpokeConcept( TLK_LEAD_ARRIVAL );
+}
+
+//-------------------------------------
+
+bool CAI_LeadBehavior::SetGoal( const AI_LeadArgs_t &args )
+{
+ CBaseEntity *pGoalEnt;
+ pGoalEnt = gEntList.FindEntityByName( NULL, args.pszGoal );
+
+ if ( !pGoalEnt )
+ return false;
+
+ m_args = args; // @Q (toml 08-13-02): need to copy string?
+ m_goal = pGoalEnt->GetLocalOrigin();
+ m_goalyaw = (args.flags & AILF_USE_GOAL_FACING) ? pGoalEnt->GetLocalAngles().y : -1;
+ m_waitpoint = vec3_origin;
+ m_waitdistance = args.flWaitDistance;
+ m_leaddistance = args.flLeadDistance ? args.flLeadDistance : 64;
+ m_retrievedistance = args.flRetrieveDistance ? args.flRetrieveDistance : (m_leaddistance + LEAD_MIN_RETRIEVEDIST_OFFSET);
+ m_successdistance = args.flSuccessDistance ? args.flSuccessDistance : 0;
+ m_run = args.bRun;
+ m_gagleader = args.bGagLeader;
+ m_hasspokenstart = args.bDontSpeakStart;
+ m_hasspokenarrival = false;
+ m_hasPausedScenes = false;
+ m_flSpeakNextNagTime = 0;
+ m_flWeaponSafetyTimeOut = 0;
+ m_flNextLeadIdle = gpGlobals->curtime + 10;
+ m_bInitialAheadTest = true;
+
+ if ( args.pszWaitPoint && args.pszWaitPoint[0] )
+ {
+ CBaseEntity *pWaitPoint = gEntList.FindEntityByName( NULL, args.pszWaitPoint );
+ if ( pWaitPoint )
+ {
+ m_waitpoint = pWaitPoint->GetLocalOrigin();
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAI_LeadBehavior::GetClosestPointOnRoute( const Vector &targetPos, Vector *pVecClosestPoint )
+{
+ AI_Waypoint_t *waypoint = GetOuter()->GetNavigator()->GetPath()->GetCurWaypoint();
+ AI_Waypoint_t *builtwaypoints = NULL;
+ if ( !waypoint )
+ {
+ // We arrive here twice when lead behaviour starts:
+ // - When the lead behaviour is first enabled. We have no schedule. We want to know if the player is ahead of us.
+ // - A frame later when we've chosen to lead the player, but we still haven't built our route. We know that the
+ // the player isn't lagging, so it's safe to go ahead and simply say he's ahead of us. This avoids building
+ // the temp route twice.
+ if ( IsCurSchedule( SCHED_LEAD_PLAYER, false ) )
+ return true;
+
+ // Build a temp route to the gold and use that
+ builtwaypoints = GetOuter()->GetPathfinder()->BuildRoute( GetOuter()->GetAbsOrigin(), m_goal, NULL, GetOuter()->GetDefaultNavGoalTolerance(), GetOuter()->GetNavType(), true );
+ if ( !builtwaypoints )
+ return false;
+
+ GetOuter()->GetPathfinder()->UnlockRouteNodes( builtwaypoints );
+ waypoint = builtwaypoints;
+ }
+
+ // Find the nearest node to the target (going forward)
+ float flNearestDist2D = 999999999;
+ float flNearestDist = 999999999;
+ float flPathDist, flPathDist2D;
+
+ Vector vecNearestPoint;
+ Vector vecPrevPos = GetOuter()->GetAbsOrigin();
+ for ( ; (waypoint != NULL) ; waypoint = waypoint->GetNext() )
+ {
+ // Find the closest point on the line segment on the path
+ Vector vecClosest;
+ CalcClosestPointOnLineSegment( targetPos, vecPrevPos, waypoint->GetPos(), vecClosest );
+ /*
+ if ( builtwaypoints )
+ {
+ NDebugOverlay::Line( vecPrevPos, waypoint->GetPos(), 0,0,255,true, 10.0 );
+ }
+ */
+ vecPrevPos = waypoint->GetPos();
+
+ // Find the distance between this test point and our goal point
+ flPathDist2D = vecClosest.AsVector2D().DistToSqr( targetPos.AsVector2D() );
+ if ( flPathDist2D > flNearestDist2D )
+ continue;
+
+ flPathDist = vecClosest.z - targetPos.z;
+ flPathDist *= flPathDist;
+ flPathDist += flPathDist2D;
+ if (( flPathDist2D == flNearestDist2D ) && ( flPathDist >= flNearestDist ))
+ continue;
+
+ flNearestDist2D = flPathDist2D;
+ flNearestDist = flPathDist;
+ vecNearestPoint = vecClosest;
+ }
+
+ if ( builtwaypoints )
+ {
+ //NDebugOverlay::Line( vecNearestPoint, targetPos, 0,255,0,true, 10.0 );
+ DeleteAll( builtwaypoints );
+ }
+
+ *pVecClosestPoint = vecNearestPoint;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the player is further ahead on the lead route than I am
+//-----------------------------------------------------------------------------
+bool CAI_LeadBehavior::PlayerIsAheadOfMe( bool bForce )
+{
+ // Find the nearest point on our route to the player, and see if that's further
+ // ahead of us than our nearest point.
+
+ // If we're not leading, our route doesn't lead to the goal, so we can't use it.
+ // If we just started leading, go ahead and test, and we'll build a temp route.
+ if ( !m_bInitialAheadTest && !IsCurSchedule( SCHED_LEAD_PLAYER, false ) && !bForce )
+ return false;
+
+ m_bInitialAheadTest = false;
+
+ Vector vecClosestPoint;
+ if ( GetClosestPointOnRoute( AI_GetSinglePlayer()->GetAbsOrigin(), &vecClosestPoint ) )
+ {
+ // If the closest point is not right next to me, then
+ // the player is somewhere ahead of me on the route.
+ if ( (vecClosestPoint - GetOuter()->GetAbsOrigin()).LengthSqr() > (32*32) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAI_LeadBehavior::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ if ( HasGoal() )
+ {
+ // Fix for bad transition case (to investigate)
+ if ( ( WorldSpaceCenter() - m_goal ).LengthSqr() > (64*64) && IsCurSchedule( SCHED_LEAD_AWAIT_SUCCESS, false) )
+ {
+ GetOuter()->ClearSchedule( "Lead behavior - bad transition?" );
+ }
+
+ // We have to collect data about the person we're leading around.
+ CBaseEntity *pFollower = AI_GetSinglePlayer();
+
+ if( pFollower )
+ {
+ ClearCondition( COND_LEAD_FOLLOWER_VERY_CLOSE );
+ ClearCondition( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME );
+
+ // Check distance to the follower
+ float flFollowerDist = ( WorldSpaceCenter() - pFollower->WorldSpaceCenter() ).Length();
+ bool bLagging = flFollowerDist > (m_leaddistance*4);
+ if ( bLagging )
+ {
+ if ( PlayerIsAheadOfMe() )
+ {
+ bLagging = false;
+ }
+ }
+
+ // Player heading towards me?
+ // Only factor this in if you're not too far from them
+ if ( flFollowerDist < (m_leaddistance*4) )
+ {
+ Vector vecVelocity = pFollower->GetSmoothedVelocity();
+ if ( VectorNormalize(vecVelocity) > 50 )
+ {
+ Vector vecToPlayer = (GetAbsOrigin() - pFollower->GetAbsOrigin());
+ VectorNormalize( vecToPlayer );
+ if ( DotProduct( vecVelocity, vecToPlayer ) > 0.5 )
+ {
+ SetCondition( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME );
+ bLagging = false;
+ }
+ }
+ }
+
+ // If he's outside our lag range, consider him lagging
+ if ( bLagging )
+ {
+ SetCondition( COND_LEAD_FOLLOWER_LAGGING );
+ ClearCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );
+ }
+ else
+ {
+ ClearCondition( COND_LEAD_FOLLOWER_LAGGING );
+ SetCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );
+
+ // If he's really close, note that
+ if ( flFollowerDist < m_leaddistance )
+ {
+ SetCondition( COND_LEAD_FOLLOWER_VERY_CLOSE );
+ }
+ }
+
+ // To be considered not lagging, the follower must be visible, and within the lead distance
+ if ( GetOuter()->FVisible( pFollower ) && GetOuter()->GetSenses()->ShouldSeeEntity( pFollower ) )
+ {
+ SetCondition( COND_LEAD_HAVE_FOLLOWER_LOS );
+ m_LostLOSTimer.Stop();
+ }
+ else
+ {
+ ClearCondition( COND_LEAD_HAVE_FOLLOWER_LOS );
+
+ // We don't have a LOS. But if we did have LOS, don't clear it until the timer is up.
+ if ( m_LostLOSTimer.IsRunning() )
+ {
+ if ( m_LostLOSTimer.Expired() )
+ {
+ SetCondition( COND_LEAD_FOLLOWER_LAGGING );
+ ClearCondition( COND_LEAD_FOLLOWER_NOT_LAGGING );
+ }
+ }
+ else
+ {
+ m_LostLOSTimer.Start();
+ }
+ }
+
+ // Now we want to see if the follower is lost. Being lost means being (far away || out of LOS )
+ // && some time has passed. Also, lagging players are considered lost if the NPC's never delivered
+ // the start speech, because it means the NPC should run to the player to start the lead.
+ if( HasCondition( COND_LEAD_FOLLOWER_LAGGING ) )
+ {
+ if ( !m_hasspokenstart )
+ {
+ SetCondition( COND_LEAD_FOLLOWER_LOST );
+ }
+ else
+ {
+ if ( m_args.bStopScenesWhenPlayerLost )
+ {
+ // Try and stop me speaking my monolog, if I am
+ if ( !m_hasPausedScenes && IsRunningScriptedScene( GetOuter() ) )
+ {
+ //Msg("Stopping scenes.\n");
+ PauseActorsScriptedScenes( GetOuter(), false );
+ m_hasPausedScenes = true;
+ }
+ }
+
+ if( m_LostTimer.IsRunning() )
+ {
+ if( m_LostTimer.Expired() )
+ {
+ SetCondition( COND_LEAD_FOLLOWER_LOST );
+ }
+ }
+ else
+ {
+ m_LostTimer.Start();
+ }
+ }
+ }
+ else
+ {
+ // If I was speaking a monolog, resume it
+ if ( m_args.bStopScenesWhenPlayerLost && m_hasPausedScenes )
+ {
+ if ( IsRunningScriptedScene( GetOuter() ) )
+ {
+ //Msg("Resuming scenes.\n");
+ ResumeActorsScriptedScenes( GetOuter(), false );
+ }
+
+ m_hasPausedScenes = false;
+ }
+
+ m_LostTimer.Stop();
+ ClearCondition( COND_LEAD_FOLLOWER_LOST );
+ }
+
+ // Evaluate for success
+ // Success right now means being stationary, close to the goal, and having the player close by
+ if ( !( m_args.flags & AILF_NO_DEF_SUCCESS ) )
+ {
+ ClearCondition( COND_LEAD_SUCCESS );
+
+ // Check Z first, and only check 2d if we're within that
+ bool bWithinZ = fabs(GetLocalOrigin().z - m_goal.z) < 64;
+ if ( bWithinZ && (GetLocalOrigin() - m_goal).Length2D() <= 64 )
+ {
+ if ( HasCondition( COND_LEAD_FOLLOWER_VERY_CLOSE ) )
+ {
+ SetCondition( COND_LEAD_SUCCESS );
+ }
+ else if ( m_successdistance )
+ {
+ float flDistSqr = (pFollower->GetAbsOrigin() - GetLocalOrigin()).Length2DSqr();
+ if ( flDistSqr < (m_successdistance*m_successdistance) )
+ {
+ SetCondition( COND_LEAD_SUCCESS );
+ }
+ }
+ }
+ }
+ if ( m_MoveMonitor.IsMarkSet() && m_MoveMonitor.TargetMoved( pFollower ) )
+ SetCondition( COND_LEAD_FOLLOWER_MOVED_FROM_MARK );
+ else
+ ClearCondition( COND_LEAD_FOLLOWER_MOVED_FROM_MARK );
+ }
+ }
+
+ if( m_args.bLeadDuringCombat )
+ {
+ ClearCondition( COND_LIGHT_DAMAGE );
+ ClearCondition( COND_HEAVY_DAMAGE );
+ }
+}
+
+//-------------------------------------
+
+int CAI_LeadBehavior::SelectSchedule()
+{
+ if ( HasGoal() )
+ {
+ if( HasCondition(COND_LEAD_SUCCESS) )
+ {
+ return SCHED_LEAD_SUCCEED;
+ }
+
+ // Player's here, but does he have the weapon we want him to have?
+ if ( m_weaponname != NULL_STRING )
+ {
+ CBasePlayer *pFollower = AI_GetSinglePlayer();
+ if ( pFollower && !pFollower->Weapon_OwnsThisType( STRING(m_weaponname) ) )
+ {
+ // If the safety timeout has run out, just give the player the weapon
+ if ( !m_flWeaponSafetyTimeOut || (m_flWeaponSafetyTimeOut > gpGlobals->curtime) )
+ return SCHED_LEAD_PLAYERNEEDSWEAPON;
+
+ string_t iszItem = AllocPooledString( "weapon_bugbait" );
+ pFollower->GiveNamedItem( STRING(iszItem) );
+ }
+ }
+
+ // If we have a waitpoint, we want to wait at it for the player.
+ if( HasWaitPoint() && !PlayerIsAheadOfMe( true ) )
+ {
+ bool bKeepWaiting = true;
+
+ // If we have no wait distance, trigger as soon as the player comes in view
+ if ( !m_waitdistance )
+ {
+ if ( HasCondition( COND_SEE_PLAYER ) )
+ {
+ // We've spotted the player, so stop waiting
+ bKeepWaiting = false;
+ }
+ }
+ else
+ {
+ // We have to collect data about the person we're leading around.
+ CBaseEntity *pFollower = AI_GetSinglePlayer();
+ if( pFollower )
+ {
+ float flFollowerDist = ( WorldSpaceCenter() - pFollower->WorldSpaceCenter() ).Length();
+ if ( flFollowerDist < m_waitdistance )
+ {
+ bKeepWaiting = false;
+ }
+ }
+ }
+
+ // Player still not here?
+ if ( bKeepWaiting )
+ return SCHED_LEAD_WAITFORPLAYER;
+
+ // We're finished waiting
+ m_waitpoint = vec3_origin;
+ Speak( TLK_LEAD_WAITOVER );
+
+ // Don't speak the start line, because we've said
+ m_hasspokenstart = true;
+ return SCHED_WAIT_FOR_SPEAK_FINISH;
+ }
+
+ // If we haven't spoken our start speech, do that first
+ if ( !m_hasspokenstart )
+ {
+ if ( HasCondition(COND_LEAD_HAVE_FOLLOWER_LOS) && HasCondition(COND_LEAD_FOLLOWER_VERY_CLOSE) )
+ return SCHED_LEAD_SPEAK_START;
+
+ // We haven't spoken to him, and we still need to. Go get him.
+ return SCHED_LEAD_RETRIEVE;
+ }
+
+ if( HasCondition( COND_LEAD_FOLLOWER_LOST ) )
+ {
+ if( m_args.iRetrievePlayer )
+ {
+ // If not, we want to go get the player.
+ DevMsg( GetOuter(), "Follower lost. Spoke COMING_BACK.\n");
+
+ Speak( TLK_LEAD_COMINGBACK );
+ m_MoveMonitor.ClearMark();
+
+ // If we spoke something, wait for it to finish
+ if ( m_args.iComingBackWaitForSpeak && IsSpeaking() )
+ return SCHED_LEAD_SPEAK_THEN_RETRIEVE_PLAYER;
+
+ return SCHED_LEAD_RETRIEVE;
+ }
+ else
+ {
+ // Just stay right here and wait.
+ return SCHED_LEAD_WAITFORPLAYERIDLE;
+ }
+ }
+
+ if( HasCondition( COND_LEAD_FOLLOWER_LAGGING ) )
+ {
+ DevMsg( GetOuter(), "Follower lagging. Spoke CATCHUP.\n");
+
+ Speak( TLK_LEAD_CATCHUP );
+ return SCHED_LEAD_PAUSE;
+ }
+ else
+ {
+ // If we're at the goal, wait for the player to get here
+ if ( ( WorldSpaceCenter() - m_goal ).LengthSqr() < (64*64) )
+ return SCHED_LEAD_AWAIT_SUCCESS;
+
+ // If we were retrieving the player, speak the resume
+ if ( IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) || IsCurSchedule( SCHED_LEAD_WAITFORPLAYERIDLE, false ) )
+ {
+ Speak( TLK_LEAD_RETRIEVE );
+
+ // If we spoke something, wait for it to finish, if the mapmakers wants us to
+ if ( m_args.iRetrieveWaitForSpeak && IsSpeaking() )
+ return SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER;
+ }
+
+ DevMsg( GetOuter(), "Leading Follower.\n");
+ return SCHED_LEAD_PLAYER;
+ }
+ }
+ return BaseClass::SelectSchedule();
+}
+
+//-------------------------------------
+
+int CAI_LeadBehavior::TranslateSchedule( int scheduleType )
+{
+ bool bInCombat = (m_args.bLeadDuringCombat && GetOuter()->GetState() == NPC_STATE_COMBAT);
+ switch( scheduleType )
+ {
+ case SCHED_LEAD_PAUSE:
+ if( bInCombat )
+ return SCHED_LEAD_PAUSE_COMBAT;
+ break;
+ }
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-------------------------------------
+
+bool CAI_LeadBehavior::IsCurTaskContinuousMove()
+{
+ const Task_t *pCurTask = GetCurTask();
+ if ( pCurTask && pCurTask->iTask == TASK_LEAD_MOVE_TO_RANGE )
+ return true;
+ return BaseClass::IsCurTaskContinuousMove();
+}
+
+//-------------------------------------
+
+void CAI_LeadBehavior::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_LEAD_FACE_GOAL:
+ {
+ if ( m_goalyaw != -1 )
+ {
+ GetMotor()->SetIdealYaw( m_goalyaw );
+ }
+
+ TaskComplete();
+ break;
+ }
+
+ case TASK_LEAD_SUCCEED:
+ {
+ Speak( TLK_LEAD_SUCCESS );
+ NotifyEvent( LBE_SUCCESS );
+
+ break;
+ }
+
+ case TASK_LEAD_ARRIVE:
+ {
+ // Only speak the first time we arrive
+ if ( !m_hasspokenarrival )
+ {
+ Speak( TLK_LEAD_ARRIVAL );
+ NotifyEvent( LBE_ARRIVAL );
+
+ m_hasspokenarrival = true;
+ }
+ else
+ {
+ TaskComplete();
+ }
+
+ break;
+ }
+
+ case TASK_STOP_LEADING:
+ {
+ ClearGoal();
+ TaskComplete();
+ break;
+ }
+
+ case TASK_GET_PATH_TO_LEAD_GOAL:
+ {
+ if ( GetNavigator()->SetGoal( m_goal ) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail("NO PATH");
+ }
+ break;
+ }
+
+ case TASK_LEAD_GET_PATH_TO_WAITPOINT:
+ {
+ if ( GetNavigator()->SetGoal( m_waitpoint ) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail("NO PATH");
+ }
+ break;
+ }
+
+ case TASK_LEAD_WALK_PATH:
+ {
+ // If we're leading, and we're supposed to run, run instead of walking
+ if ( m_run &&
+ ( IsCurSchedule( SCHED_LEAD_WAITFORPLAYER, false ) || IsCurSchedule( SCHED_LEAD_PLAYER, false ) || IsCurSchedule( SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER, false )|| IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) ) )
+ {
+ ChainStartTask( TASK_RUN_PATH );
+ }
+ else
+ {
+ ChainStartTask( TASK_WALK_PATH );
+ }
+ break;
+ }
+
+ case TASK_LEAD_WAVE_TO_PLAYER:
+ {
+ // Wave to the player if we can see him. Otherwise, just idle.
+ if ( HasCondition( COND_SEE_PLAYER ) )
+ {
+ Speak( TLK_LEAD_ATTRACTPLAYER );
+ if ( HaveSequenceForActivity(ACT_SIGNAL1) )
+ {
+ SetActivity(ACT_SIGNAL1);
+ }
+ }
+ else
+ {
+ SetActivity(ACT_IDLE);
+ }
+
+ TaskComplete();
+ break;
+ }
+
+ case TASK_LEAD_PLAYER_NEEDS_WEAPON:
+ {
+ float flAvailableTime = GetOuter()->GetExpresser()->GetSemaphoreAvailableTime( GetOuter() );
+
+ // if someone else is talking, don't speak
+ if ( flAvailableTime <= gpGlobals->curtime )
+ {
+ Speak( TLK_LEAD_MISSINGWEAPON );
+ }
+
+ SetActivity(ACT_IDLE);
+ TaskComplete();
+ break;
+ }
+
+ case TASK_LEAD_SPEAK_START:
+ {
+ m_hasspokenstart = true;
+
+ Speak( TLK_LEAD_START );
+ SetActivity(ACT_IDLE);
+ TaskComplete();
+ break;
+ }
+
+ case TASK_LEAD_MOVE_TO_RANGE:
+ {
+ // If we haven't spoken our start speech, move closer
+ if ( !m_hasspokenstart)
+ {
+ ChainStartTask( TASK_MOVE_TO_GOAL_RANGE, m_leaddistance - 24 );
+ }
+ else
+ {
+ ChainStartTask( TASK_MOVE_TO_GOAL_RANGE, m_retrievedistance );
+ }
+ break;
+ }
+
+ case TASK_LEAD_RETRIEVE_WAIT:
+ {
+ m_MoveMonitor.SetMark( AI_GetSinglePlayer(), 24 );
+ ChainStartTask( TASK_WAIT_INDEFINITE );
+ break;
+ }
+
+ case TASK_STOP_MOVING:
+ {
+ BaseClass::StartTask( pTask);
+
+ if ( IsCurSchedule( SCHED_LEAD_PAUSE, false ) && pTask->flTaskData == 1 )
+ {
+ GetNavigator()->SetArrivalDirection( GetTarget() );
+ }
+ break;
+ }
+
+ case TASK_WAIT_FOR_SPEAK_FINISH:
+ {
+ BaseClass::StartTask( pTask);
+
+ if( GetOuter()->GetState() == NPC_STATE_COMBAT )
+ {
+ // Don't stand around jabbering in combat.
+ TaskComplete();
+ }
+
+ // If we're not supposed to wait for the player, don't wait for speech to finish.
+ // Instead, just wait a wee tad, and then start moving. NPC will speak on the go.
+ if ( TaskIsRunning() && !m_args.iRetrievePlayer )
+ {
+ if ( gpGlobals->curtime - GetOuter()->GetTimeTaskStarted() > 0.3 )
+ {
+ TaskComplete();
+ }
+ }
+ break;
+ }
+
+ default:
+ BaseClass::StartTask( pTask);
+ }
+}
+
+//-------------------------------------
+
+void CAI_LeadBehavior::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_LEAD_SUCCEED:
+ {
+ if ( !IsSpeaking() )
+ {
+ TaskComplete();
+ NotifyEvent( LBE_DONE );
+ }
+ break;
+ }
+ case TASK_LEAD_ARRIVE:
+ {
+ if ( !IsSpeaking() )
+ {
+ TaskComplete();
+ NotifyEvent( LBE_ARRIVAL_DONE );
+ }
+ break;
+ }
+
+ case TASK_LEAD_MOVE_TO_RANGE:
+ {
+ // If we haven't spoken our start speech, move closer
+ if ( !m_hasspokenstart)
+ {
+ ChainRunTask( TASK_MOVE_TO_GOAL_RANGE, m_leaddistance - 24 );
+ }
+ else
+ {
+ ChainRunTask( TASK_MOVE_TO_GOAL_RANGE, m_retrievedistance );
+
+ if ( !TaskIsComplete() )
+ {
+ // Transition to a walk when we get near the player
+ // Check Z first, and only check 2d if we're within that
+ Vector vecGoalPos = GetNavigator()->GetGoalPos();
+ float distance = fabs(vecGoalPos.z - GetLocalOrigin().z);
+ bool bWithinZ = false;
+ if ( distance < m_retrievedistance )
+ {
+ distance = ( vecGoalPos - GetLocalOrigin() ).Length2D();
+ bWithinZ = true;
+ }
+
+ if ( distance > m_retrievedistance )
+ {
+ Activity followActivity = ACT_WALK;
+ if ( GetOuter()->GetState() == NPC_STATE_COMBAT || ( (!bWithinZ || distance < (m_retrievedistance*4)) && GetOuter()->GetState() != NPC_STATE_COMBAT ) )
+ {
+ followActivity = ACT_RUN;
+ }
+
+ // Don't confuse move and shoot by resetting the activity every think
+ Activity curActivity = GetNavigator()->GetMovementActivity();
+ switch( curActivity )
+ {
+ case ACT_WALK_AIM: curActivity = ACT_WALK; break;
+ case ACT_RUN_AIM: curActivity = ACT_RUN; break;
+ }
+
+ if ( curActivity != followActivity )
+ {
+ GetNavigator()->SetMovementActivity(followActivity);
+ }
+ GetNavigator()->SetArrivalDirection( GetOuter()->GetTarget() );
+ }
+ }
+ }
+ break;
+ }
+
+ case TASK_LEAD_RETRIEVE_WAIT:
+ {
+ ChainRunTask( TASK_WAIT_INDEFINITE );
+ break;
+ }
+
+ case TASK_LEAD_WALK_PATH:
+ {
+ // If we're leading, and we're supposed to run, run instead of walking
+ if ( m_run &&
+ ( IsCurSchedule( SCHED_LEAD_WAITFORPLAYER, false ) || IsCurSchedule( SCHED_LEAD_PLAYER, false ) || IsCurSchedule( SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER, false )|| IsCurSchedule( SCHED_LEAD_RETRIEVE, false ) ) )
+ {
+ ChainRunTask( TASK_RUN_PATH );
+ }
+ else
+ {
+ ChainRunTask( TASK_WALK_PATH );
+ }
+
+ // While we're walking
+ if ( TaskIsRunning() && IsCurSchedule( SCHED_LEAD_PLAYER, false ) )
+ {
+ // If we're not speaking, and we haven't tried for a while, try to speak lead idle
+ if ( m_flNextLeadIdle < gpGlobals->curtime && !IsSpeaking() )
+ {
+ m_flNextLeadIdle = gpGlobals->curtime + RandomFloat( 10,15 );
+
+ if ( !m_args.iRetrievePlayer && HasCondition( COND_LEAD_FOLLOWER_LOST ) && HasCondition(COND_SEE_PLAYER) )
+ {
+ Speak( TLK_LEAD_COMINGBACK );
+ }
+ else
+ {
+ Speak( TLK_LEAD_IDLE );
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ BaseClass::RunTask( pTask);
+ }
+}
+
+//-------------------------------------
+
+bool CAI_LeadBehavior::Speak( AIConcept_t concept )
+{
+ CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
+ if ( !pExpresser )
+ return false;
+
+ // If the leader is gagged, don't speak any lead speech
+ if ( m_gagleader )
+ return false;
+
+ // If we haven't said the start speech, don't nag
+ bool bNag = ( FStrEq(concept,TLK_LEAD_COMINGBACK) || FStrEq(concept, TLK_LEAD_CATCHUP) || FStrEq(concept, TLK_LEAD_RETRIEVE) );
+ if ( !m_hasspokenstart && bNag )
+ return false;
+
+ if ( hl2_episodic.GetBool() )
+ {
+ // If we're a player ally, only speak the concept if we're allowed to.
+ // This allows the response rules to control it better (i.e. handles respeakdelay)
+ // We ignore nag timers for this, because the response rules will control refire rates.
+ CAI_PlayerAlly *pAlly = dynamic_cast<CAI_PlayerAlly*>(GetOuter());
+ if ( pAlly )
+ return pAlly->SpeakIfAllowed( concept, GetConceptModifiers( concept ) );
+ }
+
+ // Don't spam Nags
+ if ( bNag )
+ {
+ if ( m_flSpeakNextNagTime > gpGlobals->curtime )
+ {
+ DevMsg( GetOuter(), "Leader didn't speak due to Nag timer.\n");
+ return false;
+ }
+ }
+
+ if ( pExpresser->Speak( concept, GetConceptModifiers( concept ) ) )
+ {
+ m_flSpeakNextNagTime = gpGlobals->curtime + LEAD_NAG_TIME;
+ return true;
+ }
+
+ return false;
+}
+
+//-------------------------------------
+
+bool CAI_LeadBehavior::IsSpeaking()
+{
+ CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
+ if ( !pExpresser )
+ return false;
+
+ return pExpresser->IsSpeaking();
+}
+
+//-------------------------------------
+
+bool CAI_LeadBehavior::Connect( CAI_LeadBehaviorHandler *pSink )
+{
+ m_pSink = pSink;
+ m_hSinkImplementor = dynamic_cast<CBaseEntity *>(pSink);
+
+ if ( m_hSinkImplementor == NULL )
+ DevMsg( 2, "Note: CAI_LeadBehaviorHandler connected to a sink that isn't an entity. Manual fixup on load will be necessary\n" );
+
+ return true;
+}
+
+//-------------------------------------
+
+bool CAI_LeadBehavior::Disconnect( CAI_LeadBehaviorHandler *pSink )
+{
+ Assert( pSink == m_pSink );
+ m_pSink = NULL;
+ m_hSinkImplementor = NULL;
+ return true;
+}
+
+//-------------------------------------
+
+AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_LeadBehavior )
+
+ DECLARE_CONDITION( COND_LEAD_FOLLOWER_LOST )
+ DECLARE_CONDITION( COND_LEAD_FOLLOWER_LAGGING )
+ DECLARE_CONDITION( COND_LEAD_FOLLOWER_NOT_LAGGING )
+ DECLARE_CONDITION( COND_LEAD_FOLLOWER_VERY_CLOSE )
+ DECLARE_CONDITION( COND_LEAD_SUCCESS )
+ DECLARE_CONDITION( COND_LEAD_HAVE_FOLLOWER_LOS )
+ DECLARE_CONDITION( COND_LEAD_FOLLOWER_MOVED_FROM_MARK )
+ DECLARE_CONDITION( COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME )
+
+ //---------------------------------
+ //
+ // Lead
+ //
+ DECLARE_TASK( TASK_GET_PATH_TO_LEAD_GOAL )
+ DECLARE_TASK( TASK_STOP_LEADING )
+ DECLARE_TASK( TASK_LEAD_ARRIVE )
+ DECLARE_TASK( TASK_LEAD_SUCCEED )
+ DECLARE_TASK( TASK_LEAD_FACE_GOAL )
+ DECLARE_TASK( TASK_LEAD_GET_PATH_TO_WAITPOINT )
+ DECLARE_TASK( TASK_LEAD_WAVE_TO_PLAYER )
+ DECLARE_TASK( TASK_LEAD_PLAYER_NEEDS_WEAPON )
+ DECLARE_TASK( TASK_LEAD_MOVE_TO_RANGE )
+ DECLARE_TASK( TASK_LEAD_SPEAK_START )
+ DECLARE_TASK( TASK_LEAD_RETRIEVE_WAIT )
+ DECLARE_TASK( TASK_LEAD_WALK_PATH )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_RETRIEVE,
+
+ " Tasks"
+ " TASK_GET_PATH_TO_PLAYER 0"
+ " TASK_LEAD_MOVE_TO_RANGE 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_RETRIEVE_WAIT"
+ " "
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_VERY_CLOSE"
+ " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //-------------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_SPEAK_THEN_RETRIEVE_PLAYER,
+
+ " Tasks"
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_RETRIEVE"
+ " "
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_VERY_CLOSE"
+ " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //-------------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_RETRIEVE_WAIT,
+
+ " Tasks"
+ " TASK_LEAD_RETRIEVE_WAIT 0"
+ " "
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_LOST"
+ " COND_LEAD_FOLLOWER_LAGGING"
+ " COND_LEAD_FOLLOWER_VERY_CLOSE"
+ " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
+ " COND_LEAD_FOLLOWER_MOVED_FROM_MARK"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_PLAYER,
+
+ " Tasks"
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ " TASK_GET_PATH_TO_LEAD_GOAL 0"
+ " TASK_LEAD_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ ""
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_LOST"
+ " COND_LEAD_FOLLOWER_LAGGING"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_AWAIT_SUCCESS,
+
+ " Tasks"
+ " TASK_LEAD_FACE_GOAL 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_LEAD_ARRIVE 0"
+ " TASK_WAIT_INDEFINITE 0"
+ ""
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_LOST"
+ " COND_LEAD_FOLLOWER_LAGGING"
+ " COND_LEAD_SUCCESS"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_SUCCEED,
+
+ " Tasks"
+ " TASK_LEAD_SUCCEED 0"
+ " TASK_STOP_LEADING 0"
+ ""
+ )
+
+ //---------------------------------
+ // This is the schedule Odell uses to pause the tour momentarily
+ // if the player lags behind. If the player shows up in a
+ // couple of seconds, the tour will resume. Otherwise, Odell
+ // moves to retrieve.
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_PAUSE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 1"
+ " TASK_FACE_TARGET 0"
+ " TASK_WAIT 5"
+ " TASK_WAIT_RANDOM 5"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_RETRIEVE"
+ ""
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_VERY_CLOSE"
+ " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
+ " COND_LEAD_FOLLOWER_NOT_LAGGING"
+ " COND_LEAD_FOLLOWER_LOST"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_PAUSE_COMBAT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 1"
+ " TASK_FACE_TARGET 0"
+ " TASK_WAIT 1"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_RETRIEVE"
+ ""
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_VERY_CLOSE"
+ " COND_LEAD_FOLLOWER_MOVING_TOWARDS_ME"
+ " COND_LEAD_FOLLOWER_NOT_LAGGING"
+ " COND_LEAD_FOLLOWER_LOST"
+ " COND_HEAR_DANGER"
+ )
+
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_WAITFORPLAYER,
+
+ " Tasks"
+ " TASK_LEAD_GET_PATH_TO_WAITPOINT 0"
+ " TASK_LEAD_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT 0.5"
+ " TASK_FACE_TARGET 0"
+ " TASK_LEAD_WAVE_TO_PLAYER 0"
+ " TASK_WAIT 5.0"
+ " "
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_VERY_CLOSE"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_WAITFORPLAYERIDLE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT 0.5"
+ " TASK_FACE_TARGET 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2"
+ " "
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_VERY_CLOSE"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_PLAYERNEEDSWEAPON,
+
+ " Tasks"
+ " TASK_FACE_PLAYER 0"
+ " TASK_LEAD_PLAYER_NEEDS_WEAPON 0"
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ " TASK_WAIT 8"
+ " "
+ " Interrupts"
+ )
+
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_SPEAK_START,
+
+ " Tasks"
+ " TASK_LEAD_SPEAK_START 0"
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ ""
+ " Interrupts"
+ )
+
+ //---------------------------------
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_LEAD_SPEAK_THEN_LEAD_PLAYER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_LEAD_PLAYER"
+ ""
+ " Interrupts"
+ " COND_LEAD_FOLLOWER_LOST"
+ " COND_LEAD_FOLLOWER_LAGGING"
+ " COND_LIGHT_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+AI_END_CUSTOM_SCHEDULE_PROVIDER()
+
+
+//-----------------------------------------------------------------------------
+//
+// Purpose: A level tool to control the lead behavior. Use is not required
+// in order to use behavior.
+//
+
+class CAI_LeadGoal : public CAI_GoalEntity,
+ public CAI_LeadBehaviorHandler
+{
+ DECLARE_CLASS( CAI_LeadGoal, CAI_GoalEntity );
+public:
+ CAI_LeadGoal()
+ : m_fArrived( false )
+ {
+ // These fields got added after existing levels shipped, so we set
+ // the default values here in the constructor.
+ m_iRetrievePlayer = 1;
+ m_iRetrieveWaitForSpeak = 0;
+ m_iComingBackWaitForSpeak = 0;
+ m_bStopScenesWhenPlayerLost = false;
+ m_bDontSpeakStart = false;
+ m_bLeadDuringCombat = false;
+ m_bGagLeader = false;
+ }
+
+ CAI_LeadBehavior *GetLeadBehavior();
+
+ virtual const char *GetConceptModifiers( const char *pszConcept );
+
+ virtual void InputActivate( inputdata_t &inputdata );
+ virtual void InputDeactivate( inputdata_t &inputdata );
+
+ DECLARE_DATADESC();
+private:
+
+ virtual void OnEvent( int event );
+ void InputSetSuccess( inputdata_t &inputdata );
+ void InputSetFailure( inputdata_t &inputdata );
+
+ bool m_fArrived; // @TODO (toml 08-16-02): move arrived tracking onto behavior
+ float m_flWaitDistance;
+ float m_flLeadDistance;
+ float m_flRetrieveDistance;
+ float m_flSuccessDistance;
+ bool m_bRun;
+ int m_iRetrievePlayer;
+ int m_iRetrieveWaitForSpeak;
+ int m_iComingBackWaitForSpeak;
+ bool m_bStopScenesWhenPlayerLost;
+ bool m_bDontSpeakStart;
+ bool m_bLeadDuringCombat;
+ bool m_bGagLeader;
+
+ string_t m_iszWaitPointName;
+
+ string_t m_iszStartConceptModifier;
+ string_t m_iszAttractPlayerConceptModifier;
+ string_t m_iszWaitOverConceptModifier;
+ string_t m_iszArrivalConceptModifier;
+ string_t m_iszPostArrivalConceptModifier;
+ string_t m_iszSuccessConceptModifier;
+ string_t m_iszFailureConceptModifier;
+ string_t m_iszRetrieveConceptModifier;
+ string_t m_iszComingBackConceptModifier;
+
+ // Output handlers
+ COutputEvent m_OnArrival;
+ COutputEvent m_OnArrivalDone;
+ COutputEvent m_OnSuccess;
+ COutputEvent m_OnFailure;
+ COutputEvent m_OnDone;
+};
+
+//-----------------------------------------------------------------------------
+//
+// CAI_LeadGoal implementation
+//
+
+LINK_ENTITY_TO_CLASS( ai_goal_lead, CAI_LeadGoal );
+
+BEGIN_DATADESC( CAI_LeadGoal )
+
+ DEFINE_FIELD( m_fArrived, FIELD_BOOLEAN ),
+
+ DEFINE_KEYFIELD(m_flWaitDistance, FIELD_FLOAT, "WaitDistance"),
+ DEFINE_KEYFIELD(m_iszWaitPointName, FIELD_STRING, "WaitPointName"),
+ DEFINE_KEYFIELD(m_flLeadDistance, FIELD_FLOAT, "LeadDistance"),
+ DEFINE_KEYFIELD(m_flRetrieveDistance, FIELD_FLOAT, "RetrieveDistance"),
+ DEFINE_KEYFIELD(m_flSuccessDistance, FIELD_FLOAT, "SuccessDistance"),
+ DEFINE_KEYFIELD(m_bRun, FIELD_BOOLEAN, "Run"),
+ DEFINE_KEYFIELD(m_iRetrievePlayer, FIELD_INTEGER, "Retrieve"),
+ DEFINE_KEYFIELD(m_iRetrieveWaitForSpeak, FIELD_INTEGER, "RetrieveWaitForSpeak"),
+ DEFINE_KEYFIELD(m_iComingBackWaitForSpeak, FIELD_INTEGER, "ComingBackWaitForSpeak"),
+ DEFINE_KEYFIELD(m_bStopScenesWhenPlayerLost, FIELD_BOOLEAN, "StopScenes"),
+ DEFINE_KEYFIELD(m_bDontSpeakStart, FIELD_BOOLEAN, "DontSpeakStart"),
+ DEFINE_KEYFIELD(m_bLeadDuringCombat, FIELD_BOOLEAN, "LeadDuringCombat"),
+ DEFINE_KEYFIELD(m_bGagLeader, FIELD_BOOLEAN, "GagLeader"),
+
+ DEFINE_KEYFIELD(m_iszStartConceptModifier, FIELD_STRING, "StartConceptModifier"),
+ DEFINE_KEYFIELD(m_iszAttractPlayerConceptModifier, FIELD_STRING, "AttractPlayerConceptModifier"),
+ DEFINE_KEYFIELD(m_iszWaitOverConceptModifier, FIELD_STRING, "WaitOverConceptModifier"),
+ DEFINE_KEYFIELD(m_iszArrivalConceptModifier, FIELD_STRING, "ArrivalConceptModifier"),
+ DEFINE_KEYFIELD(m_iszPostArrivalConceptModifier, FIELD_STRING, "PostArrivalConceptModifier"),
+ DEFINE_KEYFIELD(m_iszSuccessConceptModifier, FIELD_STRING, "SuccessConceptModifier"),
+ DEFINE_KEYFIELD(m_iszFailureConceptModifier, FIELD_STRING, "FailureConceptModifier"),
+ DEFINE_KEYFIELD(m_iszRetrieveConceptModifier, FIELD_STRING, "RetrieveConceptModifier"),
+ DEFINE_KEYFIELD(m_iszComingBackConceptModifier, FIELD_STRING, "ComingBackConceptModifier"),
+
+ DEFINE_OUTPUT( m_OnSuccess, "OnSuccess" ),
+ DEFINE_OUTPUT( m_OnArrival, "OnArrival" ),
+ DEFINE_OUTPUT( m_OnArrivalDone, "OnArrivalDone" ),
+ DEFINE_OUTPUT( m_OnFailure, "OnFailure" ),
+ DEFINE_OUTPUT( m_OnDone, "OnDone" ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetSuccess", InputSetSuccess ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SetFailure", InputSetFailure ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+
+CAI_LeadBehavior *CAI_LeadGoal::GetLeadBehavior()
+{
+ CAI_BaseNPC *pActor = GetActor();
+ if ( !pActor )
+ return NULL;
+
+ CAI_LeadBehavior *pBehavior;
+ if ( !pActor->GetBehavior( &pBehavior ) )
+ {
+ return NULL;
+ }
+
+ return pBehavior;
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_LeadGoal::InputSetSuccess( inputdata_t &inputdata )
+{
+ CAI_LeadBehavior *pBehavior = GetLeadBehavior();
+ if ( !pBehavior )
+ return;
+
+ // @TODO (toml 02-14-03): Hackly!
+ pBehavior->SetCondition( CAI_LeadBehavior::COND_LEAD_SUCCESS);
+}
+
+
+//-----------------------------------------------------------------------------
+
+void CAI_LeadGoal::InputSetFailure( inputdata_t &inputdata )
+{
+ DevMsg( "SetFailure unimplemented\n" );
+}
+
+
+//-----------------------------------------------------------------------------
+
+void CAI_LeadGoal::InputActivate( inputdata_t &inputdata )
+{
+ BaseClass::InputActivate( inputdata );
+
+ CAI_LeadBehavior *pBehavior = GetLeadBehavior();
+ if ( !pBehavior )
+ {
+ DevMsg( "Lead goal entity activated for an NPC that doesn't have the lead behavior\n" );
+ return;
+ }
+#ifdef HL2_EPISODIC
+ if ( (m_flLeadDistance*4) < m_flRetrieveDistance )
+ {
+ Warning("ai_goal_lead '%s': lead distance (%.2f) * 4 is < retrieve distance (%.2f). This will make the NPC act stupid. Either reduce the retrieve distance, or increase the lead distance.\n", GetDebugName(), m_flLeadDistance, m_flRetrieveDistance );
+ }
+#endif
+
+ if ( m_flRetrieveDistance < (m_flLeadDistance + LEAD_MIN_RETRIEVEDIST_OFFSET) )
+ {
+#ifdef HL2_EPISODIC
+ Warning("ai_goal_lead '%s': retrieve distance (%.2f) < lead distance (%.2f) + %d. Retrieve distance should be at least %d greater than the lead distance, or NPC will ping-pong while retrieving.\n", GetDebugName(), m_flRetrieveDistance, m_flLeadDistance, LEAD_MIN_RETRIEVEDIST_OFFSET, LEAD_MIN_RETRIEVEDIST_OFFSET );
+#endif
+ m_flRetrieveDistance = m_flLeadDistance + LEAD_MIN_RETRIEVEDIST_OFFSET;
+ }
+
+ AI_LeadArgs_t leadArgs = {
+ GetGoalEntityName(),
+ STRING(m_iszWaitPointName),
+ m_spawnflags,
+ m_flWaitDistance,
+ m_flLeadDistance,
+ m_flRetrieveDistance,
+ m_flSuccessDistance,
+ m_bRun,
+ m_iRetrievePlayer,
+ m_iRetrieveWaitForSpeak,
+ m_iComingBackWaitForSpeak,
+ m_bStopScenesWhenPlayerLost,
+ m_bDontSpeakStart,
+ m_bLeadDuringCombat,
+ m_bGagLeader,
+ };
+
+ pBehavior->LeadPlayer( leadArgs, this );
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_LeadGoal::InputDeactivate( inputdata_t &inputdata )
+{
+ BaseClass::InputDeactivate( inputdata );
+
+ CAI_LeadBehavior *pBehavior = GetLeadBehavior();
+ if ( !pBehavior )
+ return;
+
+ pBehavior->StopLeading();
+}
+
+//-----------------------------------------------------------------------------
+
+void CAI_LeadGoal::OnEvent( int event )
+{
+ COutputEvent *pOutputEvent = NULL;
+
+ switch ( event )
+ {
+ case LBE_ARRIVAL: pOutputEvent = &m_OnArrival; break;
+ case LBE_ARRIVAL_DONE: pOutputEvent = &m_OnArrivalDone; break;
+ case LBE_SUCCESS: pOutputEvent = &m_OnSuccess; break;
+ case LBE_FAILURE: pOutputEvent = &m_OnFailure; break;
+ case LBE_DONE: pOutputEvent = &m_OnDone; break;
+ }
+
+ // @TODO (toml 08-16-02): move arrived tracking onto behavior
+ if ( event == LBE_ARRIVAL )
+ m_fArrived = true;
+
+ if ( pOutputEvent )
+ pOutputEvent->FireOutput( this, this );
+}
+
+//-----------------------------------------------------------------------------
+
+const char *CAI_LeadGoal::GetConceptModifiers( const char *pszConcept )
+{
+ if ( m_iszStartConceptModifier != NULL_STRING && *STRING(m_iszStartConceptModifier) && strcmp( pszConcept, TLK_LEAD_START) == 0 )
+ return STRING( m_iszStartConceptModifier );
+
+ if ( m_iszAttractPlayerConceptModifier != NULL_STRING && *STRING(m_iszAttractPlayerConceptModifier) && strcmp( pszConcept, TLK_LEAD_ATTRACTPLAYER) == 0 )
+ return STRING( m_iszAttractPlayerConceptModifier );
+
+ if ( m_iszWaitOverConceptModifier != NULL_STRING && *STRING(m_iszWaitOverConceptModifier) && strcmp( pszConcept, TLK_LEAD_WAITOVER) == 0 )
+ return STRING( m_iszWaitOverConceptModifier );
+
+ if ( m_iszArrivalConceptModifier != NULL_STRING && *STRING(m_iszArrivalConceptModifier) && strcmp( pszConcept, TLK_LEAD_ARRIVAL) == 0 )
+ return STRING( m_iszArrivalConceptModifier );
+
+ if ( m_iszSuccessConceptModifier != NULL_STRING && *STRING(m_iszSuccessConceptModifier) && strcmp( pszConcept, TLK_LEAD_SUCCESS) == 0 )
+ return STRING( m_iszSuccessConceptModifier );
+
+ if (m_iszFailureConceptModifier != NULL_STRING && *STRING(m_iszFailureConceptModifier) && strcmp( pszConcept, TLK_LEAD_FAILURE) == 0 )
+ return STRING( m_iszFailureConceptModifier );
+
+ if (m_iszRetrieveConceptModifier != NULL_STRING && *STRING(m_iszRetrieveConceptModifier) && strcmp( pszConcept, TLK_LEAD_RETRIEVE) == 0 )
+ return STRING( m_iszRetrieveConceptModifier );
+
+ if (m_iszComingBackConceptModifier != NULL_STRING && *STRING(m_iszComingBackConceptModifier) && strcmp( pszConcept, TLK_LEAD_COMINGBACK) == 0 )
+ return STRING( m_iszComingBackConceptModifier );
+
+ if ( m_fArrived && m_iszPostArrivalConceptModifier != NULL_STRING && *STRING(m_iszPostArrivalConceptModifier) )
+ return STRING( m_iszPostArrivalConceptModifier );
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+//
+// Purpose: A custom lead goal that waits until the player has a weapon.
+//
+
+class CAI_LeadGoal_Weapon : public CAI_LeadGoal
+{
+ DECLARE_CLASS( CAI_LeadGoal_Weapon, CAI_LeadGoal );
+public:
+
+ virtual const char *GetConceptModifiers( const char *pszConcept );
+ virtual void InputActivate( inputdata_t &inputdata );
+
+private:
+ string_t m_iszWeaponName;
+ string_t m_iszMissingWeaponConceptModifier;
+
+ DECLARE_DATADESC();
+};
+
+//-----------------------------------------------------------------------------
+//
+// CAI_LeadGoal_Weapon implementation
+//
+
+LINK_ENTITY_TO_CLASS( ai_goal_lead_weapon, CAI_LeadGoal_Weapon );
+
+BEGIN_DATADESC( CAI_LeadGoal_Weapon )
+
+ DEFINE_KEYFIELD( m_iszWeaponName, FIELD_STRING, "WeaponName"),
+ DEFINE_KEYFIELD( m_iszMissingWeaponConceptModifier, FIELD_STRING, "MissingWeaponConceptModifier"),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+
+const char *CAI_LeadGoal_Weapon::GetConceptModifiers( const char *pszConcept )
+{
+ if ( m_iszMissingWeaponConceptModifier != NULL_STRING && *STRING(m_iszMissingWeaponConceptModifier) && strcmp( pszConcept, TLK_LEAD_MISSINGWEAPON) == 0 )
+ return STRING( m_iszMissingWeaponConceptModifier );
+
+ return BaseClass::GetConceptModifiers( pszConcept );
+}
+
+
+//-----------------------------------------------------------------------------
+
+void CAI_LeadGoal_Weapon::InputActivate( inputdata_t &inputdata )
+{
+ BaseClass::InputActivate( inputdata );
+
+ CAI_LeadBehavior *pBehavior = GetLeadBehavior();
+ if ( pBehavior )
+ {
+ pBehavior->SetWaitForWeapon( m_iszWeaponName );
+ }
+}