Team Fortress 2 Source Code as on 22/4/2020
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

//========= 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()