You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1688 lines
46 KiB
1688 lines
46 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#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 ) |
|
{ |
|
CAI_PlayerAlly *pOuter = dynamic_cast<CAI_PlayerAlly*>(GetOuter()); |
|
if ( pOuter && AI_IsSinglePlayer() ) |
|
{ |
|
pOuter->SetSpeechTarget( UTIL_GetLocalPlayer() ); |
|
} |
|
|
|
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(), bits_BUILD_GET_CLOSE ); |
|
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 ); |
|
} |
|
}
|
|
|