//========= 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 ) ,
( unsigned ) 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 ) ;
}
}