//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "ai_motor.h" #include "asw_ai_behavior_fear.h" #include "ai_hint.h" #include "ai_navigator.h" #include "ai_memory.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" BEGIN_DATADESC( CAI_ASW_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 ), DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ), END_DATADESC(); LINK_BEHAVIOR_TO_CLASSNAME( CAI_ASW_FearBehavior ); #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 asw_ai_enable_fear_behavior( "asw_ai_enable_fear_behavior", "1", FCVAR_CHEAT ); ConVar asw_ai_fear_player_dist( "asw_ai_fear_player_dist", "720", FCVAR_CHEAT ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CAI_ASW_FearBehavior::CAI_ASW_FearBehavior() { m_bForceFear = false; ReleaseAllHints(); m_SafePlaceMoveMonitor.ClearMark(); } //------------------------------------------------------------------------------ // Purpose: function to set up parameters // Input : szKeyName - the name of the key // szValue - the value to be set // Output : returns true of we handled this key //------------------------------------------------------------------------------ bool CAI_ASW_FearBehavior::KeyValue( const char *szKeyName, const char *szValue ) { if ( V_stricmp( szKeyName, "force" ) == 0 ) { m_bForceFear = ( atoi( szValue ) != 0 ); return true; } if ( V_stricmp( szKeyName, "duration" ) == 0 ) { m_flDuration = atof( szValue ); return true; } return BaseClass::KeyValue( szKeyName, szValue ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CAI_ASW_FearBehavior::Precache( void ) { } //----------------------------------------------------------------------------- // Purpose: // Input : *pTask - //----------------------------------------------------------------------------- void CAI_ASW_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; case TASK_FEAR_FIND_COVER_FROM_ENEMY: { bool bNodeCover = true; float flMinDistance = 1024.0f; float flMaxDistance = FLT_MAX; if ( GetOuter()->FindCoverFromEnemy( bNodeCover, flMinDistance, flMaxDistance ) ) { // m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData; TaskComplete(); } else { TaskFail( FAIL_NO_COVER ); } break; } default: BaseClass::StartTask( pTask ); break; } } //----------------------------------------------------------------------------- // Purpose: // Input : *pTask - //----------------------------------------------------------------------------- void CAI_ASW_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 + 0.5f;// 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; } } //----------------------------------------------------------------------------- // Am I in safe place from my enemy? //----------------------------------------------------------------------------- bool CAI_ASW_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_ASW_FearBehavior::SpoilSafePlace() { m_SafePlaceMoveMonitor.ClearMark(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CAI_ASW_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_ASW_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 ( !asw_ai_enable_fear_behavior.GetBool() ) { return false; } if ( !m_bForceFear && GetOuter()->IRelationType( pEnemy ) != D_FEAR ) { return false; } return BaseClass::CanSelectSchedule(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CAI_ASW_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: CBaseEntity *pBestObject = NULL; bool bFound = false; float flLength = 0.0f; AIEnemiesIter_t iter; Vector vEnemyPos; for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) ) { CBaseEntity *pEnemy = pEMemory->hEnemy; if ( !pEnemy || !pEnemy->IsAlive() || !GetOuter()->FVisible( pEnemy ) ) { continue; } Vector vDiff = GetAbsOrigin() - pEMemory->vLastKnownLocation; float flTestLength = vDiff.LengthSqr(); if ( !bFound || flTestLength < flLength ) { bFound = true; flLength = flTestLength; vEnemyPos = pEMemory->vLastKnownLocation; pBestObject = pEnemy; } } // Here's the visibility check. We can't skip this because it's time-sensitive if( pBestObject ) { m_flTimePlayerLastVisible = gpGlobals->curtime; } else { if( gpGlobals->curtime - m_flTimePlayerLastVisible >= 2.0f ) { SetCondition(COND_FEAR_SEPARATED_FROM_PLAYER); } } m_hTargetEnt = pBestObject; if( HasCondition(COND_FEAR_SEPARATED_FROM_PLAYER) ) { //Msg("I am separated from player\n"); if( IsInASafePlace() ) { SpoilSafePlace(); } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CAI_ASW_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_ASW_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_ASW_FearBehavior::SelectSchedule() { bool bInSafePlace = IsInASafePlace(); if ( m_bForceFear ) { 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 { return SCHED_FEAR_STAY_IN_SAFE_PLACE; } } 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_ASW_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_ASW_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_ASW_FearBehavior::FindFearWithdrawalDest() { CAI_Hint *pHint; CHintCriteria hintCriteria; CAI_BaseNPC *pOuter = GetOuter(); Assert(pOuter != NULL); if ( m_hTargetEnt == NULL ) { return 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( m_hTargetEnt->GetAbsOrigin(), ( asw_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_ASW_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_TASK( TASK_FEAR_FIND_COVER_FROM_ENEMY ) 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_FEAR_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_FEAR_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" ); DEFINE_SCHEDULE ( SCHED_FEAR_RUN_FROM_ENEMY, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RUN_FROM_ENEMY_FALLBACK" " TASK_STOP_MOVING 0" " TASK_FEAR_FIND_COVER_FROM_ENEMY 0" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" "" " Interrupts" " COND_NEW_ENEMY" " COND_ENEMY_DEAD" ); AI_END_CUSTOM_SCHEDULE_PROVIDER()