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.
552 lines
15 KiB
552 lines
15 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "ai_motor.h" |
|
#include "ai_behavior_fear.h" |
|
#include "ai_hint.h" |
|
#include "ai_navigator.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
BEGIN_DATADESC( CAI_FearBehavior ) |
|
DEFINE_FIELD( m_flTimeToSafety, FIELD_TIME ), |
|
DEFINE_FIELD( m_flTimePlayerLastVisible, FIELD_TIME ), |
|
DEFINE_FIELD( m_hSafePlaceHint, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hMovingToHint, FIELD_EHANDLE ), |
|
DEFINE_EMBEDDED( m_SafePlaceMoveMonitor ), |
|
DEFINE_FIELD( m_flDeferUntil, FIELD_TIME ), |
|
END_DATADESC(); |
|
|
|
#define BEHAVIOR_FEAR_SAFETY_TIME 5 |
|
#define FEAR_SAFE_PLACE_TOLERANCE 36.0f |
|
#define FEAR_ENEMY_TOLERANCE_CLOSE_DIST_SQR Square(300.0f) // (25 feet) |
|
#define FEAR_ENEMY_TOLERANCE_TOO_CLOSE_DIST_SQR Square( 60.0f ) // (5 Feet) |
|
|
|
ConVar ai_enable_fear_behavior( "ai_enable_fear_behavior", "1" ); |
|
|
|
ConVar ai_fear_player_dist("ai_fear_player_dist", "720" ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CAI_FearBehavior::CAI_FearBehavior() |
|
{ |
|
ReleaseAllHints(); |
|
m_SafePlaceMoveMonitor.ClearMark(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::Precache( void ) |
|
{ |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTask - |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::StartTask( const Task_t *pTask ) |
|
{ |
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_FEAR_IN_SAFE_PLACE: |
|
// We've arrived! Lock the hint and set the marker. we're safe for now. |
|
m_hSafePlaceHint = m_hMovingToHint; |
|
m_hSafePlaceHint->Lock( GetOuter() ); |
|
m_SafePlaceMoveMonitor.SetMark( GetOuter(), FEAR_SAFE_PLACE_TOLERANCE ); |
|
TaskComplete(); |
|
break; |
|
|
|
case TASK_FEAR_GET_PATH_TO_SAFETY_HINT: |
|
// Using TaskInterrupt() optimizations. See RunTask(). |
|
break; |
|
|
|
case TASK_FEAR_WAIT_FOR_SAFETY: |
|
m_flTimeToSafety = gpGlobals->curtime + BEHAVIOR_FEAR_SAFETY_TIME; |
|
break; |
|
|
|
default: |
|
BaseClass::StartTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTask - |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::RunTask( const Task_t *pTask ) |
|
{ |
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_FEAR_WAIT_FOR_SAFETY: |
|
if( HasCondition(COND_SEE_ENEMY) ) |
|
{ |
|
m_flTimeToSafety = gpGlobals->curtime + BEHAVIOR_FEAR_SAFETY_TIME; |
|
} |
|
else |
|
{ |
|
if( gpGlobals->curtime > m_flTimeToSafety ) |
|
{ |
|
TaskComplete(); |
|
} |
|
} |
|
break; |
|
|
|
case TASK_FEAR_GET_PATH_TO_SAFETY_HINT: |
|
{ |
|
switch( GetOuter()->GetTaskInterrupt() ) |
|
{ |
|
case 0:// Find the hint node |
|
{ |
|
ReleaseAllHints(); |
|
CAI_Hint *pHint = FindFearWithdrawalDest(); |
|
|
|
if( pHint == NULL ) |
|
{ |
|
TaskFail("Fear: Couldn't find hint node\n"); |
|
m_flDeferUntil = gpGlobals->curtime + 3.0f;// Don't bang the hell out of this behavior. If we don't find a node, take a short break and run regular AI. |
|
} |
|
else |
|
{ |
|
m_hMovingToHint.Set( pHint ); |
|
GetOuter()->TaskInterrupt(); |
|
} |
|
} |
|
break; |
|
|
|
case 1:// Do the pathfinding. |
|
{ |
|
Assert( m_hMovingToHint != NULL ); |
|
|
|
AI_NavGoal_t goal(m_hMovingToHint->GetAbsOrigin()); |
|
goal.pTarget = NULL; |
|
if( GetNavigator()->SetGoal( goal ) == false ) |
|
{ |
|
m_hMovingToHint.Set( NULL ); |
|
// Do whatever we'd want to do if we can't find a path |
|
/* |
|
Msg("Can't path to the Fear Hint!\n"); |
|
|
|
AI_NavGoal_t nearGoal( GOALTYPE_LOCATION_NEAREST_NODE, m_hRallyPoint->GetAbsOrigin(), AIN_DEF_ACTIVITY, 256 ); |
|
if ( GetNavigator()->SetGoal( nearGoal, AIN_CLEAR_PREVIOUS_STATE ) ) |
|
{ |
|
//FIXME: HACK! The internal pathfinding is setting this without our consent, so override it! |
|
ClearCondition( COND_TASK_FAILED ); |
|
GetNavigator()->SetArrivalDirection( m_hRallyPoint->GetAbsAngles() ); |
|
TaskComplete(); |
|
return; |
|
} |
|
*/ |
|
} |
|
else |
|
{ |
|
GetNavigator()->SetArrivalDirection( m_hMovingToHint->GetAbsAngles() ); |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::RunTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : TRUE if I have an enemy and that enemy would attack me if it could |
|
// Notes : Returns FALSE if the enemy is neutral or likes me. |
|
//----------------------------------------------------------------------------- |
|
bool CAI_FearBehavior::EnemyDislikesMe() |
|
{ |
|
CBaseEntity *pEnemy = GetEnemy(); |
|
|
|
if( pEnemy == NULL ) |
|
return false; |
|
|
|
if( pEnemy->MyNPCPointer() == NULL ) |
|
return false; |
|
|
|
Disposition_t disposition = pEnemy->MyNPCPointer()->IRelationType(GetOuter()); |
|
|
|
Assert(disposition != D_ER); |
|
|
|
if( disposition >= D_LI ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// This place is definitely no longer safe. Stop picking it for a while. |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::MarkAsUnsafe() |
|
{ |
|
Assert( m_hSafePlaceHint ); |
|
|
|
// Disable the node to stop anyone from picking it for a while. |
|
m_hSafePlaceHint->DisableForSeconds( 5.0f ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Am I in safe place from my enemy? |
|
//----------------------------------------------------------------------------- |
|
bool CAI_FearBehavior::IsInASafePlace() |
|
{ |
|
// No safe place in mind. |
|
if( !m_SafePlaceMoveMonitor.IsMarkSet() ) |
|
return false; |
|
|
|
// I have a safe place, but I'm not there. |
|
if( m_SafePlaceMoveMonitor.TargetMoved(GetOuter()) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::SpoilSafePlace() |
|
{ |
|
m_SafePlaceMoveMonitor.ClearMark(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::ReleaseAllHints() |
|
{ |
|
if( m_hSafePlaceHint ) |
|
{ |
|
// If I have a safe place, unlock it for others. |
|
m_hSafePlaceHint->Unlock(); |
|
|
|
// Don't make it available right away. I probably left for a good reason. |
|
// We also don't want to oscillate |
|
m_hSafePlaceHint->DisableForSeconds( 4.0f ); |
|
m_hSafePlaceHint = NULL; |
|
} |
|
|
|
if( m_hMovingToHint ) |
|
{ |
|
m_hMovingToHint->Unlock(); |
|
m_hMovingToHint = NULL; |
|
} |
|
|
|
m_SafePlaceMoveMonitor.ClearMark(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
// Notes : This behavior runs when I have an enemy that I fear, but who |
|
// does NOT hate or fear me (meaning they aren't going to fight me) |
|
//----------------------------------------------------------------------------- |
|
bool CAI_FearBehavior::CanSelectSchedule() |
|
{ |
|
if( !GetOuter()->IsInterruptable() ) |
|
return false; |
|
|
|
if( m_flDeferUntil > gpGlobals->curtime ) |
|
return false; |
|
|
|
CBaseEntity *pEnemy = GetEnemy(); |
|
|
|
if( pEnemy == NULL ) |
|
return false; |
|
|
|
//if( !HasCondition(COND_SEE_PLAYER) ) |
|
// return false; |
|
|
|
if( !ai_enable_fear_behavior.GetBool() ) |
|
return false; |
|
|
|
if( GetOuter()->IRelationType(pEnemy) != D_FR ) |
|
return false; |
|
|
|
if( !pEnemy->ClassMatches("npc_hunter") ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::GatherConditions() |
|
{ |
|
BaseClass::GatherConditions(); |
|
|
|
ClearCondition( COND_FEAR_ENEMY_CLOSE ); |
|
ClearCondition( COND_FEAR_ENEMY_TOO_CLOSE ); |
|
if( GetEnemy() ) |
|
{ |
|
float flEnemyDistSqr = GetAbsOrigin().DistToSqr(GetEnemy()->GetAbsOrigin()); |
|
|
|
if( flEnemyDistSqr < FEAR_ENEMY_TOLERANCE_TOO_CLOSE_DIST_SQR ) |
|
{ |
|
SetCondition( COND_FEAR_ENEMY_TOO_CLOSE ); |
|
if( IsInASafePlace() ) |
|
{ |
|
SpoilSafePlace(); |
|
} |
|
} |
|
else if( flEnemyDistSqr < FEAR_ENEMY_TOLERANCE_CLOSE_DIST_SQR && GetEnemy()->GetEnemy() == GetOuter() ) |
|
{ |
|
// Only become scared of an enemy at this range if they're my enemy, too |
|
SetCondition( COND_FEAR_ENEMY_CLOSE ); |
|
if( IsInASafePlace() ) |
|
{ |
|
SpoilSafePlace(); |
|
} |
|
} |
|
} |
|
|
|
ClearCondition(COND_FEAR_SEPARATED_FROM_PLAYER); |
|
|
|
// Check for separation from the player |
|
// -The player is farther away than 60 feet |
|
// -I haven't seen the player in 2 seconds |
|
// |
|
// Here's the distance check: |
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
if( pPlayer != NULL && GetAbsOrigin().DistToSqr(pPlayer->GetAbsOrigin()) >= Square( ai_fear_player_dist.GetFloat() * 1.5f ) ) |
|
{ |
|
SetCondition(COND_FEAR_SEPARATED_FROM_PLAYER); |
|
} |
|
|
|
// Here's the visibility check. We can't skip this because it's time-sensitive |
|
if( GetOuter()->FVisible(pPlayer) ) |
|
{ |
|
m_flTimePlayerLastVisible = gpGlobals->curtime; |
|
} |
|
else |
|
{ |
|
if( gpGlobals->curtime - m_flTimePlayerLastVisible >= 2.0f ) |
|
{ |
|
SetCondition(COND_FEAR_SEPARATED_FROM_PLAYER); |
|
} |
|
} |
|
|
|
if( HasCondition(COND_FEAR_SEPARATED_FROM_PLAYER) ) |
|
{ |
|
//Msg("I am separated from player\n"); |
|
|
|
if( IsInASafePlace() ) |
|
{ |
|
SpoilSafePlace(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::BeginScheduleSelection() |
|
{ |
|
if( m_hSafePlaceHint ) |
|
{ |
|
// We think we're safe. Is it true? |
|
if( !IsInASafePlace() ) |
|
{ |
|
// no! So mark it so. |
|
ReleaseAllHints(); |
|
} |
|
} |
|
|
|
m_flTimePlayerLastVisible = gpGlobals->curtime; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::EndScheduleSelection() |
|
{ |
|
// We don't have to release our hints or markers or anything here. |
|
// Just because we ran other AI for a while doesn't mean we aren't still in a safe place. |
|
//ReleaseAllHints(); |
|
} |
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
// Notes : If fear behavior is running at all, we know we're afraid of our enemy |
|
//----------------------------------------------------------------------------- |
|
int CAI_FearBehavior::SelectSchedule() |
|
{ |
|
bool bInSafePlace = IsInASafePlace(); |
|
|
|
if( !HasCondition(COND_HEAR_DANGER) ) |
|
{ |
|
if( !bInSafePlace ) |
|
{ |
|
// Always move to a safe place if we're not running from a danger sound |
|
return SCHED_FEAR_MOVE_TO_SAFE_PLACE; |
|
} |
|
else |
|
{ |
|
// We ARE in a safe place |
|
if( HasCondition(COND_CAN_RANGE_ATTACK1) ) |
|
return SCHED_RANGE_ATTACK1; |
|
|
|
return SCHED_FEAR_STAY_IN_SAFE_PLACE; |
|
} |
|
} |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CAI_FearBehavior::BuildScheduleTestBits() |
|
{ |
|
BaseClass::BuildScheduleTestBits(); |
|
|
|
if( GetOuter()->GetState() != NPC_STATE_SCRIPT ) |
|
{ |
|
// Stop doing ANYTHING if we get scared. |
|
//GetOuter()->SetCustomInterruptCondition( COND_HEAR_DANGER ); |
|
|
|
if( !IsCurSchedule(SCHED_FEAR_MOVE_TO_SAFE_PLACE_RETRY, false) && !IsCurSchedule(SCHED_FEAR_MOVE_TO_SAFE_PLACE, false) ) |
|
{ |
|
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal(COND_FEAR_SEPARATED_FROM_PLAYER) ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
int CAI_FearBehavior::TranslateSchedule( int scheduleType ) |
|
{ |
|
switch( scheduleType ) |
|
{ |
|
case SCHED_FEAR_MOVE_TO_SAFE_PLACE: |
|
if( HasCondition(COND_FEAR_ENEMY_TOO_CLOSE) ) |
|
{ |
|
// If I'm moving to a safe place AND have an enemy too close to me, |
|
// make the move to safety while ignoring the condition. |
|
// this stops an oscillation |
|
// IS THIS CODE EVER EVEN BEING CALLED? (sjb) |
|
return SCHED_FEAR_MOVE_TO_SAFE_PLACE_RETRY; |
|
} |
|
break; |
|
} |
|
|
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
CAI_Hint *CAI_FearBehavior::FindFearWithdrawalDest() |
|
{ |
|
CAI_Hint *pHint; |
|
CHintCriteria hintCriteria; |
|
CAI_BaseNPC *pOuter = GetOuter(); |
|
|
|
Assert(pOuter != NULL); |
|
|
|
hintCriteria.AddHintType( HINT_PLAYER_ALLY_FEAR_DEST ); |
|
hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE_TO_PLAYER | bits_HINT_NOT_CLOSE_TO_ENEMY /*| bits_HINT_NODE_IN_VIEWCONE | bits_HINT_NPC_IN_NODE_FOV*/ ); |
|
hintCriteria.AddIncludePosition( AI_GetSinglePlayer()->GetAbsOrigin(), ( ai_fear_player_dist.GetFloat() ) ); |
|
|
|
pHint = CAI_HintManager::FindHint( pOuter, hintCriteria ); |
|
|
|
if( pHint ) |
|
{ |
|
// Reserve this node while I try to get to it. When I get there I will lock it. |
|
// Otherwise, if I fail to get there, the node will come available again soon. |
|
pHint->DisableForSeconds( 4.0f ); |
|
} |
|
#if 0 |
|
else |
|
{ |
|
Msg("DID NOT FIND HINT\n"); |
|
NDebugOverlay::Cross3D( GetOuter()->WorldSpaceCenter(), 32, 255, 255, 0, false, 10.0f ); |
|
} |
|
#endif |
|
|
|
return pHint; |
|
} |
|
|
|
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FearBehavior ) |
|
|
|
DECLARE_TASK( TASK_FEAR_GET_PATH_TO_SAFETY_HINT ) |
|
DECLARE_TASK( TASK_FEAR_WAIT_FOR_SAFETY ) |
|
DECLARE_TASK( TASK_FEAR_IN_SAFE_PLACE ) |
|
|
|
DECLARE_CONDITION( COND_FEAR_ENEMY_CLOSE ) |
|
DECLARE_CONDITION( COND_FEAR_ENEMY_TOO_CLOSE ) |
|
DECLARE_CONDITION( COND_FEAR_SEPARATED_FROM_PLAYER ) |
|
|
|
//=============================================== |
|
//=============================================== |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FEAR_MOVE_TO_SAFE_PLACE, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RUN_FROM_ENEMY" |
|
" TASK_FEAR_GET_PATH_TO_SAFETY_HINT 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_FEAR_IN_SAFE_PLACE 0" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_FEAR_STAY_IN_SAFE_PLACE" |
|
"" |
|
" Interrupts" |
|
"" |
|
" COND_HEAR_DANGER" |
|
" COND_NEW_ENEMY" |
|
" COND_FEAR_ENEMY_TOO_CLOSE" |
|
); |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FEAR_MOVE_TO_SAFE_PLACE_RETRY, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RUN_FROM_ENEMY" |
|
" TASK_FEAR_GET_PATH_TO_SAFETY_HINT 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_FEAR_IN_SAFE_PLACE 0" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_FEAR_STAY_IN_SAFE_PLACE" |
|
"" |
|
" Interrupts" |
|
"" |
|
" COND_HEAR_DANGER" |
|
" COND_NEW_ENEMY" |
|
); |
|
|
|
//=============================================== |
|
//=============================================== |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FEAR_STAY_IN_SAFE_PLACE, |
|
|
|
" Tasks" |
|
" TASK_FEAR_WAIT_FOR_SAFETY 0" |
|
"" |
|
" Interrupts" |
|
"" |
|
" COND_NEW_ENEMY" |
|
" COND_HEAR_DANGER" |
|
" COND_FEAR_ENEMY_CLOSE" |
|
" COND_FEAR_ENEMY_TOO_CLOSE" |
|
" COND_CAN_RANGE_ATTACK1" |
|
" COND_FEAR_SEPARATED_FROM_PLAYER" |
|
); |
|
|
|
|
|
AI_END_CUSTOM_SCHEDULE_PROVIDER()
|
|
|