Counter Strike : Global Offensive Source Code
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.

14382 lines
410 KiB

  1. //========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ai_basenpc.h"
  8. #include "fmtstr.h"
  9. #include "activitylist.h"
  10. #include "animation.h"
  11. #include "basecombatweapon.h"
  12. #include "soundent.h"
  13. #include "decals.h"
  14. #include "entitylist.h"
  15. #include "eventqueue.h"
  16. #include "entityapi.h"
  17. #include "bitstring.h"
  18. #include "gamerules.h" // For g_pGameRules
  19. #include "scripted.h"
  20. #include "worldsize.h"
  21. #include "game.h"
  22. #include "shot_manipulator.h"
  23. #ifdef HL2_DLL
  24. #include "ai_interactions.h"
  25. #include "hl2_gamerules.h"
  26. #endif // HL2_DLL
  27. #include "ai_network.h"
  28. #include "ai_networkmanager.h"
  29. #include "ai_pathfinder.h"
  30. #include "ai_node.h"
  31. #include "ai_default.h"
  32. #include "ai_schedule.h"
  33. #include "ai_task.h"
  34. #include "ai_hull.h"
  35. #include "ai_moveprobe.h"
  36. #include "ai_hint.h"
  37. #include "ai_navigator.h"
  38. #include "ai_senses.h"
  39. #include "ai_squadslot.h"
  40. #include "ai_memory.h"
  41. #include "ai_squad.h"
  42. #include "ai_localnavigator.h"
  43. #include "ai_tacticalservices.h"
  44. #include "ai_behavior.h"
  45. #include "ai_dynamiclink.h"
  46. #include "ai_criteria.h"
  47. #include "basegrenade_shared.h"
  48. #include "ammodef.h"
  49. #include "player.h"
  50. #include "sceneentity.h"
  51. #include "ndebugoverlay.h"
  52. #include "mathlib/mathlib.h"
  53. #include "bone_setup.h"
  54. #include "IEffects.h"
  55. #include "vstdlib/random.h"
  56. #include "engine/IEngineSound.h"
  57. #include "tier1/strtools.h"
  58. #include "doors.h"
  59. #include "BasePropDoor.h"
  60. #include "saverestore_utlvector.h"
  61. #include "npcevent.h"
  62. #include "movevars_shared.h"
  63. #include "te_effect_dispatch.h"
  64. #include "globals.h"
  65. #include "saverestore_bitstring.h"
  66. #include "checksum_crc.h"
  67. #include "iservervehicle.h"
  68. #include "filters.h"
  69. #ifdef HL2_DLL
  70. #include "npc_bullseye.h"
  71. #include "hl2_player.h"
  72. #include "weapon_physcannon.h"
  73. #endif
  74. #include "waterbullet.h"
  75. #include "in_buttons.h"
  76. #include "eventlist.h"
  77. #include "globalstate.h"
  78. #include "physics_prop_ragdoll.h"
  79. #if defined( HL2_EP3 ) || defined( INFESTED_DLL )
  80. #include "physics_prop_statue.h"
  81. #endif
  82. #include "vphysics/friction.h"
  83. #include "physics_npc_solver.h"
  84. #include "death_pose.h"
  85. #include "datacache/imdlcache.h"
  86. #include "vstdlib/jobthread.h"
  87. #include "ai_addon.h"
  88. #ifdef HL2_EPISODIC
  89. #include "npc_alyx_episodic.h"
  90. #endif
  91. #ifdef PORTAL
  92. #include "portal_base2d_shared.h"
  93. #endif
  94. #include "env_debughistory.h"
  95. #include "collisionutils.h"
  96. extern ConVar sk_healthkit;
  97. // dvs: for opening doors -- these should probably not be here
  98. #include "ai_route.h"
  99. #include "ai_waypoint.h"
  100. #include "utlbuffer.h"
  101. #include "GameStats.h"
  102. #include "physics_prop_statue.h"
  103. // memdbgon must be the last include file in a .cpp file!!!
  104. #include "tier0/memdbgon.h"
  105. //#define DEBUG_LOOK
  106. bool RagdollManager_SaveImportant( CAI_BaseNPC *pNPC );
  107. #define MIN_PHYSICS_FLINCH_DAMAGE 5.0f
  108. #define NPC_GRENADE_FEAR_DIST 200
  109. #define MAX_GLASS_PENETRATION_DEPTH 16.0f
  110. #define FINDNAMEDENTITY_MAX_ENTITIES 32 // max number of entities to be considered for random entity selection in FindNamedEntity
  111. extern bool g_fDrawLines;
  112. extern short g_sModelIndexLaser; // holds the index for the laser beam
  113. extern short g_sModelIndexLaserDot; // holds the index for the laser beam dot
  114. // Debugging tools
  115. ConVar ai_no_select_box( "ai_no_select_box", "0" );
  116. ConVar ai_show_think_tolerance( "ai_show_think_tolerance", "0" );
  117. ConVar ai_debug_think_ticks( "ai_debug_think_ticks", "0" );
  118. ConVar ai_debug_doors( "ai_debug_doors", "0" );
  119. ConVar ai_debug_enemies( "ai_debug_enemies", "0" );
  120. ConVar ai_rebalance_thinks( "ai_rebalance_thinks", "1" );
  121. ConVar ai_use_efficiency( "ai_use_efficiency", "1" );
  122. ConVar ai_use_frame_think_limits( "ai_use_frame_think_limits", "1" );
  123. ConVar ai_default_efficient( "ai_default_efficient", ( IsGameConsole() ) ? "1" : "0" );
  124. ConVar ai_efficiency_override( "ai_efficiency_override", "0" );
  125. ConVar ai_debug_efficiency( "ai_debug_efficiency", "0" );
  126. ConVar ai_debug_dyninteractions( "ai_debug_dyninteractions", "0", FCVAR_NONE, "Debug the NPC dynamic interaction system." );
  127. ConVar ai_frametime_limit( "ai_frametime_limit", "50", FCVAR_NONE, "frametime limit for min efficiency AIE_NORMAL (in sec's)." );
  128. ConVar ai_use_think_optimizations( "ai_use_think_optimizations", "1" );
  129. ConVar ai_test_moveprobe_ignoresmall( "ai_test_moveprobe_ignoresmall", "0" );
  130. #ifdef HL2_EPISODIC
  131. extern ConVar ai_vehicle_avoidance;
  132. #endif // HL2_EPISODIC
  133. #ifndef _RETAIL
  134. #define ShouldUseEfficiency() ( ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool() )
  135. #define ShouldUseFrameThinkLimits() ( ai_use_think_optimizations.GetBool() && ai_use_frame_think_limits.GetBool() )
  136. #define ShouldRebalanceThinks() ( ai_use_think_optimizations.GetBool() && ai_rebalance_thinks.GetBool() )
  137. #define ShouldDefaultEfficient() ( ai_use_think_optimizations.GetBool() && ai_default_efficient.GetBool() )
  138. #else
  139. #define ShouldUseEfficiency() ( true )
  140. #define ShouldUseFrameThinkLimits() ( true )
  141. #define ShouldRebalanceThinks() ( true )
  142. #define ShouldDefaultEfficient() ( true )
  143. #endif
  144. #ifndef _RETAIL
  145. #define DbgEnemyMsg if ( !ai_debug_enemies.GetBool() ) ; else DevMsg
  146. #else
  147. #define DbgEnemyMsg if ( 0 ) ; else DevMsg
  148. #endif
  149. #ifdef DEBUG_AI_FRAME_THINK_LIMITS
  150. #define DbgFrameLimitMsg DevMsg
  151. #else
  152. #define DbgFrameLimitMsg (void)
  153. #endif
  154. // NPC damage adjusters
  155. ConVar sk_npc_head( "sk_npc_head","2" );
  156. ConVar sk_npc_chest( "sk_npc_chest","1" );
  157. ConVar sk_npc_stomach( "sk_npc_stomach","1" );
  158. ConVar sk_npc_arm( "sk_npc_arm","1" );
  159. ConVar sk_npc_leg( "sk_npc_leg","1" );
  160. ConVar showhitlocation( "showhitlocation", "0" );
  161. // Squad debugging
  162. ConVar ai_debug_squads( "ai_debug_squads", "0" );
  163. ConVar ai_debug_loners( "ai_debug_loners", "0" );
  164. // Shoot trajectory
  165. ConVar ai_lead_time( "ai_lead_time","0.0" );
  166. ConVar ai_shot_stats( "ai_shot_stats", "0" );
  167. ConVar ai_shot_stats_term( "ai_shot_stats_term", "1000" );
  168. ConVar ai_shot_bias( "ai_shot_bias", "1.0" );
  169. ConVar ai_spread_defocused_cone_multiplier( "ai_spread_defocused_cone_multiplier","3.0" );
  170. ConVar ai_spread_cone_focus_time( "ai_spread_cone_focus_time","0.6" );
  171. ConVar ai_spread_pattern_focus_time( "ai_spread_pattern_focus_time","0.8" );
  172. ConVar ai_reaction_delay_idle( "ai_reaction_delay_idle","0.3" );
  173. ConVar ai_reaction_delay_alert( "ai_reaction_delay_alert", "0.1" );
  174. ConVar ai_strong_optimizations( "ai_strong_optimizations", ( IsGameConsole() ) ? "1" : "0" );
  175. bool AIStrongOpt( void )
  176. {
  177. return ai_strong_optimizations.GetBool();
  178. }
  179. //-----------------------------------------------------------------------------
  180. //
  181. // Crude frame timings
  182. //
  183. CFastTimer g_AIRunTimer;
  184. CFastTimer g_AIPostRunTimer;
  185. CFastTimer g_AIMoveTimer;
  186. CFastTimer g_AIConditionsTimer;
  187. CFastTimer g_AIPrescheduleThinkTimer;
  188. CFastTimer g_AIMaintainScheduleTimer;
  189. //-----------------------------------------------------------------------------
  190. //-----------------------------------------------------------------------------
  191. CAI_Manager g_AI_Manager;
  192. //-------------------------------------
  193. CAI_Manager::CAI_Manager()
  194. {
  195. m_AIs.EnsureCapacity( MAX_AIS );
  196. }
  197. //-------------------------------------
  198. CAI_BaseNPC **CAI_Manager::AccessAIs()
  199. {
  200. if (m_AIs.Count())
  201. return &m_AIs[0];
  202. return NULL;
  203. }
  204. //-------------------------------------
  205. int CAI_Manager::NumAIs()
  206. {
  207. return m_AIs.Count();
  208. }
  209. //-------------------------------------
  210. void CAI_Manager::AddAI( CAI_BaseNPC *pAI )
  211. {
  212. m_AIs.AddToTail( pAI );
  213. }
  214. //-------------------------------------
  215. void CAI_Manager::RemoveAI( CAI_BaseNPC *pAI )
  216. {
  217. int i = m_AIs.Find( pAI );
  218. if ( i != -1 )
  219. m_AIs.FastRemove( i );
  220. }
  221. //-----------------------------------------------------------------------------
  222. // ================================================================
  223. // Init static data
  224. // ================================================================
  225. int CAI_BaseNPC::m_nDebugBits = 0;
  226. CAI_BaseNPC* CAI_BaseNPC::m_pDebugNPC = NULL;
  227. int CAI_BaseNPC::m_nDebugPauseIndex = -1;
  228. CAI_ClassScheduleIdSpace CAI_BaseNPC::gm_ClassScheduleIdSpace( true );
  229. CAI_GlobalScheduleNamespace CAI_BaseNPC::gm_SchedulingSymbols;
  230. CAI_LocalIdSpace CAI_BaseNPC::gm_SquadSlotIdSpace( true );
  231. string_t CAI_BaseNPC::gm_iszPlayerSquad;
  232. int CAI_BaseNPC::gm_iNextThinkRebalanceTick;
  233. float CAI_BaseNPC::gm_flTimeLastSpawn;
  234. int CAI_BaseNPC::gm_nSpawnedThisFrame;
  235. CSimpleSimTimer CAI_BaseNPC::m_AnyUpdateEnemyPosTimer;
  236. //
  237. // Deferred Navigation calls go here
  238. //
  239. CPostFrameNavigationHook g_PostFrameNavigationHook;
  240. CPostFrameNavigationHook *PostFrameNavigationSystem( void )
  241. {
  242. return &g_PostFrameNavigationHook;
  243. }
  244. //-----------------------------------------------------------------------------
  245. // Purpose:
  246. //-----------------------------------------------------------------------------
  247. bool CPostFrameNavigationHook::Init( void )
  248. {
  249. m_Functors.Purge();
  250. m_bGameFrameRunning = false;
  251. return true;
  252. }
  253. // Main query job
  254. CJob *g_pQueuedNavigationQueryJob = NULL;
  255. static void ProcessNavigationQueries( CFunctor **pData, unsigned int nCount )
  256. {
  257. // Run all queued navigation on a separate thread
  258. for ( int i = 0; i < nCount; i++ )
  259. {
  260. (*pData[i])();
  261. }
  262. }
  263. //-----------------------------------------------------------------------------
  264. // Purpose:
  265. //-----------------------------------------------------------------------------
  266. void CPostFrameNavigationHook::FrameUpdatePreEntityThink( void )
  267. {
  268. // If the thread is executing, then wait for it to finish
  269. if ( g_pQueuedNavigationQueryJob )
  270. {
  271. g_pQueuedNavigationQueryJob->WaitForFinishAndRelease();
  272. g_pQueuedNavigationQueryJob = NULL;
  273. m_Functors.Purge();
  274. }
  275. if ( ai_post_frame_navigation.GetBool() == false )
  276. return;
  277. SetGrameFrameRunning( true );
  278. }
  279. //-----------------------------------------------------------------------------
  280. // Purpose: Now that the game frame has collected all the navigation queries, service them
  281. //-----------------------------------------------------------------------------
  282. void CPostFrameNavigationHook::FrameUpdatePostEntityThink( void )
  283. {
  284. if ( ai_post_frame_navigation.GetBool() == false )
  285. return;
  286. // The guts of the NPC will check against this to decide whether or not to queue its navigation calls
  287. SetGrameFrameRunning( false );
  288. // Throw this off to a thread job
  289. g_pQueuedNavigationQueryJob = ThreadExecute( &ProcessNavigationQueries, m_Functors.Base(), m_Functors.Count() );
  290. }
  291. //-----------------------------------------------------------------------------
  292. // Purpose: Queue up our navigation call
  293. //-----------------------------------------------------------------------------
  294. void CPostFrameNavigationHook::EnqueueEntityNavigationQuery( CAI_BaseNPC *pNPC, CFunctor *pFunctor )
  295. {
  296. if ( ai_post_frame_navigation.GetBool() == false )
  297. return;
  298. m_Functors.AddToTail( pFunctor );
  299. pNPC->SetNavigationDeferred( true );
  300. }
  301. //
  302. // Deferred Navigation calls go here
  303. //
  304. // ================================================================
  305. // Class Methods
  306. // ================================================================
  307. //-------------------------------------
  308. void CAI_BaseNPC::AddBehavior( CAI_BehaviorBase *pBehavior )
  309. {
  310. #ifdef DEBUG
  311. Assert( m_Behaviors.Find( pBehavior ) == m_Behaviors.InvalidIndex() );
  312. for ( int i = 0; i < m_Behaviors.Count(); i++)
  313. {
  314. Assert( typeid(*m_Behaviors[i]) != typeid(*pBehavior) );
  315. }
  316. #endif
  317. m_Behaviors.AddToTail( pBehavior );
  318. pBehavior->SetOuter( this );
  319. pBehavior->SetBackBridge( this );
  320. }
  321. //-------------------------------------
  322. void CAI_BaseNPC::RemoveAndDestroyBehavior( CAI_BehaviorBase *pBehavior )
  323. {
  324. #ifdef DEBUG
  325. bool bFound = false;
  326. for ( int i = 0; i < m_Behaviors.Count(); i++)
  327. {
  328. if ( typeid(*m_Behaviors[i]) == typeid(*pBehavior) )
  329. {
  330. bFound = true;
  331. break;
  332. }
  333. }
  334. Assert( bFound && !pBehavior->IsRunning() );
  335. #endif
  336. m_Behaviors.FindAndRemove( pBehavior );
  337. delete pBehavior;
  338. }
  339. //-------------------------------------
  340. bool CAI_BaseNPC::BehaviorSelectSchedule()
  341. {
  342. for ( int i = 0; i < m_Behaviors.Count(); i++ )
  343. {
  344. if ( m_Behaviors[i]->CanSelectSchedule() && ShouldBehaviorSelectSchedule( m_Behaviors[i] ) )
  345. {
  346. DeferSchedulingToBehavior( m_Behaviors[i] );
  347. return true;
  348. }
  349. }
  350. DeferSchedulingToBehavior( NULL );
  351. return false;
  352. }
  353. //------------------------------------
  354. void CAI_BaseNPC::SetPrimaryBehavior( CAI_BehaviorBase *pNewBehavior )
  355. {
  356. bool change = ( m_pPrimaryBehavior != pNewBehavior );
  357. CAI_BehaviorBase *pOldBehavior = m_pPrimaryBehavior;
  358. m_pPrimaryBehavior = pNewBehavior;
  359. if ( change )
  360. {
  361. if ( m_pPrimaryBehavior )
  362. {
  363. m_pPrimaryBehavior->BeginScheduleSelection();
  364. g_bBehaviorHost_PreventBaseClassGatherConditions = true;
  365. m_pPrimaryBehavior->GatherConditions();
  366. g_bBehaviorHost_PreventBaseClassGatherConditions = false;
  367. }
  368. if ( pOldBehavior )
  369. {
  370. pOldBehavior->EndScheduleSelection();
  371. VacateStrategySlot();
  372. }
  373. OnChangeRunningBehavior( pOldBehavior, pNewBehavior );
  374. }
  375. }
  376. //-------------------------------------
  377. bool CAI_BaseNPC::OnBehaviorChangeStatus( CAI_BehaviorBase *pBehavior, bool fCanFinishSchedule )
  378. {
  379. if ( pBehavior == GetPrimaryBehavior() && !pBehavior->CanSelectSchedule() && !fCanFinishSchedule )
  380. {
  381. DeferSchedulingToBehavior( NULL );
  382. return true;
  383. }
  384. return false;
  385. }
  386. //-------------------------------------
  387. CAI_BehaviorBase *CAI_BaseNPC::DeferSchedulingToBehavior( CAI_BehaviorBase *pNewBehavior )
  388. {
  389. CAI_BehaviorBase *pOldBehavior = m_pPrimaryBehavior;
  390. SetPrimaryBehavior( pNewBehavior );
  391. return pOldBehavior;
  392. }
  393. //-----------------------------------------------------------------------------
  394. // Purpose: Static debug function to clear schedules for all NPCS
  395. // Input :
  396. // Output :
  397. //-----------------------------------------------------------------------------
  398. void CAI_BaseNPC::ClearAllSchedules(void)
  399. {
  400. CAI_BaseNPC *npc = gEntList.NextEntByClass( (CAI_BaseNPC *)NULL );
  401. while (npc)
  402. {
  403. npc->ClearSchedule( "CAI_BaseNPC::ClearAllSchedules" );
  404. npc->GetNavigator()->ClearGoal();
  405. npc = gEntList.NextEntByClass(npc);
  406. }
  407. }
  408. // ==============================================================================
  409. //-----------------------------------------------------------------------------
  410. // Purpose:
  411. // Input :
  412. // Output :
  413. //-----------------------------------------------------------------------------
  414. bool CAI_BaseNPC::Event_Gibbed( const CTakeDamageInfo &info )
  415. {
  416. bool gibbed = CorpseGib( info );
  417. if ( gibbed )
  418. {
  419. // don't remove players!
  420. UTIL_Remove( this );
  421. SetThink( NULL ); //We're going away, so don't think anymore.
  422. }
  423. else
  424. {
  425. CorpseFade();
  426. }
  427. return gibbed;
  428. }
  429. //=========================================================
  430. // GetFlinchActivity - determines the best type of flinch
  431. // anim to play.
  432. //=========================================================
  433. Activity CAI_BaseNPC::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
  434. {
  435. Activity flinchActivity;
  436. switch ( LastHitGroup() )
  437. {
  438. // pick a region-specific flinch
  439. case HITGROUP_HEAD:
  440. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_HEAD : ACT_FLINCH_HEAD;
  441. break;
  442. case HITGROUP_STOMACH:
  443. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_STOMACH : ACT_FLINCH_STOMACH;
  444. break;
  445. case HITGROUP_LEFTARM:
  446. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTARM : ACT_FLINCH_LEFTARM;
  447. break;
  448. case HITGROUP_RIGHTARM:
  449. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTARM : ACT_FLINCH_RIGHTARM;
  450. break;
  451. case HITGROUP_LEFTLEG:
  452. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTLEG : ACT_FLINCH_LEFTLEG;
  453. break;
  454. case HITGROUP_RIGHTLEG:
  455. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTLEG : ACT_FLINCH_RIGHTLEG;
  456. break;
  457. case HITGROUP_CHEST:
  458. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_CHEST : ACT_FLINCH_CHEST;
  459. break;
  460. case HITGROUP_GEAR:
  461. case HITGROUP_GENERIC:
  462. default:
  463. // just get a generic flinch.
  464. if ( bHeavyDamage )
  465. {
  466. flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH;
  467. }
  468. else
  469. {
  470. flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
  471. }
  472. break;
  473. }
  474. // do we have a sequence for the ideal activity?
  475. if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE )
  476. {
  477. if ( bHeavyDamage )
  478. {
  479. flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH;
  480. // If we fail at finding a big flinch, resort to a small one
  481. if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE )
  482. {
  483. flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
  484. }
  485. }
  486. else
  487. {
  488. flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
  489. }
  490. }
  491. return flinchActivity;
  492. }
  493. //-----------------------------------------------------------------------------
  494. // Purpose:
  495. // Input :
  496. //-----------------------------------------------------------------------------
  497. void CAI_BaseNPC::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput )
  498. {
  499. if ( !m_bDidDeathCleanup )
  500. {
  501. m_bDidDeathCleanup = true;
  502. if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
  503. {
  504. // bail out of this script here
  505. m_hCine->CancelScript();
  506. // now keep going with the death code
  507. }
  508. if ( GetHintNode() )
  509. {
  510. GetHintNode()->Unlock();
  511. SetHintNode( NULL );
  512. }
  513. if( bFireDeathOutput )
  514. {
  515. m_OnDeath.FireOutput( pCulprit, this );
  516. }
  517. // Vacate any strategy slot I might have
  518. VacateStrategySlot();
  519. // Remove from squad if in one
  520. if (m_pSquad)
  521. {
  522. // If I'm in idle it means that I didn't see who killed me
  523. // and my squad is still in idle state. Tell squad we have
  524. // an enemy to wake them up and put the enemy position at
  525. // my death position
  526. if ( m_NPCState == NPC_STATE_IDLE && pCulprit)
  527. {
  528. // If we already have some danger memory, don't do this cheat
  529. if ( GetEnemies()->GetDangerMemory() == NULL )
  530. {
  531. UpdateEnemyMemory( pCulprit, GetAbsOrigin() );
  532. }
  533. }
  534. // Remove from squad
  535. m_pSquad->RemoveFromSquad(this, true);
  536. m_pSquad = NULL;
  537. }
  538. RemoveActorFromScriptedScenes( this, false /*all scenes*/ );
  539. }
  540. else
  541. DevMsg( "Unexpected double-death-cleanup\n" );
  542. }
  543. void CAI_BaseNPC::SelectDeathPose( const CTakeDamageInfo &info )
  544. {
  545. if ( !GetModelPtr() || (info.GetDamageType() & DMG_PREVENT_PHYSICS_FORCE) )
  546. return;
  547. if ( ShouldPickADeathPose() == false )
  548. return;
  549. Activity aActivity = ACT_INVALID;
  550. int iDeathFrame = 0;
  551. SelectDeathPoseActivityAndFrame( this, info, LastHitGroup(), aActivity, iDeathFrame );
  552. if ( aActivity == ACT_INVALID )
  553. {
  554. SetDeathPose( ACT_INVALID );
  555. SetDeathPoseFrame( 0 );
  556. return;
  557. }
  558. SetDeathPose( SelectWeightedSequence( aActivity ) );
  559. SetDeathPoseFrame( iDeathFrame );
  560. }
  561. //-----------------------------------------------------------------------------
  562. // Purpose:
  563. // Input :
  564. //-----------------------------------------------------------------------------
  565. void CAI_BaseNPC::Event_Killed( const CTakeDamageInfo &info )
  566. {
  567. if (IsCurSchedule(SCHED_NPC_FREEZE))
  568. {
  569. // We're frozen; don't die.
  570. return;
  571. }
  572. Wake( false );
  573. //Adrian: Select a death pose to extrapolate the ragdoll's velocity.
  574. SelectDeathPose( info );
  575. m_lifeState = LIFE_DYING;
  576. CleanupOnDeath( info.GetAttacker() );
  577. StopLoopingSounds();
  578. DeathSound( info );
  579. if ( ( GetFlags() & FL_NPC ) && ( ShouldGib( info ) == false ) )
  580. {
  581. SetTouch( NULL );
  582. }
  583. BaseClass::Event_Killed( info );
  584. if ( m_bFadeCorpse )
  585. {
  586. m_bImportanRagdoll = RagdollManager_SaveImportant( this );
  587. }
  588. // Make sure this condition is fired too (OnTakeDamage breaks out before this happens on death)
  589. SetCondition( COND_LIGHT_DAMAGE );
  590. SetIdealState( NPC_STATE_DEAD );
  591. // Some characters rely on getting a state transition, even to death.
  592. // zombies, for instance. When a character becomes a ragdoll, their
  593. // server entity ceases to think, so we have to set the dead state here
  594. // because the AI code isn't going to pick up the change on the next think
  595. // for us.
  596. // Adrian - Only set this if we are going to become a ragdoll. We still want to
  597. // select SCHED_DIE or do something special when this NPC dies and we wont
  598. // catch the change of state if we set this to whatever the ideal state is.
  599. if ( CanBecomeRagdoll() || IsRagdoll() )
  600. SetState( NPC_STATE_DEAD );
  601. // If the remove-no-ragdoll flag is set in the damage type, we're being
  602. // told to remove ourselves immediately on death. This is used when something
  603. // else has some special reason for us to vanish instead of creating a ragdoll.
  604. // i.e. The barnacle does this because it's already got a ragdoll for us.
  605. if ( info.GetDamageType() & DMG_REMOVENORAGDOLL )
  606. {
  607. if ( !IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
  608. {
  609. // Go away
  610. RemoveDeferred();
  611. }
  612. }
  613. }
  614. //-----------------------------------------------------------------------------
  615. void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
  616. {
  617. BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
  618. #ifdef HL2_EPISODIC
  619. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  620. if ( pPlayer && pPlayer->IRelationType( this ) != D_LI )
  621. {
  622. CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx();
  623. if ( alyx )
  624. {
  625. alyx->EnemyIgnited( this );
  626. }
  627. }
  628. #endif
  629. }
  630. //-----------------------------------------------------------------------------
  631. ConVar ai_block_damage( "ai_block_damage","0" );
  632. bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info )
  633. {
  634. if ( ai_block_damage.GetBool() )
  635. return false;
  636. // FIXME: hook a friendly damage filter to the npc instead?
  637. if ( (CapabilitiesGet() & bits_CAP_FRIENDLY_DMG_IMMUNE) && info.GetAttacker() && info.GetAttacker() != this )
  638. {
  639. // check attackers relationship with me
  640. CBaseCombatCharacter *npcEnemy = info.GetAttacker()->MyCombatCharacterPointer();
  641. bool bHitByVehicle = false;
  642. if ( !npcEnemy )
  643. {
  644. if ( info.GetAttacker()->GetServerVehicle() )
  645. {
  646. bHitByVehicle = true;
  647. }
  648. }
  649. if ( bHitByVehicle || (npcEnemy && npcEnemy->IRelationType( this ) == D_LI) )
  650. {
  651. m_fNoDamageDecal = true;
  652. if ( npcEnemy && npcEnemy->IsPlayer() )
  653. {
  654. m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this );
  655. // This also counts as being harmed by player's squad.
  656. m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
  657. }
  658. return false;
  659. }
  660. }
  661. if ( !BaseClass::PassesDamageFilter( info ) )
  662. {
  663. m_fNoDamageDecal = true;
  664. return false;
  665. }
  666. return true;
  667. }
  668. //-----------------------------------------------------------------------------
  669. // Purpose:
  670. // Input :
  671. // Output :
  672. //-----------------------------------------------------------------------------
  673. int CAI_BaseNPC::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  674. {
  675. Forget( bits_MEMORY_INCOVER );
  676. bool bIsBelowHalfHealthBefore = ( m_iHealth <= ( m_iMaxHealth / 2 ) );
  677. if ( !BaseClass::OnTakeDamage_Alive( info ) )
  678. return 0;
  679. if ( GetSleepState() == AISS_WAITING_FOR_THREAT )
  680. Wake();
  681. // NOTE: This must happen after the base class is called; we need to reduce
  682. // health before the pain sound, since some NPCs use the final health
  683. // level as a modifier to determine which pain sound to use.
  684. // REVISIT: Combine soldiers shoot each other a lot and then talk about it
  685. // this improves that case a bunch, but it seems kind of harsh.
  686. if ( !m_pSquad || !m_pSquad->SquadIsMember( info.GetAttacker() ) )
  687. {
  688. PainSound( info );// "Ouch!"
  689. }
  690. // See if we're running a dynamic interaction that should break when I am damaged.
  691. if ( IsActiveDynamicInteraction() )
  692. {
  693. ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
  694. if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_DAMAGE )
  695. {
  696. // Can only break when we're in the action anim
  697. if ( m_hCine->IsPlayingAction() )
  698. {
  699. m_hCine->StopActionLoop( true );
  700. }
  701. }
  702. }
  703. // If we're not allowed to die, refuse to die
  704. // Allow my interaction partner to kill me though
  705. if ( m_iHealth <= 0 && HasInteractionCantDie() && info.GetAttacker() != m_hInteractionPartner )
  706. {
  707. m_iHealth = 1;
  708. }
  709. #if 0
  710. // HACKHACK Don't kill npcs in a script. Let them break their scripts first
  711. // THIS is a Half-Life 1 hack that's not cutting the mustard in the scripts
  712. // that have been authored for Half-Life 2 thus far. (sjb)
  713. if ( m_NPCState == NPC_STATE_SCRIPT )
  714. {
  715. SetCondition( COND_LIGHT_DAMAGE );
  716. }
  717. #endif
  718. // -----------------------------------
  719. // Fire outputs
  720. // -----------------------------------
  721. if ( m_flLastDamageTime != gpGlobals->curtime )
  722. {
  723. // only fire once per frame
  724. m_OnDamaged.FireOutput( info.GetAttacker(), this);
  725. if( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
  726. {
  727. m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this );
  728. // This also counts as being harmed by player's squad.
  729. m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
  730. }
  731. else
  732. {
  733. // See if the person that injured me is an NPC.
  734. CAI_BaseNPC *pAttacker = dynamic_cast<CAI_BaseNPC *>( info.GetAttacker() );
  735. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  736. if( pAttacker && pAttacker->IsAlive() && pPlayer )
  737. {
  738. if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() )
  739. {
  740. m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
  741. }
  742. }
  743. }
  744. }
  745. if( (info.GetDamageType() & DMG_CRUSH) && !(info.GetDamageType() & DMG_PHYSGUN) && info.GetDamage() >= MIN_PHYSICS_FLINCH_DAMAGE )
  746. {
  747. SetCondition( COND_PHYSICS_DAMAGE );
  748. }
  749. if ( !bIsBelowHalfHealthBefore && ( m_iHealth <= ( m_iMaxHealth / 2 ) ) )
  750. {
  751. m_OnHalfHealth.FireOutput( info.GetAttacker(), this );
  752. }
  753. // react to the damage (get mad)
  754. if ( ( (GetFlags() & FL_NPC) == 0 ) || !info.GetAttacker() )
  755. return 1;
  756. // If the attacker was an NPC or client update my position memory
  757. if ( info.GetAttacker()->GetFlags() & (FL_NPC | FL_CLIENT) )
  758. {
  759. // ------------------------------------------------------------------
  760. // DO NOT CHANGE THIS CODE W/O CONSULTING
  761. // Only update information about my attacker I don't see my attacker
  762. // ------------------------------------------------------------------
  763. if ( !FInViewCone( info.GetAttacker() ) || !FVisible( info.GetAttacker() ) )
  764. {
  765. // -------------------------------------------------------------
  766. // If I have an inflictor (enemy / grenade) update memory with
  767. // position of inflictor, otherwise update with an position
  768. // estimate for where the attack came from
  769. // ------------------------------------------------------
  770. Vector vAttackPos;
  771. if (info.GetInflictor())
  772. {
  773. vAttackPos = info.GetInflictor()->GetAbsOrigin();
  774. }
  775. else
  776. {
  777. vAttackPos = (GetAbsOrigin() + ( g_vecAttackDir * 64 ));
  778. }
  779. // ----------------------------------------------------------------
  780. // If I already have an enemy, assume that the attack
  781. // came from the enemy and update my enemy's position
  782. // unless I already know about the attacker or I can see my enemy
  783. // ----------------------------------------------------------------
  784. if ( GetEnemy() != NULL &&
  785. !GetEnemies()->HasMemory( info.GetAttacker() ) &&
  786. !HasCondition(COND_SEE_ENEMY) )
  787. {
  788. UpdateEnemyMemory(GetEnemy(), vAttackPos, GetEnemy());
  789. }
  790. // ----------------------------------------------------------------
  791. // If I already know about this enemy, update his position
  792. // ----------------------------------------------------------------
  793. else if (GetEnemies()->HasMemory( info.GetAttacker() ))
  794. {
  795. UpdateEnemyMemory(info.GetAttacker(), vAttackPos);
  796. }
  797. // -----------------------------------------------------------------
  798. // Otherwise just note the position, but don't add enemy to my list
  799. // -----------------------------------------------------------------
  800. else
  801. {
  802. UpdateEnemyMemory(NULL, vAttackPos);
  803. }
  804. }
  805. // add pain to the conditions
  806. if ( IsLightDamage( info ) )
  807. {
  808. SetCondition( COND_LIGHT_DAMAGE );
  809. }
  810. if ( IsHeavyDamage( info ) )
  811. {
  812. SetCondition( COND_HEAVY_DAMAGE );
  813. }
  814. ForceGatherConditions();
  815. // Keep track of how much consecutive damage I have recieved
  816. if ((gpGlobals->curtime - m_flLastDamageTime) < 1.0)
  817. {
  818. m_flSumDamage += info.GetDamage();
  819. }
  820. else
  821. {
  822. m_flSumDamage = info.GetDamage();
  823. }
  824. m_flLastDamageTime = gpGlobals->curtime;
  825. if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
  826. m_flLastPlayerDamageTime = gpGlobals->curtime;
  827. GetEnemies()->OnTookDamageFrom( info.GetAttacker() );
  828. if (m_flSumDamage > m_iMaxHealth*0.3)
  829. {
  830. SetCondition(COND_REPEATED_DAMAGE);
  831. }
  832. NotifyFriendsOfDamage( info.GetAttacker() );
  833. }
  834. // ---------------------------------------------------------------
  835. // Insert a combat sound so that nearby NPCs know I've been hit
  836. // ---------------------------------------------------------------
  837. CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1024, 0.5, this, SOUNDENT_CHANNEL_INJURY );
  838. return 1;
  839. }
  840. //=========================================================
  841. // OnTakeDamage_Dying - takedamage function called when a npc's
  842. // corpse is damaged.
  843. //=========================================================
  844. int CAI_BaseNPC::OnTakeDamage_Dying( const CTakeDamageInfo &info )
  845. {
  846. if ( info.GetDamageType() & DMG_PLASMA )
  847. {
  848. if ( m_takedamage != DAMAGE_EVENTS_ONLY )
  849. {
  850. m_iHealth -= info.GetDamage();
  851. if (m_iHealth < -500)
  852. {
  853. UTIL_Remove(this);
  854. }
  855. }
  856. }
  857. return BaseClass::OnTakeDamage_Dying( info );
  858. }
  859. //=========================================================
  860. // OnTakeDamage_Dead - takedamage function called when a npc's
  861. // corpse is damaged.
  862. //=========================================================
  863. int CAI_BaseNPC::OnTakeDamage_Dead( const CTakeDamageInfo &info )
  864. {
  865. Vector vecDir;
  866. // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit).
  867. vecDir = vec3_origin;
  868. if ( info.GetInflictor() )
  869. {
  870. vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter();
  871. VectorNormalize( vecDir );
  872. g_vecAttackDir = vecDir;
  873. }
  874. #if 0// turn this back on when the bounding box issues are resolved.
  875. SetGroundEntity( NULL );
  876. GetLocalOrigin().z += 1;
  877. // let the damage scoot the corpse around a bit.
  878. if ( info.GetInflictor() && (info.GetAttacker()->GetSolid() != SOLID_TRIGGER) )
  879. {
  880. ApplyAbsVelocityImpulse( vecDir * -DamageForce( flDamage ) );
  881. }
  882. #endif
  883. // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse.
  884. if ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) )
  885. {
  886. // Accumulate corpse gibbing damage, so you can gib with multiple hits
  887. if ( m_takedamage != DAMAGE_EVENTS_ONLY )
  888. {
  889. m_iHealth -= info.GetDamage() * 0.1;
  890. }
  891. }
  892. if ( info.GetDamageType() & DMG_PLASMA )
  893. {
  894. if ( m_takedamage != DAMAGE_EVENTS_ONLY )
  895. {
  896. m_iHealth -= info.GetDamage();
  897. if (m_iHealth < -500)
  898. {
  899. UTIL_Remove(this);
  900. }
  901. }
  902. }
  903. return 1;
  904. }
  905. //-----------------------------------------------------------------------------
  906. //-----------------------------------------------------------------------------
  907. void CAI_BaseNPC::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity )
  908. {
  909. CAI_BaseNPC *pAttacker = pAttackerEntity->MyNPCPointer();
  910. if ( pAttacker )
  911. {
  912. const Vector &origin = GetAbsOrigin();
  913. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  914. {
  915. const float NEAR_Z = 10*12;
  916. const float NEAR_XY_SQ = Square( 50*12 );
  917. CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i];
  918. if ( pNpc && pNpc != this )
  919. {
  920. const Vector &originNpc = pNpc->GetAbsOrigin();
  921. if ( fabsf( originNpc.z - origin.z ) < NEAR_Z )
  922. {
  923. if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ )
  924. {
  925. if ( pNpc->GetSquad() == GetSquad() || IRelationType( pNpc ) == D_LI )
  926. pNpc->OnFriendDamaged( this, pAttacker );
  927. }
  928. }
  929. }
  930. }
  931. }
  932. }
  933. //-----------------------------------------------------------------------------
  934. //-----------------------------------------------------------------------------
  935. void CAI_BaseNPC::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity *pAttacker )
  936. {
  937. if ( GetSleepState() != AISS_WAITING_FOR_INPUT )
  938. {
  939. float distSqToThreat = ( GetAbsOrigin() - pAttacker->GetAbsOrigin() ).LengthSqr();
  940. if ( GetSleepState() != AISS_AWAKE && distSqToThreat < Square( 20 * 12 ) )
  941. Wake();
  942. if ( distSqToThreat < Square( 50 * 12 ) )
  943. ForceGatherConditions();
  944. }
  945. }
  946. //-----------------------------------------------------------------------------
  947. //-----------------------------------------------------------------------------
  948. bool CAI_BaseNPC::IsLightDamage( const CTakeDamageInfo &info )
  949. {
  950. // ALL nonzero damage is light damage! Mask off COND_LIGHT_DAMAGE if you want to ignore light damage.
  951. return ( info.GetDamage() > 0 );
  952. }
  953. bool CAI_BaseNPC::IsHeavyDamage( const CTakeDamageInfo &info )
  954. {
  955. return ( info.GetDamage() > 20 );
  956. }
  957. void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, int iClassIgnore, CBaseEntity *pEntityIgnore )
  958. {
  959. RadiusDamage( info, GetAbsOrigin(), info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore );
  960. }
  961. void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrc, int iClassIgnore, CBaseEntity *pEntityIgnore )
  962. {
  963. RadiusDamage( info, vecSrc, info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore );
  964. }
  965. //-----------------------------------------------------------------------------
  966. // Set the contents types that are solid by default to all NPCs
  967. //-----------------------------------------------------------------------------
  968. unsigned int CAI_BaseNPC::PhysicsSolidMaskForEntity( void ) const
  969. {
  970. return MASK_NPCSOLID;
  971. }
  972. //=========================================================
  973. //-----------------------------------------------------------------------------
  974. // Purpose:
  975. //-----------------------------------------------------------------------------
  976. void CAI_BaseNPC::DecalTrace( trace_t *pTrace, char const *decalName )
  977. {
  978. if ( m_fNoDamageDecal )
  979. {
  980. m_fNoDamageDecal = false;
  981. // @Note (toml 04-23-03): e3, don't decal face on damage if still alive
  982. return;
  983. }
  984. BaseClass::DecalTrace( pTrace, decalName );
  985. }
  986. //-----------------------------------------------------------------------------
  987. // Purpose:
  988. //-----------------------------------------------------------------------------
  989. void CAI_BaseNPC::ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName )
  990. {
  991. if ( m_fNoDamageDecal )
  992. {
  993. m_fNoDamageDecal = false;
  994. // @Note (toml 04-23-03): e3, don't decal face on damage if still alive
  995. return;
  996. }
  997. BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName );
  998. }
  999. //---------------------------------------------------------
  1000. // Return the number by which to multiply incoming damage
  1001. // based on the hitgroup it hits. This exists mainly so
  1002. // that individual NPC's can have more or less resistance
  1003. // to damage done to certain hitgroups.
  1004. //---------------------------------------------------------
  1005. float CAI_BaseNPC::GetHitgroupDamageMultiplier( int iHitGroup, const CTakeDamageInfo &info )
  1006. {
  1007. switch( iHitGroup )
  1008. {
  1009. case HITGROUP_GENERIC:
  1010. return 1.0f;
  1011. case HITGROUP_HEAD:
  1012. return sk_npc_head.GetFloat();
  1013. case HITGROUP_CHEST:
  1014. return sk_npc_chest.GetFloat();
  1015. case HITGROUP_STOMACH:
  1016. return sk_npc_stomach.GetFloat();
  1017. case HITGROUP_LEFTARM:
  1018. case HITGROUP_RIGHTARM:
  1019. return sk_npc_arm.GetFloat();
  1020. case HITGROUP_LEFTLEG:
  1021. case HITGROUP_RIGHTLEG:
  1022. return sk_npc_leg.GetFloat();
  1023. default:
  1024. return 1.0f;
  1025. }
  1026. }
  1027. //=========================================================
  1028. // TraceAttack
  1029. //=========================================================
  1030. void CAI_BaseNPC::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
  1031. {
  1032. m_fNoDamageDecal = false;
  1033. if ( m_takedamage == DAMAGE_NO )
  1034. return;
  1035. CTakeDamageInfo subInfo = info;
  1036. SetLastHitGroup( ptr->hitgroup );
  1037. m_nForceBone = ptr->physicsbone; // save this bone for physics forces
  1038. Assert( m_nForceBone > -255 && m_nForceBone < 256 );
  1039. bool bDebug = showhitlocation.GetBool();
  1040. switch ( ptr->hitgroup )
  1041. {
  1042. case HITGROUP_GENERIC:
  1043. if( bDebug ) DevMsg("Hit Location: Generic\n");
  1044. break;
  1045. // hit gear, react but don't bleed
  1046. case HITGROUP_GEAR:
  1047. subInfo.SetDamage( 0.01 );
  1048. ptr->hitgroup = HITGROUP_GENERIC;
  1049. if( bDebug ) DevMsg("Hit Location: Gear\n");
  1050. break;
  1051. case HITGROUP_HEAD:
  1052. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  1053. if( bDebug ) DevMsg("Hit Location: Head\n");
  1054. break;
  1055. case HITGROUP_CHEST:
  1056. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  1057. if( bDebug ) DevMsg("Hit Location: Chest\n");
  1058. break;
  1059. case HITGROUP_STOMACH:
  1060. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  1061. if( bDebug ) DevMsg("Hit Location: Stomach\n");
  1062. break;
  1063. case HITGROUP_LEFTARM:
  1064. case HITGROUP_RIGHTARM:
  1065. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  1066. if( bDebug ) DevMsg("Hit Location: Left/Right Arm\n");
  1067. break
  1068. ;
  1069. case HITGROUP_LEFTLEG:
  1070. case HITGROUP_RIGHTLEG:
  1071. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  1072. if( bDebug ) DevMsg("Hit Location: Left/Right Leg\n");
  1073. break;
  1074. default:
  1075. if( bDebug ) DevMsg("Hit Location: UNKNOWN\n");
  1076. break;
  1077. }
  1078. if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK ) )
  1079. {
  1080. if( !IsPlayer() || ( IsPlayer() && g_pGameRules->IsMultiplayer() ) )
  1081. {
  1082. // NPC's always bleed. Players only bleed in multiplayer.
  1083. SpawnBlood( ptr->endpos, vecDir, BloodColor(), subInfo.GetDamage() );// a little surface blood.
  1084. }
  1085. TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() );
  1086. if ( ptr->hitgroup == HITGROUP_HEAD && m_iHealth - subInfo.GetDamage() > 0 )
  1087. {
  1088. m_fNoDamageDecal = true;
  1089. }
  1090. }
  1091. // Airboat gun will impart major force if it's about to kill him....
  1092. if ( info.GetDamageType() & DMG_AIRBOAT )
  1093. {
  1094. if ( subInfo.GetDamage() >= GetHealth() )
  1095. {
  1096. float flMagnitude = subInfo.GetDamageForce().Length();
  1097. if ( (flMagnitude != 0.0f) && (flMagnitude < 400.0f * 65.0f) )
  1098. {
  1099. subInfo.ScaleDamageForce( 400.0f * 65.0f / flMagnitude );
  1100. }
  1101. }
  1102. }
  1103. if( info.GetInflictor() )
  1104. {
  1105. subInfo.SetInflictor( info.GetInflictor() );
  1106. }
  1107. else
  1108. {
  1109. subInfo.SetInflictor( info.GetAttacker() );
  1110. }
  1111. AddMultiDamage( subInfo, this );
  1112. }
  1113. //-----------------------------------------------------------------------------
  1114. // Purpose: Checks if point is in spread angle between source and target Pos
  1115. // Used to prevent friendly fire
  1116. // Input : Source of attack, target position, spread angle
  1117. // Output :
  1118. //-----------------------------------------------------------------------------
  1119. bool CAI_BaseNPC::PointInSpread( CBaseCombatCharacter *pCheckEntity, const Vector &sourcePos, const Vector &targetPos, const Vector &testPoint, float flSpread, float maxDistOffCenter )
  1120. {
  1121. float distOffLine = CalcDistanceToLine2D( testPoint.AsVector2D(), sourcePos.AsVector2D(), targetPos.AsVector2D() );
  1122. if ( distOffLine < maxDistOffCenter )
  1123. {
  1124. Vector toTarget = targetPos - sourcePos;
  1125. float distTarget = VectorNormalize(toTarget);
  1126. Vector toTest = testPoint - sourcePos;
  1127. float distTest = VectorNormalize(toTest);
  1128. // Only reject if target is on other side
  1129. if (distTarget > distTest)
  1130. {
  1131. toTarget.z = 0.0;
  1132. toTest.z = 0.0;
  1133. float dotProduct = DotProduct(toTarget,toTest);
  1134. if (dotProduct > flSpread)
  1135. {
  1136. return true;
  1137. }
  1138. else if( dotProduct > 0.0f )
  1139. {
  1140. // If this guy is in front, do the hull/line test:
  1141. if( pCheckEntity )
  1142. {
  1143. float flBBoxDist = NAI_Hull::Width( pCheckEntity->GetHullType() );
  1144. flBBoxDist *= 1.414f; // sqrt(2)
  1145. // !!!BUGBUG - this 2d check will stop a citizen shooting at a gunship or strider
  1146. // if another citizen is between them, even though the strider or gunship may be
  1147. // high up in the air (sjb)
  1148. distOffLine = CalcDistanceToLine( testPoint, sourcePos, targetPos );
  1149. if( distOffLine < flBBoxDist )
  1150. {
  1151. return true;
  1152. }
  1153. }
  1154. }
  1155. }
  1156. }
  1157. return false;
  1158. }
  1159. //-----------------------------------------------------------------------------
  1160. // Purpose: Checks if player is in spread angle between source and target Pos
  1161. // Used to prevent friendly fire
  1162. // Input : Source of attack, target position, spread angle
  1163. // Output :
  1164. //-----------------------------------------------------------------------------
  1165. bool CAI_BaseNPC::PlayerInSpread( const Vector &sourcePos, const Vector &targetPos, float flSpread, float maxDistOffCenter, bool ignoreHatedPlayers )
  1166. {
  1167. // loop through all players
  1168. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  1169. {
  1170. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  1171. if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) != D_HT ) )
  1172. {
  1173. if ( PointInSpread( pPlayer, sourcePos, targetPos, pPlayer->WorldSpaceCenter(), flSpread, maxDistOffCenter ) )
  1174. return true;
  1175. }
  1176. }
  1177. return false;
  1178. }
  1179. //-----------------------------------------------------------------------------
  1180. // Purpose: Checks if player is in range of given location. Used by NPCs
  1181. // to prevent friendly fire
  1182. // Input :
  1183. // Output :
  1184. //-----------------------------------------------------------------------------
  1185. CBaseEntity *CAI_BaseNPC::PlayerInRange( const Vector &vecLocation, float flDist )
  1186. {
  1187. // loop through all players
  1188. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  1189. {
  1190. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  1191. if (pPlayer && (vecLocation - pPlayer->WorldSpaceCenter() ).Length2D() <= flDist)
  1192. {
  1193. return pPlayer;
  1194. }
  1195. }
  1196. return NULL;
  1197. }
  1198. #define BULLET_WIZZDIST 80.0
  1199. #define SLOPE ( -1.0 / BULLET_WIZZDIST )
  1200. void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer )
  1201. {
  1202. CBasePlayer *pPlayer;
  1203. Vector vecBulletPath;
  1204. Vector vecPlayerPath;
  1205. Vector vecBulletDir;
  1206. Vector vecNearestPoint;
  1207. float flDist;
  1208. float flBulletDist;
  1209. vecBulletPath = vecEndPos - vecSrc;
  1210. vecBulletDir = vecBulletPath;
  1211. VectorNormalize(vecBulletDir);
  1212. // see how near this bullet passed by player in a single player game
  1213. // for multiplayer, we need to go through the list of clients.
  1214. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  1215. {
  1216. pPlayer = UTIL_PlayerByIndex( i );
  1217. if ( !pPlayer )
  1218. continue;
  1219. // Don't hear one's own bullets
  1220. if( pPlayer->edict() == pShooter )
  1221. continue;
  1222. vecPlayerPath = pPlayer->EarPosition() - vecSrc;
  1223. flDist = DotProduct( vecPlayerPath, vecBulletDir );
  1224. vecNearestPoint = vecSrc + vecBulletDir * flDist;
  1225. // FIXME: minus m_vecViewOffset?
  1226. flBulletDist = ( vecNearestPoint - pPlayer->EarPosition() ).Length();
  1227. }
  1228. }
  1229. //-----------------------------------------------------------------------------
  1230. // Hits triggers with raycasts
  1231. //-----------------------------------------------------------------------------
  1232. class CTriggerTraceEnum : public IEntityEnumerator
  1233. {
  1234. public:
  1235. CTriggerTraceEnum( Ray_t *pRay, const CTakeDamageInfo &info, const Vector& dir, int contentsMask ) :
  1236. m_info( info ), m_VecDir(dir), m_ContentsMask(contentsMask), m_pRay(pRay)
  1237. {
  1238. }
  1239. virtual bool EnumEntity( IHandleEntity *pHandleEntity )
  1240. {
  1241. trace_t tr;
  1242. CBaseEntity *pEnt = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
  1243. // Done to avoid hitting an entity that's both solid & a trigger.
  1244. if ( pEnt->IsSolid() )
  1245. return true;
  1246. enginetrace->ClipRayToEntity( *m_pRay, m_ContentsMask, pHandleEntity, &tr );
  1247. if (tr.fraction < 1.0f)
  1248. {
  1249. pEnt->DispatchTraceAttack( m_info, m_VecDir, &tr );
  1250. ApplyMultiDamage();
  1251. }
  1252. return true;
  1253. }
  1254. private:
  1255. Vector m_VecDir;
  1256. int m_ContentsMask;
  1257. Ray_t *m_pRay;
  1258. CTakeDamageInfo m_info;
  1259. };
  1260. void CBaseEntity::TraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir )
  1261. {
  1262. Ray_t ray;
  1263. ray.Init( start, end );
  1264. CTriggerTraceEnum triggerTraceEnum( &ray, info, dir, MASK_SHOT );
  1265. enginetrace->EnumerateEntities( ray, true, &triggerTraceEnum );
  1266. }
  1267. //-----------------------------------------------------------------------------
  1268. // Purpose:
  1269. // Output : const char
  1270. //-----------------------------------------------------------------------------
  1271. const char *CAI_BaseNPC::GetTracerType( void )
  1272. {
  1273. if ( GetActiveWeapon() )
  1274. {
  1275. return GetActiveWeapon()->GetTracerType();
  1276. }
  1277. return BaseClass::GetTracerType();
  1278. }
  1279. //-----------------------------------------------------------------------------
  1280. // Purpose:
  1281. // Input : &vecTracerSrc -
  1282. // &tr -
  1283. // iTracerType -
  1284. //-----------------------------------------------------------------------------
  1285. void CAI_BaseNPC::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
  1286. {
  1287. if ( GetActiveWeapon() )
  1288. {
  1289. GetActiveWeapon()->MakeTracer( vecTracerSrc, tr, iTracerType );
  1290. return;
  1291. }
  1292. BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
  1293. }
  1294. //-----------------------------------------------------------------------------
  1295. // Purpose:
  1296. //-----------------------------------------------------------------------------
  1297. void CAI_BaseNPC::FireBullets( const FireBulletsInfo_t &info )
  1298. {
  1299. #ifdef HL2_DLL
  1300. // If we're shooting at a bullseye, become perfectly accurate if the bullseye demands it
  1301. if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
  1302. {
  1303. CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(GetEnemy());
  1304. if ( pBullseye && pBullseye->UsePerfectAccuracy() )
  1305. {
  1306. FireBulletsInfo_t accurateInfo = info;
  1307. accurateInfo.m_vecSpread = vec3_origin;
  1308. BaseClass::FireBullets( accurateInfo );
  1309. return;
  1310. }
  1311. }
  1312. #endif
  1313. BaseClass::FireBullets( info );
  1314. }
  1315. //-----------------------------------------------------------------------------
  1316. // Shot statistics
  1317. //-----------------------------------------------------------------------------
  1318. void CBaseEntity::UpdateShotStatistics( const trace_t &tr )
  1319. {
  1320. if ( ai_shot_stats.GetBool() )
  1321. {
  1322. CAI_BaseNPC *pNpc = MyNPCPointer();
  1323. if ( pNpc )
  1324. {
  1325. pNpc->m_TotalShots++;
  1326. if ( tr.m_pEnt == pNpc->GetEnemy() )
  1327. {
  1328. pNpc->m_TotalHits++;
  1329. }
  1330. }
  1331. }
  1332. }
  1333. //-----------------------------------------------------------------------------
  1334. // Handle shot entering water
  1335. //-----------------------------------------------------------------------------
  1336. void CBaseEntity::HandleShotImpactingGlass( const FireBulletsInfo_t &info,
  1337. const trace_t &tr, const Vector &vecDir, ITraceFilter *pTraceFilter )
  1338. {
  1339. // Move through the glass until we're at the other side
  1340. Vector testPos = tr.endpos + ( vecDir * MAX_GLASS_PENETRATION_DEPTH );
  1341. CEffectData data;
  1342. data.m_vNormal = tr.plane.normal;
  1343. data.m_vOrigin = tr.endpos;
  1344. DispatchEffect( "GlassImpact", data );
  1345. trace_t penetrationTrace;
  1346. // Re-trace as if the bullet had passed right through
  1347. UTIL_TraceLine( testPos, tr.endpos, MASK_SHOT, pTraceFilter, &penetrationTrace );
  1348. // See if we found the surface again
  1349. if ( penetrationTrace.startsolid || tr.fraction == 0.0f || penetrationTrace.fraction == 1.0f )
  1350. return;
  1351. //FIXME: This is technically frustrating MultiDamage, but multiple shots hitting multiple targets in one call
  1352. // would do exactly the same anyway...
  1353. // Impact the other side (will look like an exit effect)
  1354. DoImpactEffect( penetrationTrace, GetAmmoDef()->DamageType(info.m_iAmmoType) );
  1355. data.m_vNormal = penetrationTrace.plane.normal;
  1356. data.m_vOrigin = penetrationTrace.endpos;
  1357. DispatchEffect( "GlassImpact", data );
  1358. // Refire the round, as if starting from behind the glass
  1359. FireBulletsInfo_t behindGlassInfo;
  1360. behindGlassInfo.m_iShots = 1;
  1361. behindGlassInfo.m_vecSrc = penetrationTrace.endpos;
  1362. behindGlassInfo.m_vecDirShooting = vecDir;
  1363. behindGlassInfo.m_vecSpread = vec3_origin;
  1364. behindGlassInfo.m_flDistance = info.m_flDistance*( 1.0f - tr.fraction );
  1365. behindGlassInfo.m_iAmmoType = info.m_iAmmoType;
  1366. behindGlassInfo.m_iTracerFreq = info.m_iTracerFreq;
  1367. behindGlassInfo.m_flDamage = info.m_flDamage;
  1368. behindGlassInfo.m_pAttacker = info.m_pAttacker ? info.m_pAttacker : this;
  1369. behindGlassInfo.m_nFlags = info.m_nFlags;
  1370. FireBullets( behindGlassInfo );
  1371. }
  1372. //-----------------------------------------------------------------------------
  1373. // Computes the tracer start position
  1374. //-----------------------------------------------------------------------------
  1375. #define SHOT_UNDERWATER_BUBBLE_DIST 400
  1376. void CBaseEntity::CreateBubbleTrailTracer( const Vector &vecShotSrc, const Vector &vecShotEnd, const Vector &vecShotDir )
  1377. {
  1378. int nBubbles;
  1379. Vector vecBubbleEnd;
  1380. float flLengthSqr = vecShotSrc.DistToSqr( vecShotEnd );
  1381. if ( flLengthSqr > SHOT_UNDERWATER_BUBBLE_DIST * SHOT_UNDERWATER_BUBBLE_DIST )
  1382. {
  1383. VectorMA( vecShotSrc, SHOT_UNDERWATER_BUBBLE_DIST, vecShotDir, vecBubbleEnd );
  1384. nBubbles = WATER_BULLET_BUBBLES_PER_INCH * SHOT_UNDERWATER_BUBBLE_DIST;
  1385. }
  1386. else
  1387. {
  1388. float flLength = sqrt(flLengthSqr) - 0.1f;
  1389. nBubbles = WATER_BULLET_BUBBLES_PER_INCH * flLength;
  1390. VectorMA( vecShotSrc, flLength, vecShotDir, vecBubbleEnd );
  1391. }
  1392. Vector vecTracerSrc;
  1393. ComputeTracerStartPosition( vecShotSrc, &vecTracerSrc );
  1394. UTIL_BubbleTrail( vecTracerSrc, vecBubbleEnd, nBubbles );
  1395. }
  1396. //=========================================================
  1397. //=========================================================
  1398. void CAI_BaseNPC::MakeDamageBloodDecal ( int cCount, float flNoise, trace_t *ptr, Vector vecDir )
  1399. {
  1400. // make blood decal on the wall!
  1401. trace_t Bloodtr;
  1402. Vector vecTraceDir;
  1403. int i;
  1404. if ( !IsAlive() )
  1405. {
  1406. // dealing with a dead npc.
  1407. if ( m_iMaxHealth <= 0 )
  1408. {
  1409. // no blood decal for a npc that has already decalled its limit.
  1410. return;
  1411. }
  1412. else
  1413. {
  1414. m_iMaxHealth -= 1;
  1415. }
  1416. }
  1417. for ( i = 0 ; i < cCount ; i++ )
  1418. {
  1419. vecTraceDir = vecDir;
  1420. vecTraceDir.x += random->RandomFloat( -flNoise, flNoise );
  1421. vecTraceDir.y += random->RandomFloat( -flNoise, flNoise );
  1422. vecTraceDir.z += random->RandomFloat( -flNoise, flNoise );
  1423. AI_TraceLine( ptr->endpos, ptr->endpos + vecTraceDir * 172, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &Bloodtr);
  1424. if ( Bloodtr.fraction != 1.0 )
  1425. {
  1426. UTIL_BloodDecalTrace( &Bloodtr, BloodColor() );
  1427. }
  1428. }
  1429. }
  1430. //-----------------------------------------------------------------------------
  1431. // Purpose:
  1432. // Input : &tr -
  1433. // nDamageType -
  1434. //-----------------------------------------------------------------------------
  1435. void CAI_BaseNPC::DoImpactEffect( trace_t &tr, int nDamageType )
  1436. {
  1437. if ( GetActiveWeapon() != NULL )
  1438. {
  1439. GetActiveWeapon()->DoImpactEffect( tr, nDamageType );
  1440. return;
  1441. }
  1442. BaseClass::DoImpactEffect( tr, nDamageType );
  1443. }
  1444. //---------------------------------------------------------
  1445. //---------------------------------------------------------
  1446. #define InterruptFromCondition( iCondition ) \
  1447. AI_RemapFromGlobal( ( AI_IdIsLocal( iCondition ) ? GetClassScheduleIdSpace()->ConditionLocalToGlobal( iCondition ) : iCondition ) )
  1448. void CAI_BaseNPC::SetCondition( int iCondition )
  1449. {
  1450. int interrupt = InterruptFromCondition( iCondition );
  1451. if ( interrupt == -1 )
  1452. {
  1453. Assert(0);
  1454. return;
  1455. }
  1456. if ( HasCondition( iCondition ) == false )
  1457. {
  1458. OnConditionSet( iCondition );
  1459. }
  1460. m_Conditions.Set( interrupt );
  1461. }
  1462. //---------------------------------------------------------
  1463. //---------------------------------------------------------
  1464. bool CAI_BaseNPC::HasCondition( int iCondition )
  1465. {
  1466. int interrupt = InterruptFromCondition( iCondition );
  1467. if ( interrupt == -1 )
  1468. {
  1469. Assert(0);
  1470. return false;
  1471. }
  1472. bool bReturn = m_Conditions.IsBitSet(interrupt);
  1473. return (bReturn);
  1474. }
  1475. //---------------------------------------------------------
  1476. //---------------------------------------------------------
  1477. bool CAI_BaseNPC::HasCondition( int iCondition, bool bUseIgnoreConditions )
  1478. {
  1479. if ( bUseIgnoreConditions )
  1480. return HasCondition( iCondition );
  1481. int interrupt = InterruptFromCondition( iCondition );
  1482. if ( interrupt == -1 )
  1483. {
  1484. Assert(0);
  1485. return false;
  1486. }
  1487. bool bReturn = m_ConditionsPreIgnore.IsBitSet(interrupt);
  1488. return (bReturn);
  1489. }
  1490. //---------------------------------------------------------
  1491. //---------------------------------------------------------
  1492. void CAI_BaseNPC::ClearCondition( int iCondition )
  1493. {
  1494. int interrupt = InterruptFromCondition( iCondition );
  1495. if ( interrupt == -1 )
  1496. {
  1497. Assert(0);
  1498. return;
  1499. }
  1500. if ( HasCondition( iCondition ) == true )
  1501. {
  1502. OnConditionCleared( iCondition );
  1503. }
  1504. m_Conditions.Clear(interrupt);
  1505. }
  1506. //---------------------------------------------------------
  1507. //---------------------------------------------------------
  1508. void CAI_BaseNPC::ClearConditions( int *pConditions, int nConditions )
  1509. {
  1510. for ( int i = 0; i < nConditions; ++i )
  1511. {
  1512. int iCondition = pConditions[i];
  1513. int interrupt = InterruptFromCondition( iCondition );
  1514. if ( interrupt == -1 )
  1515. {
  1516. Assert(0);
  1517. continue;
  1518. }
  1519. m_Conditions.Clear( interrupt );
  1520. }
  1521. }
  1522. //---------------------------------------------------------
  1523. //---------------------------------------------------------
  1524. void CAI_BaseNPC::SetIgnoreConditions( int *pConditions, int nConditions )
  1525. {
  1526. for ( int i = 0; i < nConditions; ++i )
  1527. {
  1528. int iCondition = pConditions[i];
  1529. int interrupt = InterruptFromCondition( iCondition );
  1530. if ( interrupt == -1 )
  1531. {
  1532. Assert(0);
  1533. continue;
  1534. }
  1535. m_InverseIgnoreConditions.Clear( interrupt ); // clear means ignore
  1536. }
  1537. }
  1538. void CAI_BaseNPC::ClearIgnoreConditions( int *pConditions, int nConditions )
  1539. {
  1540. for ( int i = 0; i < nConditions; ++i )
  1541. {
  1542. int iCondition = pConditions[i];
  1543. int interrupt = InterruptFromCondition( iCondition );
  1544. if ( interrupt == -1 )
  1545. {
  1546. Assert(0);
  1547. continue;
  1548. }
  1549. m_InverseIgnoreConditions.Set( interrupt ); // set means don't ignore
  1550. }
  1551. }
  1552. //---------------------------------------------------------
  1553. //---------------------------------------------------------
  1554. bool CAI_BaseNPC::HasInterruptCondition( int iCondition )
  1555. {
  1556. if( !GetCurSchedule() )
  1557. {
  1558. return false;
  1559. }
  1560. int interrupt = InterruptFromCondition( iCondition );
  1561. if ( interrupt == -1 )
  1562. {
  1563. Assert(0);
  1564. return false;
  1565. }
  1566. return ( m_Conditions.IsBitSet( interrupt ) && GetCurSchedule()->HasInterrupt( interrupt ) );
  1567. }
  1568. //---------------------------------------------------------
  1569. //---------------------------------------------------------
  1570. bool CAI_BaseNPC::ConditionInterruptsCurSchedule( int iCondition )
  1571. {
  1572. if( !GetCurSchedule() )
  1573. {
  1574. return false;
  1575. }
  1576. int interrupt = InterruptFromCondition( iCondition );
  1577. if ( interrupt == -1 )
  1578. {
  1579. Assert(0);
  1580. return false;
  1581. }
  1582. return ( GetCurSchedule()->HasInterrupt( interrupt ) );
  1583. }
  1584. //---------------------------------------------------------
  1585. //---------------------------------------------------------
  1586. bool CAI_BaseNPC::ConditionInterruptsSchedule( int localScheduleID, int iCondition )
  1587. {
  1588. CAI_Schedule *pSchedule = GetSchedule( localScheduleID );
  1589. if ( !pSchedule )
  1590. return false;
  1591. int interrupt = InterruptFromCondition( iCondition );
  1592. if ( interrupt == -1 )
  1593. {
  1594. Assert(0);
  1595. return false;
  1596. }
  1597. return ( pSchedule->HasInterrupt( interrupt ) );
  1598. }
  1599. //-----------------------------------------------------------------------------
  1600. // Purpose: Sets the interrupt conditions for a scripted schedule
  1601. // Input : interrupt - the level of interrupt we allow
  1602. //-----------------------------------------------------------------------------
  1603. void CAI_BaseNPC::SetScriptedScheduleIgnoreConditions( Interruptability_t interrupt )
  1604. {
  1605. static int g_GeneralConditions[] =
  1606. {
  1607. COND_CAN_MELEE_ATTACK1,
  1608. COND_CAN_MELEE_ATTACK2,
  1609. COND_CAN_RANGE_ATTACK1,
  1610. COND_CAN_RANGE_ATTACK2,
  1611. COND_ENEMY_DEAD,
  1612. COND_HEAR_BULLET_IMPACT,
  1613. COND_HEAR_COMBAT,
  1614. COND_HEAR_DANGER,
  1615. COND_HEAR_PHYSICS_DANGER,
  1616. COND_NEW_ENEMY,
  1617. COND_PROVOKED,
  1618. COND_SEE_ENEMY,
  1619. COND_SEE_FEAR,
  1620. COND_SMELL,
  1621. };
  1622. static int g_DamageConditions[] =
  1623. {
  1624. COND_HEAVY_DAMAGE,
  1625. COND_LIGHT_DAMAGE,
  1626. COND_RECEIVED_ORDERS,
  1627. };
  1628. ClearIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
  1629. ClearIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );
  1630. if ( interrupt > GENERAL_INTERRUPTABILITY )
  1631. SetIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
  1632. if ( interrupt > DAMAGEORDEATH_INTERRUPTABILITY )
  1633. SetIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );
  1634. }
  1635. //-----------------------------------------------------------------------------
  1636. // Returns whether we currently have any interrupt conditions that would
  1637. // interrupt the given schedule.
  1638. //-----------------------------------------------------------------------------
  1639. bool CAI_BaseNPC::HasConditionsToInterruptSchedule( int nLocalScheduleID )
  1640. {
  1641. CAI_Schedule *pSchedule = GetSchedule( nLocalScheduleID );
  1642. if ( !pSchedule )
  1643. return false;
  1644. CAI_ScheduleBits bitsMask;
  1645. pSchedule->GetInterruptMask( &bitsMask );
  1646. CAI_ScheduleBits bitsOut;
  1647. AccessConditionBits().And( bitsMask, &bitsOut );
  1648. return !bitsOut.IsAllClear();
  1649. }
  1650. //-----------------------------------------------------------------------------
  1651. //-----------------------------------------------------------------------------
  1652. bool CAI_BaseNPC::IsCustomInterruptConditionSet( int nCondition )
  1653. {
  1654. int interrupt = InterruptFromCondition( nCondition );
  1655. if ( interrupt == -1 )
  1656. {
  1657. Assert(0);
  1658. return false;
  1659. }
  1660. return m_CustomInterruptConditions.IsBitSet( interrupt );
  1661. }
  1662. //-----------------------------------------------------------------------------
  1663. // Purpose: Sets a flag in the custom interrupt flags, translating the condition
  1664. // to the proper global space, if necessary
  1665. //-----------------------------------------------------------------------------
  1666. void CAI_BaseNPC::SetCustomInterruptCondition( int nCondition )
  1667. {
  1668. int interrupt = InterruptFromCondition( nCondition );
  1669. if ( interrupt == -1 )
  1670. {
  1671. Assert(0);
  1672. return;
  1673. }
  1674. m_CustomInterruptConditions.Set( interrupt );
  1675. }
  1676. //-----------------------------------------------------------------------------
  1677. // Purpose: Clears a flag in the custom interrupt flags, translating the condition
  1678. // to the proper global space, if necessary
  1679. //-----------------------------------------------------------------------------
  1680. void CAI_BaseNPC::ClearCustomInterruptCondition( int nCondition )
  1681. {
  1682. int interrupt = InterruptFromCondition( nCondition );
  1683. if ( interrupt == -1 )
  1684. {
  1685. Assert(0);
  1686. return;
  1687. }
  1688. m_CustomInterruptConditions.Clear( interrupt );
  1689. }
  1690. //-----------------------------------------------------------------------------
  1691. // Purpose: Clears all the custom interrupt flags.
  1692. //-----------------------------------------------------------------------------
  1693. void CAI_BaseNPC::ClearCustomInterruptConditions()
  1694. {
  1695. m_CustomInterruptConditions.ClearAll();
  1696. }
  1697. //-----------------------------------------------------------------------------
  1698. void CAI_BaseNPC::SetDistLook( float flDistLook )
  1699. {
  1700. m_pSenses->SetDistLook( flDistLook );
  1701. }
  1702. //-----------------------------------------------------------------------------
  1703. bool CAI_BaseNPC::QueryHearSound( CSound *pSound )
  1704. {
  1705. if ( pSound->SoundContext() & SOUND_CONTEXT_COMBINE_ONLY )
  1706. return false;
  1707. if ( pSound->SoundContext() & SOUND_CONTEXT_ALLIES_ONLY )
  1708. {
  1709. if ( !IsPlayerAlly() )
  1710. return false;
  1711. }
  1712. if ( pSound->IsSoundType( SOUND_PLAYER ) && GetState() == NPC_STATE_IDLE && !FVisible( pSound->GetSoundReactOrigin() ) )
  1713. {
  1714. // NPC's that are IDLE should disregard player movement sounds if they can't see them.
  1715. // This does not affect them hearing the player's weapon.
  1716. // !!!BUGBUG - this probably makes NPC's not hear doors opening, because doors opening put SOUND_PLAYER
  1717. // in the world, but the door's model will block the FVisible() trace and this code will then
  1718. // deduce that the sound can not be heard
  1719. return false;
  1720. }
  1721. // Disregard footsteps from our own class type
  1722. if ( pSound->IsSoundType( SOUND_COMBAT ) && pSound->SoundChannel() == SOUNDENT_CHANNEL_NPC_FOOTSTEP )
  1723. {
  1724. if ( pSound->m_hOwner && pSound->m_hOwner->ClassMatches( m_iClassname ) )
  1725. return false;
  1726. }
  1727. if( ShouldIgnoreSound( pSound ) )
  1728. return false;
  1729. return true;
  1730. }
  1731. //-----------------------------------------------------------------------------
  1732. bool CAI_BaseNPC::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
  1733. {
  1734. if ( bOnlyHateOrFearIfNPC && pEntity->IsNPC() )
  1735. {
  1736. Disposition_t disposition = IRelationType( pEntity );
  1737. return ( disposition == D_HT || disposition == D_FR );
  1738. }
  1739. return true;
  1740. }
  1741. //-----------------------------------------------------------------------------
  1742. void CAI_BaseNPC::OnLooked( int iDistance )
  1743. {
  1744. // DON'T let visibility information from last frame sit around!
  1745. static int conditionsToClear[] =
  1746. {
  1747. COND_SEE_HATE,
  1748. COND_SEE_DISLIKE,
  1749. COND_SEE_ENEMY,
  1750. COND_SEE_FEAR,
  1751. COND_SEE_NEMESIS,
  1752. COND_SEE_PLAYER,
  1753. COND_LOST_PLAYER,
  1754. COND_ENEMY_WENT_NULL,
  1755. };
  1756. bool bHadSeePlayer = HasCondition(COND_SEE_PLAYER);
  1757. ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
  1758. AISightIter_t iter;
  1759. CBaseEntity *pSightEnt;
  1760. pSightEnt = GetSenses()->GetFirstSeenEntity( &iter );
  1761. while( pSightEnt )
  1762. {
  1763. if ( pSightEnt->IsPlayer() )
  1764. {
  1765. // if we see a client, remember that (mostly for scripted AI)
  1766. SetCondition(COND_SEE_PLAYER);
  1767. m_flLastSawPlayerTime = gpGlobals->curtime;
  1768. }
  1769. Disposition_t relation = IRelationType( pSightEnt );
  1770. // the looker will want to consider this entity
  1771. // don't check anything else about an entity that can't be seen, or an entity that you don't care about.
  1772. if ( relation != D_NU )
  1773. {
  1774. if ( pSightEnt == GetEnemy() )
  1775. {
  1776. // we know this ent is visible, so if it also happens to be our enemy, store that now.
  1777. SetCondition(COND_SEE_ENEMY);
  1778. }
  1779. // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when
  1780. // we see npcs other than the Enemy.
  1781. switch ( relation )
  1782. {
  1783. case D_HT:
  1784. {
  1785. int priority = IRelationPriority( pSightEnt );
  1786. if (priority < 0)
  1787. {
  1788. SetCondition(COND_SEE_DISLIKE);
  1789. }
  1790. else if (priority > 10)
  1791. {
  1792. SetCondition(COND_SEE_NEMESIS);
  1793. }
  1794. else
  1795. {
  1796. SetCondition(COND_SEE_HATE);
  1797. }
  1798. UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin());
  1799. break;
  1800. }
  1801. case D_FR:
  1802. UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin());
  1803. SetCondition(COND_SEE_FEAR);
  1804. break;
  1805. case D_LI:
  1806. case D_NU:
  1807. break;
  1808. default:
  1809. DevWarning( 2, "%s can't assess %s\n", GetClassname(), pSightEnt->GetClassname() );
  1810. break;
  1811. }
  1812. }
  1813. pSightEnt = GetSenses()->GetNextSeenEntity( &iter );
  1814. }
  1815. // Did we lose the player?
  1816. if ( bHadSeePlayer && !HasCondition(COND_SEE_PLAYER) )
  1817. {
  1818. SetCondition(COND_LOST_PLAYER);
  1819. }
  1820. }
  1821. //-----------------------------------------------------------------------------
  1822. void CAI_BaseNPC::OnListened()
  1823. {
  1824. AISoundIter_t iter;
  1825. CSound *pCurrentSound;
  1826. static int conditionsToClear[] =
  1827. {
  1828. COND_HEAR_DANGER,
  1829. COND_HEAR_COMBAT,
  1830. COND_HEAR_WORLD,
  1831. COND_HEAR_PLAYER,
  1832. COND_HEAR_THUMPER,
  1833. COND_HEAR_BUGBAIT,
  1834. COND_HEAR_PHYSICS_DANGER,
  1835. COND_HEAR_BULLET_IMPACT,
  1836. COND_HEAR_MOVE_AWAY,
  1837. COND_NO_HEAR_DANGER,
  1838. COND_SMELL,
  1839. };
  1840. ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
  1841. pCurrentSound = GetSenses()->GetFirstHeardSound( &iter );
  1842. while ( pCurrentSound )
  1843. {
  1844. // the npc cares about this sound, and it's close enough to hear.
  1845. int condition = COND_NONE;
  1846. if ( pCurrentSound->FIsSound() )
  1847. {
  1848. // this is an audible sound.
  1849. switch( pCurrentSound->SoundTypeNoContext() )
  1850. {
  1851. case SOUND_DANGER:
  1852. if( gpGlobals->curtime > m_flIgnoreDangerSoundsUntil)
  1853. condition = COND_HEAR_DANGER;
  1854. break;
  1855. case SOUND_THUMPER: condition = COND_HEAR_THUMPER; break;
  1856. case SOUND_BUGBAIT: condition = COND_HEAR_BUGBAIT; break;
  1857. case SOUND_COMBAT:
  1858. if ( pCurrentSound->SoundChannel() == SOUNDENT_CHANNEL_SPOOKY_NOISE )
  1859. {
  1860. condition = COND_HEAR_SPOOKY;
  1861. }
  1862. else
  1863. {
  1864. condition = COND_HEAR_COMBAT;
  1865. }
  1866. break;
  1867. case SOUND_WORLD: condition = COND_HEAR_WORLD; break;
  1868. case SOUND_PLAYER: condition = COND_HEAR_PLAYER; break;
  1869. case SOUND_BULLET_IMPACT: condition = COND_HEAR_BULLET_IMPACT; break;
  1870. case SOUND_PHYSICS_DANGER: condition = COND_HEAR_PHYSICS_DANGER; break;
  1871. case SOUND_DANGER_SNIPERONLY:/* silence warning */ break;
  1872. case SOUND_MOVE_AWAY: condition = COND_HEAR_MOVE_AWAY; break;
  1873. case SOUND_PLAYER_VEHICLE: condition = COND_HEAR_PLAYER; break;
  1874. default:
  1875. DevMsg( "**ERROR: NPC %s hearing sound of unknown type %d!\n", GetClassname(), pCurrentSound->SoundType() );
  1876. break;
  1877. }
  1878. }
  1879. else
  1880. {
  1881. // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent
  1882. condition = COND_SMELL;
  1883. }
  1884. if ( condition != COND_NONE )
  1885. {
  1886. SetCondition( condition );
  1887. }
  1888. pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
  1889. }
  1890. if( !HasCondition( COND_HEAR_DANGER ) )
  1891. {
  1892. SetCondition( COND_NO_HEAR_DANGER );
  1893. }
  1894. // Sound outputs
  1895. if ( HasCondition( COND_HEAR_WORLD ) )
  1896. {
  1897. m_OnHearWorld.FireOutput(this, this);
  1898. }
  1899. if ( HasCondition( COND_HEAR_PLAYER ) )
  1900. {
  1901. m_OnHearPlayer.FireOutput(this, this);
  1902. }
  1903. if ( HasCondition( COND_HEAR_COMBAT ) ||
  1904. HasCondition( COND_HEAR_BULLET_IMPACT ) ||
  1905. HasCondition( COND_HEAR_DANGER ) )
  1906. {
  1907. m_OnHearCombat.FireOutput(this, this);
  1908. }
  1909. }
  1910. //=========================================================
  1911. // FValidateHintType - tells use whether or not the npc cares
  1912. // about the type of Hint Node given
  1913. //=========================================================
  1914. bool CAI_BaseNPC::FValidateHintType ( CAI_Hint *pHint )
  1915. {
  1916. return false;
  1917. }
  1918. //-----------------------------------------------------------------------------
  1919. // Purpose: Override in subclasses to associate specific hint types
  1920. // with activities
  1921. //-----------------------------------------------------------------------------
  1922. Activity CAI_BaseNPC::GetHintActivity( short sHintType, Activity HintsActivity )
  1923. {
  1924. if ( HintsActivity != ACT_INVALID )
  1925. return HintsActivity;
  1926. return ACT_IDLE;
  1927. }
  1928. //-----------------------------------------------------------------------------
  1929. // Purpose: Override in subclasses to give specific hint types delays
  1930. // before they can be used again
  1931. // Input :
  1932. // Output :
  1933. //-----------------------------------------------------------------------------
  1934. float CAI_BaseNPC::GetHintDelay( short sHintType )
  1935. {
  1936. return 0;
  1937. }
  1938. //-----------------------------------------------------------------------------
  1939. // Purpose: Return incoming grenade if spotted
  1940. // Input :
  1941. // Output :
  1942. //-----------------------------------------------------------------------------
  1943. CBaseGrenade* CAI_BaseNPC::IncomingGrenade(void)
  1944. {
  1945. int iDist;
  1946. AIEnemiesIter_t iter;
  1947. for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  1948. {
  1949. CBaseGrenade* pBG = dynamic_cast<CBaseGrenade*>((CBaseEntity*)pEMemory->hEnemy);
  1950. // Make sure this memory is for a grenade and grenade is not dead
  1951. if (!pBG || pBG->m_lifeState == LIFE_DEAD)
  1952. continue;
  1953. // Make sure it's visible
  1954. if (!FVisible(pBG))
  1955. continue;
  1956. // Check if it's near me
  1957. iDist = ( pBG->GetAbsOrigin() - GetAbsOrigin() ).Length();
  1958. if ( iDist <= NPC_GRENADE_FEAR_DIST )
  1959. return pBG;
  1960. // Check if it's headed towards me
  1961. Vector vGrenadeDir = GetAbsOrigin() - pBG->GetAbsOrigin();
  1962. Vector vGrenadeVel;
  1963. pBG->GetVelocity( &vGrenadeVel, NULL );
  1964. VectorNormalize(vGrenadeDir);
  1965. VectorNormalize(vGrenadeVel);
  1966. float flDotPr = DotProduct(vGrenadeDir, vGrenadeVel);
  1967. if (flDotPr > 0.85)
  1968. return pBG;
  1969. }
  1970. return NULL;
  1971. }
  1972. //-----------------------------------------------------------------------------
  1973. // Purpose: Check my physical state with the environment
  1974. // Input :
  1975. // Output :
  1976. //-----------------------------------------------------------------------------
  1977. void CAI_BaseNPC::TryRestoreHull(void)
  1978. {
  1979. if ( IsUsingSmallHull() && GetCurSchedule() )
  1980. {
  1981. trace_t tr;
  1982. Vector vUpBit = GetAbsOrigin();
  1983. vUpBit.z += 1;
  1984. AI_TraceHull( GetAbsOrigin(), vUpBit, GetHullMins(),
  1985. GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  1986. if ( !tr.startsolid && (tr.fraction == 1.0) )
  1987. {
  1988. SetHullSizeNormal();
  1989. }
  1990. }
  1991. }
  1992. //=========================================================
  1993. //=========================================================
  1994. int CAI_BaseNPC::GetSoundInterests( void )
  1995. {
  1996. return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_PLAYER_VEHICLE |
  1997. SOUND_BULLET_IMPACT;
  1998. }
  1999. //---------------------------------------------------------
  2000. //---------------------------------------------------------
  2001. int CAI_BaseNPC::GetSoundPriority( CSound *pSound )
  2002. {
  2003. int iSoundTypeNoContext = pSound->SoundTypeNoContext();
  2004. int iSoundContext = pSound->SoundContext();
  2005. if( iSoundTypeNoContext & SOUND_DANGER )
  2006. {
  2007. return SOUND_PRIORITY_HIGHEST;
  2008. }
  2009. if( iSoundTypeNoContext & SOUND_COMBAT )
  2010. {
  2011. if( iSoundContext & SOUND_CONTEXT_EXPLOSION )
  2012. {
  2013. return SOUND_PRIORITY_VERY_HIGH;
  2014. }
  2015. return SOUND_PRIORITY_HIGH;
  2016. }
  2017. return SOUND_PRIORITY_NORMAL;
  2018. }
  2019. //---------------------------------------------------------
  2020. // Return the loudest sound of this type in the sound list
  2021. //---------------------------------------------------------
  2022. CSound *CAI_BaseNPC::GetLoudestSoundOfType( int iType )
  2023. {
  2024. return CSoundEnt::GetLoudestSoundOfType( iType, EarPosition() );
  2025. }
  2026. //=========================================================
  2027. // GetBestSound - returns a pointer to the sound the npc
  2028. // should react to. Right now responds only to nearest sound.
  2029. //=========================================================
  2030. CSound* CAI_BaseNPC::GetBestSound( int validTypes )
  2031. {
  2032. if ( m_pLockedBestSound->m_iType != SOUND_NONE )
  2033. return m_pLockedBestSound;
  2034. CSound *pResult = GetSenses()->GetClosestSound( false, validTypes );
  2035. if ( pResult == NULL)
  2036. DevMsg( "Warning: NULL Return from GetBestSound\n" ); // condition previously set now no longer true. Have seen this when play too many sounds...
  2037. return pResult;
  2038. }
  2039. //=========================================================
  2040. // PBestScent - returns a pointer to the scent the npc
  2041. // should react to. Right now responds only to nearest scent
  2042. //=========================================================
  2043. CSound* CAI_BaseNPC::GetBestScent( void )
  2044. {
  2045. CSound *pResult = GetSenses()->GetClosestSound( true );
  2046. if ( pResult == NULL)
  2047. DevMsg("Warning: NULL Return from GetBestScent\n" );
  2048. return pResult;
  2049. }
  2050. //-----------------------------------------------------------------------------
  2051. void CAI_BaseNPC::LockBestSound()
  2052. {
  2053. UnlockBestSound();
  2054. CSound *pBestSound = GetBestSound();
  2055. if ( pBestSound )
  2056. *m_pLockedBestSound = *pBestSound;
  2057. }
  2058. //-----------------------------------------------------------------------------
  2059. void CAI_BaseNPC::UnlockBestSound()
  2060. {
  2061. if ( m_pLockedBestSound->m_iType != SOUND_NONE )
  2062. {
  2063. m_pLockedBestSound->m_iType = SOUND_NONE;
  2064. OnListened(); // reset hearing conditions
  2065. }
  2066. }
  2067. //-----------------------------------------------------------------------------
  2068. // Purpose: Return true if the specified sound is visible. Handles sounds generated by entities correctly.
  2069. // Input : *pSound -
  2070. //-----------------------------------------------------------------------------
  2071. bool CAI_BaseNPC::SoundIsVisible( CSound *pSound )
  2072. {
  2073. CBaseEntity *pBlocker = NULL;
  2074. if ( !FVisible( pSound->GetSoundReactOrigin(), MASK_BLOCKLOS, &pBlocker ) )
  2075. {
  2076. // Is the blocker the sound owner?
  2077. if ( pBlocker && pBlocker == pSound->m_hOwner )
  2078. return true;
  2079. return false;
  2080. }
  2081. return true;
  2082. }
  2083. //-----------------------------------------------------------------------------
  2084. // Purpose: Returns true if target is in legal range of eye movements
  2085. // Input :
  2086. // Output :
  2087. //-----------------------------------------------------------------------------
  2088. bool CAI_BaseNPC::ValidEyeTarget(const Vector &lookTargetPos)
  2089. {
  2090. Vector vHeadDir = HeadDirection3D( );
  2091. Vector lookTargetDir = lookTargetPos - EyePosition();
  2092. VectorNormalize(lookTargetDir);
  2093. // Only look if it doesn't crank my eyeballs too far
  2094. float dotPr = DotProduct(lookTargetDir, vHeadDir);
  2095. if (dotPr > 0.7)
  2096. {
  2097. return true;
  2098. }
  2099. return false;
  2100. }
  2101. //-----------------------------------------------------------------------------
  2102. // Purpose: Integrate head turn over time
  2103. // Input :
  2104. // Output :
  2105. //-----------------------------------------------------------------------------
  2106. void CAI_BaseNPC::SetHeadDirection( const Vector &vTargetPos, float flInterval)
  2107. {
  2108. if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD))
  2109. return;
  2110. #ifdef DEBUG_LOOK
  2111. // Draw line in body, head, and eye directions
  2112. Vector vEyePos = EyePosition();
  2113. Vector vHeadDir;
  2114. HeadDirection3D(&vHeadDir);
  2115. Vector vBodyDir;
  2116. BodyDirection2D(&vBodyDir);
  2117. //UNDONE <<TODO>>
  2118. // currently eye dir just returns head dir, so use vTargetPos for now
  2119. //Vector vEyeDir; w
  2120. //EyeDirection3D(&vEyeDir);
  2121. NDebugOverlay::Line( vEyePos, vEyePos+(50*vHeadDir), 255, 0, 0, false, 0.1 );
  2122. NDebugOverlay::Line( vEyePos, vEyePos+(50*vBodyDir), 0, 255, 0, false, 0.1 );
  2123. NDebugOverlay::Line( vEyePos, vTargetPos, 0, 0, 255, false, 0.1 );
  2124. #endif
  2125. //--------------------------------------
  2126. // Set head yaw
  2127. //--------------------------------------
  2128. float flDesiredYaw = VecToYaw(vTargetPos - GetLocalOrigin()) - GetLocalAngles().y;
  2129. if (flDesiredYaw > 180)
  2130. flDesiredYaw -= 360;
  2131. if (flDesiredYaw < -180)
  2132. flDesiredYaw += 360;
  2133. float iRate = 0.8;
  2134. // Make frame rate independent
  2135. float timeToUse = flInterval;
  2136. while (timeToUse > 0)
  2137. {
  2138. m_flHeadYaw = (iRate * m_flHeadYaw) + (1-iRate)*flDesiredYaw;
  2139. timeToUse -= 0.1;
  2140. }
  2141. if (m_flHeadYaw > 360) m_flHeadYaw = 0;
  2142. m_flHeadYaw = SetBoneController( 0, m_flHeadYaw );
  2143. //--------------------------------------
  2144. // Set head pitch
  2145. //--------------------------------------
  2146. Vector vEyePosition = EyePosition();
  2147. float fTargetDist = (vTargetPos - vEyePosition).Length();
  2148. float fVertDist = vTargetPos.z - vEyePosition.z;
  2149. float flDesiredPitch = -RAD2DEG(atan(fVertDist/fTargetDist));
  2150. // Make frame rate independent
  2151. timeToUse = flInterval;
  2152. while (timeToUse > 0)
  2153. {
  2154. m_flHeadPitch = (iRate * m_flHeadPitch) + (1-iRate)*flDesiredPitch;
  2155. timeToUse -= 0.1;
  2156. }
  2157. if (m_flHeadPitch > 360) m_flHeadPitch = 0;
  2158. SetBoneController( 1, m_flHeadPitch );
  2159. }
  2160. //------------------------------------------------------------------------------
  2161. // Purpose : Calculate the NPC's eye direction in 2D world space
  2162. // Input :
  2163. // Output :
  2164. //------------------------------------------------------------------------------
  2165. Vector CAI_BaseNPC::EyeDirection2D( void )
  2166. {
  2167. // UNDONE
  2168. // For now just return head direction....
  2169. return HeadDirection2D( );
  2170. }
  2171. //------------------------------------------------------------------------------
  2172. // Purpose : Calculate the NPC's eye direction in 2D world space
  2173. // Input :
  2174. // Output :
  2175. //------------------------------------------------------------------------------
  2176. Vector CAI_BaseNPC::EyeDirection3D( void )
  2177. {
  2178. // UNDONE //<<TODO>>
  2179. // For now just return head direction....
  2180. return HeadDirection3D( );
  2181. }
  2182. //------------------------------------------------------------------------------
  2183. // Purpose : Calculate the NPC's head direction in 2D world space
  2184. // Input :
  2185. // Output :
  2186. //------------------------------------------------------------------------------
  2187. Vector CAI_BaseNPC::HeadDirection2D( void )
  2188. {
  2189. // UNDONE <<TODO>>
  2190. // This does not account for head rotations in the animation,
  2191. // only those done via bone controllers
  2192. // Head yaw is in local cooridnate so it must be added to the body's yaw
  2193. QAngle bodyAngles = BodyAngles();
  2194. float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y;
  2195. // Convert head yaw into facing direction
  2196. return UTIL_YawToVector( flWorldHeadYaw );
  2197. }
  2198. //------------------------------------------------------------------------------
  2199. // Purpose : Calculate the NPC's head direction in 3D world space
  2200. // Input :
  2201. // Output :
  2202. //------------------------------------------------------------------------------
  2203. Vector CAI_BaseNPC::HeadDirection3D( void )
  2204. {
  2205. Vector vHeadDirection;
  2206. // UNDONE <<TODO>>
  2207. // This does not account for head rotations in the animation,
  2208. // only those done via bone controllers
  2209. // Head yaw is in local cooridnate so it must be added to the body's yaw
  2210. QAngle bodyAngles = BodyAngles();
  2211. float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y;
  2212. // Convert head yaw into facing direction
  2213. AngleVectors( QAngle( m_flHeadPitch, flWorldHeadYaw, 0), &vHeadDirection );
  2214. return vHeadDirection;
  2215. }
  2216. //-----------------------------------------------------------------------------
  2217. // Purpose: Look at other NPCs and clients from time to time
  2218. // Input :
  2219. // Output :
  2220. //-----------------------------------------------------------------------------
  2221. CBaseEntity *CAI_BaseNPC::EyeLookTarget( void )
  2222. {
  2223. if (m_flNextEyeLookTime < gpGlobals->curtime)
  2224. {
  2225. CBaseEntity* pBestEntity = NULL;
  2226. float fBestDist = MAX_COORD_RANGE;
  2227. float fTestDist;
  2228. CBaseEntity *pEntity = NULL;
  2229. for ( CEntitySphereQuery sphere( GetAbsOrigin(), 1024, 0 ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() )
  2230. {
  2231. if (pEntity == this)
  2232. {
  2233. continue;
  2234. }
  2235. CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
  2236. if (pNPC || (pEntity->GetFlags() & FL_CLIENT))
  2237. {
  2238. fTestDist = (GetAbsOrigin() - pEntity->EyePosition()).Length();
  2239. if (fTestDist < fBestDist)
  2240. {
  2241. if (ValidEyeTarget(pEntity->EyePosition()))
  2242. {
  2243. fBestDist = fTestDist;
  2244. pBestEntity = pEntity;
  2245. }
  2246. }
  2247. }
  2248. }
  2249. if (pBestEntity)
  2250. {
  2251. m_flNextEyeLookTime = gpGlobals->curtime + random->RandomInt(1,5);
  2252. m_hEyeLookTarget = pBestEntity;
  2253. }
  2254. }
  2255. return m_hEyeLookTarget;
  2256. }
  2257. //-----------------------------------------------------------------------------
  2258. // Purpose: Set direction that the NPC aiming their gun
  2259. // returns true is the passed Vector is in
  2260. // the caller's forward aim cone. The dot product is performed
  2261. // in 2d, making the view cone infinitely tall. By default, the
  2262. // callers aim cone is assumed to be very narrow
  2263. //-----------------------------------------------------------------------------
  2264. bool CAI_BaseNPC::FInAimCone( const Vector &vecSpot )
  2265. {
  2266. Vector los = ( vecSpot - GetAbsOrigin() );
  2267. // do this in 2D
  2268. los.z = 0;
  2269. VectorNormalize( los );
  2270. Vector facingDir = BodyDirection2D( );
  2271. float flDot = DotProduct( los, facingDir );
  2272. if (CapabilitiesGet() & bits_CAP_AIM_GUN)
  2273. {
  2274. // FIXME: query current animation for ranges
  2275. return ( flDot > DOT_30DEGREE );
  2276. }
  2277. if ( flDot > 0.994 )//!!!BUGBUG - magic number same as FacingIdeal(), what is this?
  2278. return true;
  2279. return false;
  2280. }
  2281. //-----------------------------------------------------------------------------
  2282. // Purpose: Cache whatever pose parameters we intend to use
  2283. //-----------------------------------------------------------------------------
  2284. void CAI_BaseNPC::PopulatePoseParameters( void )
  2285. {
  2286. m_poseAim_Pitch = LookupPoseParameter( "aim_pitch" );
  2287. m_poseAim_Yaw = LookupPoseParameter( "aim_yaw" );
  2288. m_poseMove_Yaw = LookupPoseParameter( "move_yaw" );
  2289. BaseClass::PopulatePoseParameters();
  2290. }
  2291. //-----------------------------------------------------------------------------
  2292. // Purpose: Set direction that the NPC aiming their gun
  2293. //-----------------------------------------------------------------------------
  2294. void CAI_BaseNPC::SetAim( const Vector &aimDir )
  2295. {
  2296. QAngle angDir;
  2297. VectorAngles( aimDir, angDir );
  2298. float curPitch = GetPoseParameter( m_poseAim_Pitch );
  2299. float curYaw = GetPoseParameter( m_poseAim_Yaw );
  2300. float newPitch;
  2301. float newYaw;
  2302. if( GetEnemy() )
  2303. {
  2304. // clamp and dampen movement
  2305. newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
  2306. float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
  2307. // float flNewTargetYaw = UTIL_ApproachAngle( flRelativeYaw, curYaw, 20 );
  2308. // float newYaw = curYaw + 0.8 * UTIL_AngleDiff( flNewTargetYaw, curYaw );
  2309. newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
  2310. }
  2311. else
  2312. {
  2313. // Sweep your weapon more slowly if you're not fighting someone
  2314. newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
  2315. float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
  2316. newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
  2317. }
  2318. newPitch = AngleNormalize( newPitch );
  2319. newYaw = AngleNormalize( newYaw );
  2320. SetPoseParameter( m_poseAim_Pitch, newPitch );
  2321. SetPoseParameter( m_poseAim_Yaw, newYaw );
  2322. // Msg("yaw %.0f (%.0f %.0f)\n", newYaw, angDir.y, GetAbsAngles().y );
  2323. // Calculate our interaction yaw.
  2324. // If we've got a small adjustment off our abs yaw, use that.
  2325. // Otherwise, use our abs yaw.
  2326. if ( fabs(newYaw) < 20 )
  2327. {
  2328. m_flInteractionYaw = angDir.y;
  2329. }
  2330. else
  2331. {
  2332. m_flInteractionYaw = GetAbsAngles().y;
  2333. }
  2334. }
  2335. void CAI_BaseNPC::RelaxAim( )
  2336. {
  2337. float curPitch = GetPoseParameter( m_poseAim_Pitch );
  2338. float curYaw = GetPoseParameter( m_poseAim_Yaw );
  2339. // dampen existing aim
  2340. float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
  2341. float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );
  2342. SetPoseParameter( m_poseAim_Pitch, newPitch );
  2343. SetPoseParameter( m_poseAim_Yaw, newYaw );
  2344. // DevMsg("relax aim %.0f %0.f\n", newPitch, newYaw );
  2345. }
  2346. //-----------------------------------------------------------------------------
  2347. void CAI_BaseNPC::AimGun()
  2348. {
  2349. if (GetEnemy())
  2350. {
  2351. Vector vecShootOrigin;
  2352. vecShootOrigin = Weapon_ShootPosition();
  2353. Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
  2354. SetAim( vecShootDir );
  2355. }
  2356. else
  2357. {
  2358. RelaxAim( );
  2359. }
  2360. }
  2361. //-----------------------------------------------------------------------------
  2362. // Purpose: Set direction that the NPC is looking
  2363. // Input :
  2364. // Output :
  2365. //-----------------------------------------------------------------------------
  2366. void CAI_BaseNPC::MaintainLookTargets ( float flInterval )
  2367. {
  2368. // --------------------------------------------------------
  2369. // Try to look at enemy if I have one
  2370. // --------------------------------------------------------
  2371. if ((CBaseEntity*)GetEnemy())
  2372. {
  2373. if ( ValidEyeTarget(GetEnemy()->EyePosition()) )
  2374. {
  2375. SetHeadDirection(GetEnemy()->EyePosition(),flInterval);
  2376. SetViewtarget( GetEnemy()->EyePosition() );
  2377. return;
  2378. }
  2379. }
  2380. #if 0
  2381. // --------------------------------------------------------
  2382. // First check if I've been assigned to look at an entity
  2383. // --------------------------------------------------------
  2384. CBaseEntity *lookTarget = EyeLookTarget();
  2385. if (lookTarget && ValidEyeTarget(lookTarget->EyePosition()))
  2386. {
  2387. SetHeadDirection(lookTarget->EyePosition(),flInterval);
  2388. SetViewtarget( lookTarget->EyePosition() );
  2389. return;
  2390. }
  2391. #endif
  2392. // --------------------------------------------------------
  2393. // If I'm moving, look at my target position
  2394. // --------------------------------------------------------
  2395. if (GetNavigator()->IsGoalActive() && ValidEyeTarget(GetNavigator()->GetCurWaypointPos()))
  2396. {
  2397. SetHeadDirection(GetNavigator()->GetCurWaypointPos(),flInterval);
  2398. SetViewtarget( GetNavigator()->GetCurWaypointPos() );
  2399. return;
  2400. }
  2401. // -------------------------------------
  2402. // If I hear a combat sounds look there
  2403. // -------------------------------------
  2404. if ( HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER) )
  2405. {
  2406. CSound *pSound = GetBestSound();
  2407. if ( pSound && pSound->IsSoundType(SOUND_COMBAT | SOUND_DANGER) )
  2408. {
  2409. if (ValidEyeTarget( pSound->GetSoundOrigin() ))
  2410. {
  2411. SetHeadDirection(pSound->GetSoundOrigin(),flInterval);
  2412. SetViewtarget( pSound->GetSoundOrigin() );
  2413. return;
  2414. }
  2415. }
  2416. }
  2417. // -------------------------------------
  2418. // Otherwise just look around randomly
  2419. // -------------------------------------
  2420. // Check that look target position is still valid
  2421. if (m_flNextEyeLookTime > gpGlobals->curtime)
  2422. {
  2423. if (!ValidEyeTarget(m_vEyeLookTarget))
  2424. {
  2425. // Force choosing of new look target
  2426. m_flNextEyeLookTime = 0;
  2427. }
  2428. }
  2429. if (m_flNextEyeLookTime < gpGlobals->curtime)
  2430. {
  2431. Vector vBodyDir = BodyDirection2D( );
  2432. /*
  2433. Vector lookSpread = Vector(0.82,0.82,0.22);
  2434. float x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  2435. float y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  2436. float z = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  2437. QAngle angles;
  2438. VectorAngles( vBodyDir, angles );
  2439. Vector forward, right, up;
  2440. AngleVectors( angles, &forward, &right, &up );
  2441. Vector vecDir = vBodyDir + (x * lookSpread.x * forward) + (y * lookSpread.y * right) + (z * lookSpread.z * up);
  2442. float lookDist = random->RandomInt(50,2000);
  2443. m_vEyeLookTarget = EyePosition() + lookDist*vecDir;
  2444. */
  2445. m_vEyeLookTarget = EyePosition() + 500*vBodyDir;
  2446. m_flNextEyeLookTime = gpGlobals->curtime + 0.5; // random->RandomInt(1,5);
  2447. }
  2448. SetHeadDirection(m_vEyeLookTarget,flInterval);
  2449. // ----------------------------------------------------
  2450. // Integrate eye turn in frame rate independent manner
  2451. // ----------------------------------------------------
  2452. float timeToUse = flInterval;
  2453. while (timeToUse > 0)
  2454. {
  2455. m_vCurEyeTarget = ((1 - m_flEyeIntegRate) * m_vCurEyeTarget + m_flEyeIntegRate * m_vEyeLookTarget);
  2456. timeToUse -= 0.1;
  2457. }
  2458. SetViewtarget( m_vCurEyeTarget );
  2459. }
  2460. //-----------------------------------------------------------------------------
  2461. // Let the motor deal with turning presentation issues
  2462. //-----------------------------------------------------------------------------
  2463. void CAI_BaseNPC::MaintainTurnActivity( )
  2464. {
  2465. // Don't bother if we're in a vehicle
  2466. if ( IsInAVehicle() )
  2467. return;
  2468. AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainTurnActivity );
  2469. GetMotor()->MaintainTurnActivity( );
  2470. }
  2471. //-----------------------------------------------------------------------------
  2472. // Here's where all motion occurs
  2473. //-----------------------------------------------------------------------------
  2474. void CAI_BaseNPC::PerformMovement()
  2475. {
  2476. // don't bother to move if the npc isn't alive
  2477. if (!IsAlive())
  2478. return;
  2479. AI_PROFILE_SCOPE(CAI_BaseNPC_PerformMovement);
  2480. g_AIMoveTimer.Start();
  2481. float flInterval = ( m_flTimeLastMovement != FLT_MAX ) ? gpGlobals->curtime - m_flTimeLastMovement : 0.1;
  2482. m_pNavigator->Move( ROUND_TO_TICKS( flInterval ) );
  2483. m_flTimeLastMovement = gpGlobals->curtime;
  2484. g_AIMoveTimer.End();
  2485. }
  2486. //-----------------------------------------------------------------------------
  2487. // Updates to npc after movement is completed
  2488. //-----------------------------------------------------------------------------
  2489. void CAI_BaseNPC::PostMovement()
  2490. {
  2491. AI_PROFILE_SCOPE( CAI_BaseNPC_PostMovement );
  2492. InvalidateBoneCache();
  2493. if ( GetModelPtr() && GetModelPtr()->SequencesAvailable() )
  2494. {
  2495. float flInterval = GetAnimTimeInterval();
  2496. if ( CapabilitiesGet() & bits_CAP_AIM_GUN )
  2497. {
  2498. AI_PROFILE_SCOPE( CAI_BaseNPC_PM_AimGun );
  2499. AimGun();
  2500. }
  2501. else
  2502. {
  2503. // NPCs with bits_CAP_AIM_GUN update this in SetAim, called by AimGun.
  2504. m_flInteractionYaw = GetAbsAngles().y;
  2505. }
  2506. // set look targets for npcs with animated faces
  2507. if ( CapabilitiesGet() & bits_CAP_ANIMATEDFACE )
  2508. {
  2509. AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainLookTargets );
  2510. MaintainLookTargets(flInterval);
  2511. }
  2512. }
  2513. {
  2514. AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainTurnActivity );
  2515. // update turning as needed
  2516. MaintainTurnActivity( );
  2517. }
  2518. }
  2519. //-----------------------------------------------------------------------------
  2520. float g_AINextDisabledMessageTime;
  2521. extern bool IsInCommentaryMode( void );
  2522. bool CAI_BaseNPC::PreThink( void )
  2523. {
  2524. // ----------------------------------------------------------
  2525. // Skip AI if its been disabled or networks haven't been
  2526. // loaded, and put a warning message on the screen
  2527. //
  2528. // Don't do this if the convar wants it hidden
  2529. // ----------------------------------------------------------
  2530. #ifndef PORTAL2
  2531. if ( (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI || !g_pAINetworkManager->NetworksLoaded()) )
  2532. {
  2533. if ( gpGlobals->curtime >= g_AINextDisabledMessageTime && !IsInCommentaryMode() )
  2534. {
  2535. g_AINextDisabledMessageTime = gpGlobals->curtime + 0.5f;
  2536. hudtextparms_s tTextParam;
  2537. tTextParam.x = 0.7;
  2538. tTextParam.y = 0.65;
  2539. tTextParam.effect = 0;
  2540. tTextParam.r1 = 255;
  2541. tTextParam.g1 = 255;
  2542. tTextParam.b1 = 255;
  2543. tTextParam.a1 = 255;
  2544. tTextParam.r2 = 255;
  2545. tTextParam.g2 = 255;
  2546. tTextParam.b2 = 255;
  2547. tTextParam.a2 = 255;
  2548. tTextParam.fadeinTime = 0;
  2549. tTextParam.fadeoutTime = 0;
  2550. tTextParam.holdTime = 0.6;
  2551. tTextParam.fxTime = 0;
  2552. tTextParam.channel = 1;
  2553. UTIL_HudMessageAll( tTextParam, "A.I. Disabled...\n" );
  2554. }
  2555. SetActivity( ACT_IDLE );
  2556. return false;
  2557. }
  2558. #endif // !PORTAL2
  2559. // --------------------------------------------------------
  2560. // If debug stepping
  2561. // --------------------------------------------------------
  2562. if (CAI_BaseNPC::m_nDebugBits & bits_debugStepAI)
  2563. {
  2564. if (m_nDebugCurIndex >= CAI_BaseNPC::m_nDebugPauseIndex)
  2565. {
  2566. if (!GetNavigator()->IsGoalActive())
  2567. {
  2568. m_flPlaybackRate = 0;
  2569. }
  2570. return false;
  2571. }
  2572. else
  2573. {
  2574. m_flPlaybackRate = 1;
  2575. }
  2576. }
  2577. if ( m_hOpeningDoor.Get() && AIIsDebuggingDoors( this ) )
  2578. {
  2579. NDebugOverlay::Line( EyePosition(), m_hOpeningDoor->WorldSpaceCenter(), 255, 255, 255, false, .1 );
  2580. }
  2581. return true;
  2582. }
  2583. //-----------------------------------------------------------------------------
  2584. extern ConVar ai_sequence_debug;
  2585. void CAI_BaseNPC::RunAnimation( void )
  2586. {
  2587. VPROF_BUDGET( "CAI_BaseNPC_RunAnimation", VPROF_BUDGETGROUP_SERVER_ANIM );
  2588. if ( !GetModelPtr() )
  2589. return;
  2590. float flInterval = GetAnimTimeInterval();
  2591. StudioFrameAdvance( ); // animate
  2592. if ((CAI_BaseNPC::m_nDebugBits & bits_debugStepAI) && !GetNavigator()->IsGoalActive())
  2593. {
  2594. flInterval = 0;
  2595. }
  2596. // start or end a fidget
  2597. // This needs a better home -- switching animations over time should be encapsulated on a per-activity basis
  2598. // perhaps MaintainActivity() or a ShiftAnimationOverTime() or something.
  2599. if ( m_NPCState != NPC_STATE_SCRIPT && m_NPCState != NPC_STATE_DEAD && m_Activity == ACT_IDLE && IsActivityFinished() )
  2600. {
  2601. int iSequence;
  2602. // FIXME: this doesn't reissue a translation, so if the idle activity translation changes over time, it'll never get reset
  2603. if ( SequenceLoops() )
  2604. {
  2605. // animation does loop, which means we're playing subtle idle. Might need to fidget.
  2606. iSequence = SelectWeightedSequence ( m_translatedActivity );
  2607. }
  2608. else
  2609. {
  2610. // animation that just ended doesn't loop! That means we just finished a fidget
  2611. // and should return to our heaviest weighted idle (the subtle one)
  2612. iSequence = SelectHeaviestSequence ( m_translatedActivity );
  2613. }
  2614. if ( iSequence != ACTIVITY_NOT_AVAILABLE )
  2615. {
  2616. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  2617. {
  2618. DevMsg("RunAnimation calling ResetSequence %s\n", GetSequenceName(iSequence));
  2619. }
  2620. ResetSequence( iSequence ); // Set to new anim (if it's there)
  2621. //Adrian: Basically everywhere else in the AI code this variable gets set to whatever our sequence is.
  2622. //But here it doesn't and not setting it causes any animation set through here to be stomped by the
  2623. //ideal sequence before it has a chance of playing out (since there's code that reselects the ideal
  2624. //sequence if it doesn't match the current one).
  2625. if ( hl2_episodic.GetBool() )
  2626. {
  2627. m_nIdealSequence = iSequence;
  2628. }
  2629. }
  2630. }
  2631. DispatchAnimEvents( this );
  2632. }
  2633. //-----------------------------------------------------------------------------
  2634. void CAI_BaseNPC::PostRun( void )
  2635. {
  2636. AI_PROFILE_SCOPE(CAI_BaseNPC_PostRun);
  2637. g_AIPostRunTimer.Start();
  2638. if ( !IsMoving() )
  2639. {
  2640. if ( GetIdealActivity() == ACT_WALK ||
  2641. GetIdealActivity() == ACT_RUN ||
  2642. GetIdealActivity() == ACT_WALK_AIM ||
  2643. GetIdealActivity() == ACT_RUN_AIM )
  2644. {
  2645. PostRunStopMoving();
  2646. }
  2647. }
  2648. RunAnimation();
  2649. // update slave items (weapons)
  2650. Weapon_FrameUpdate();
  2651. g_AIPostRunTimer.End();
  2652. }
  2653. //-----------------------------------------------------------------------------
  2654. void CAI_BaseNPC::PostRunStopMoving()
  2655. {
  2656. DbgNavMsg1( this, "NPC %s failed to stop properly, slamming activity\n", GetDebugName() );
  2657. if ( !GetNavigator()->SetGoalFromStoppingPath() )
  2658. SetIdealActivity( GetStoppedActivity() ); // This is to prevent running in place
  2659. }
  2660. //-----------------------------------------------------------------------------
  2661. bool CAI_BaseNPC::ShouldAlwaysThink()
  2662. {
  2663. // @TODO (toml 07-08-03): This needs to be beefed up.
  2664. // There are simple cases already seen where an AI can briefly leave
  2665. // the PVS while navigating to the player. Perhaps should incorporate a heuristic taking into
  2666. // account mode, enemy, last time saw player, player range etc. For example, if enemy is player,
  2667. // and player is within 100 feet, and saw the player within the past 15 seconds, keep running...
  2668. return HasSpawnFlags(SF_NPC_ALWAYSTHINK);
  2669. }
  2670. //-----------------------------------------------------------------------------
  2671. // Purpose: Return true if the Player should be running the auto-move-out-of-way
  2672. // avoidance code, which also means that the NPC shouldn't care about running into the Player.
  2673. //-----------------------------------------------------------------------------
  2674. bool CAI_BaseNPC::ShouldPlayerAvoid( void )
  2675. {
  2676. if ( GetState() == NPC_STATE_SCRIPT )
  2677. return true;
  2678. if ( IsInAScript() )
  2679. return true;
  2680. if ( IsInLockedScene() == true )
  2681. return true;
  2682. if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) )
  2683. return true;
  2684. return false;
  2685. }
  2686. //-----------------------------------------------------------------------------
  2687. void CAI_BaseNPC::UpdateEfficiency( bool bInPVS )
  2688. {
  2689. // Sleeping NPCs always dormant
  2690. if ( GetSleepState() != AISS_AWAKE )
  2691. {
  2692. SetEfficiency( AIE_DORMANT );
  2693. return;
  2694. }
  2695. m_bInChoreo = ( GetState() == NPC_STATE_SCRIPT || IsCurSchedule( SCHED_SCENE_GENERIC, false ) );
  2696. if ( !ShouldUseEfficiency() )
  2697. {
  2698. SetEfficiency( AIE_NORMAL );
  2699. SetMoveEfficiency( AIME_NORMAL );
  2700. return;
  2701. }
  2702. //---------------------------------
  2703. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  2704. static Vector vPlayerEyePosition;
  2705. static Vector vPlayerForward;
  2706. static int iPrevFrame = -1;
  2707. if ( gpGlobals->framecount != iPrevFrame )
  2708. {
  2709. iPrevFrame = gpGlobals->framecount;
  2710. if ( pPlayer )
  2711. {
  2712. pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL );
  2713. }
  2714. }
  2715. Vector vToNPC = GetAbsOrigin() - vPlayerEyePosition;
  2716. float playerDist = VectorNormalize( vToNPC );
  2717. bool bPlayerFacing;
  2718. bool bClientPVSExpanded = UTIL_ClientPVSIsExpanded();
  2719. if ( pPlayer )
  2720. {
  2721. bPlayerFacing = ( bClientPVSExpanded || ( bInPVS && vPlayerForward.Dot( vToNPC ) > 0 ) );
  2722. }
  2723. else
  2724. {
  2725. playerDist = 0;
  2726. bPlayerFacing = true;
  2727. }
  2728. //---------------------------------
  2729. bool bInVisibilityPVS = ( bClientPVSExpanded && UTIL_FindClientInVisibilityPVS( edict() ) != NULL );
  2730. //---------------------------------
  2731. if ( ( bInPVS && ( bPlayerFacing || playerDist < 25*12 ) ) || bClientPVSExpanded )
  2732. {
  2733. SetMoveEfficiency( AIME_NORMAL );
  2734. }
  2735. else
  2736. {
  2737. SetMoveEfficiency( AIME_EFFICIENT );
  2738. }
  2739. //---------------------------------
  2740. if ( !IsRetail() && ai_efficiency_override.GetInt() > AIE_NORMAL && ai_efficiency_override.GetInt() <= AIE_DORMANT )
  2741. {
  2742. SetEfficiency( (AI_Efficiency_t)ai_efficiency_override.GetInt() );
  2743. return;
  2744. }
  2745. //---------------------------------
  2746. // Some conditions will always force normal
  2747. if ( gpGlobals->curtime - GetLastAttackTime() < .15 )
  2748. {
  2749. SetEfficiency( AIE_NORMAL );
  2750. return;
  2751. }
  2752. bool bFramerateOk = ( gpGlobals->frametime < ai_frametime_limit.GetFloat() );
  2753. if ( m_bForceConditionsGather ||
  2754. gpGlobals->curtime - GetLastAttackTime() < .2 ||
  2755. gpGlobals->curtime - m_flLastDamageTime < .2 ||
  2756. ( GetState() < NPC_STATE_IDLE || GetState() > NPC_STATE_SCRIPT ) ||
  2757. ( ( bInPVS || bInVisibilityPVS ) &&
  2758. ( ( GetTask() && !TaskIsRunning() ) ||
  2759. GetTaskInterrupt() > 0 ||
  2760. m_bInChoreo ) ) )
  2761. {
  2762. SetEfficiency( ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT );
  2763. return;
  2764. }
  2765. AI_Efficiency_t minEfficiency;
  2766. if ( !ShouldDefaultEfficient() )
  2767. {
  2768. minEfficiency = ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT;
  2769. }
  2770. else
  2771. {
  2772. minEfficiency = ( bFramerateOk ) ? AIE_EFFICIENT : AIE_VERY_EFFICIENT;
  2773. }
  2774. // Stay normal if there's any chance of a relevant danger sound
  2775. bool bPotentialDanger = false;
  2776. if ( GetSoundInterests() & SOUND_DANGER )
  2777. {
  2778. int iSound = CSoundEnt::ActiveList();
  2779. while ( iSound != SOUNDLIST_EMPTY )
  2780. {
  2781. CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound );
  2782. float hearingSensitivity = HearingSensitivity();
  2783. Vector vEarPosition = EarPosition();
  2784. if ( pCurrentSound && (SOUND_DANGER & pCurrentSound->SoundType()) )
  2785. {
  2786. float flHearDistanceSq = pCurrentSound->Volume() * hearingSensitivity;
  2787. flHearDistanceSq *= flHearDistanceSq;
  2788. if ( pCurrentSound->GetSoundOrigin().DistToSqr( vEarPosition ) <= flHearDistanceSq )
  2789. {
  2790. bPotentialDanger = true;
  2791. break;
  2792. }
  2793. }
  2794. iSound = pCurrentSound->NextSound();
  2795. }
  2796. }
  2797. if ( bPotentialDanger )
  2798. {
  2799. SetEfficiency( minEfficiency );
  2800. return;
  2801. }
  2802. //---------------------------------
  2803. if ( !pPlayer )
  2804. {
  2805. // No heuristic currently for dedicated servers
  2806. SetEfficiency( minEfficiency );
  2807. return;
  2808. }
  2809. enum
  2810. {
  2811. DIST_NEAR,
  2812. DIST_MID,
  2813. DIST_FAR
  2814. };
  2815. int range;
  2816. if ( bInPVS )
  2817. {
  2818. if ( playerDist < 15*12 )
  2819. {
  2820. SetEfficiency( minEfficiency );
  2821. return;
  2822. }
  2823. range = ( playerDist < 50*12 ) ? DIST_NEAR :
  2824. ( playerDist < 200*12 ) ? DIST_MID : DIST_FAR;
  2825. }
  2826. else
  2827. {
  2828. range = ( playerDist < 25*12 ) ? DIST_NEAR :
  2829. ( playerDist < 100*12 ) ? DIST_MID : DIST_FAR;
  2830. }
  2831. // Efficiency mappings
  2832. int state = GetState();
  2833. if (state == NPC_STATE_SCRIPT ) // Treat script as alert. Already confirmed not in PVS
  2834. state = NPC_STATE_ALERT;
  2835. static AI_Efficiency_t mappings[] =
  2836. {
  2837. // Idle
  2838. // In PVS
  2839. // Facing
  2840. AIE_NORMAL,
  2841. AIE_EFFICIENT,
  2842. AIE_EFFICIENT,
  2843. // Not facing
  2844. AIE_EFFICIENT,
  2845. AIE_EFFICIENT,
  2846. AIE_VERY_EFFICIENT,
  2847. // Not in PVS
  2848. AIE_VERY_EFFICIENT,
  2849. AIE_SUPER_EFFICIENT,
  2850. AIE_SUPER_EFFICIENT,
  2851. // Alert
  2852. // In PVS
  2853. // Facing
  2854. AIE_NORMAL,
  2855. AIE_EFFICIENT,
  2856. AIE_EFFICIENT,
  2857. // Not facing
  2858. AIE_NORMAL,
  2859. AIE_EFFICIENT,
  2860. AIE_EFFICIENT,
  2861. // Not in PVS
  2862. AIE_EFFICIENT,
  2863. AIE_VERY_EFFICIENT,
  2864. AIE_SUPER_EFFICIENT,
  2865. // Combat
  2866. // In PVS
  2867. // Facing
  2868. AIE_NORMAL,
  2869. AIE_NORMAL,
  2870. AIE_EFFICIENT,
  2871. // Not facing
  2872. AIE_NORMAL,
  2873. AIE_EFFICIENT,
  2874. AIE_EFFICIENT,
  2875. // Not in PVS
  2876. AIE_NORMAL,
  2877. AIE_EFFICIENT,
  2878. AIE_VERY_EFFICIENT,
  2879. };
  2880. static const int stateBase[] = { 0, 9, 18 };
  2881. const int NOT_FACING_OFFSET = 3;
  2882. const int NO_PVS_OFFSET = 6;
  2883. int iStateOffset = stateBase[state - NPC_STATE_IDLE] ;
  2884. int iFacingOffset = (!bInPVS || bPlayerFacing) ? 0 : NOT_FACING_OFFSET;
  2885. int iPVSOffset = (bInPVS) ? 0 : NO_PVS_OFFSET;
  2886. int iMapping = iStateOffset + iPVSOffset + iFacingOffset + range;
  2887. Assert( iMapping < ARRAYSIZE( mappings ) );
  2888. AI_Efficiency_t efficiency = mappings[iMapping];
  2889. //---------------------------------
  2890. AI_Efficiency_t maxEfficiency = AIE_SUPER_EFFICIENT;
  2891. if ( bInVisibilityPVS && state >= NPC_STATE_ALERT )
  2892. {
  2893. maxEfficiency = AIE_EFFICIENT;
  2894. }
  2895. else if ( bInVisibilityPVS || HasCondition( COND_SEE_PLAYER ) )
  2896. {
  2897. maxEfficiency = AIE_VERY_EFFICIENT;
  2898. }
  2899. //---------------------------------
  2900. SetEfficiency( clamp( efficiency, minEfficiency, maxEfficiency ) );
  2901. }
  2902. //-----------------------------------------------------------------------------
  2903. void CAI_BaseNPC::UpdateSleepState( bool bInPVS )
  2904. {
  2905. if ( GetSleepState() > AISS_AWAKE )
  2906. {
  2907. CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
  2908. if ( !pLocalPlayer )
  2909. {
  2910. if ( gpGlobals->maxClients > 1 )
  2911. {
  2912. Wake();
  2913. }
  2914. else
  2915. {
  2916. Warning( "CAI_BaseNPC::UpdateSleepState called with NULL pLocalPlayer\n" );
  2917. }
  2918. return;
  2919. }
  2920. if ( m_flWakeRadius > .1 && !(pLocalPlayer->GetFlags() & FL_NOTARGET) && ( pLocalPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() <= Square(m_flWakeRadius) )
  2921. Wake();
  2922. else if ( GetSleepState() == AISS_WAITING_FOR_PVS )
  2923. {
  2924. if ( bInPVS )
  2925. Wake();
  2926. }
  2927. else if ( GetSleepState() == AISS_WAITING_FOR_THREAT )
  2928. {
  2929. if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
  2930. Wake();
  2931. else
  2932. {
  2933. if ( bInPVS )
  2934. {
  2935. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  2936. {
  2937. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  2938. if ( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) && pPlayer->FVisible( this ) )
  2939. Wake();
  2940. }
  2941. }
  2942. // Should check for visible danger sounds
  2943. if ( (GetSoundInterests() & SOUND_DANGER) && !(HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN)) )
  2944. {
  2945. int iSound = CSoundEnt::ActiveList();
  2946. while ( iSound != SOUNDLIST_EMPTY )
  2947. {
  2948. CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound );
  2949. Assert( pCurrentSound );
  2950. if ( (pCurrentSound->SoundType() & SOUND_DANGER) &&
  2951. GetSenses()->CanHearSound( pCurrentSound ) &&
  2952. SoundIsVisible( pCurrentSound ))
  2953. {
  2954. Wake();
  2955. break;
  2956. }
  2957. iSound = pCurrentSound->NextSound();
  2958. }
  2959. }
  2960. }
  2961. }
  2962. }
  2963. else
  2964. {
  2965. // NPC is awake
  2966. // Don't let an NPC sleep if they're running a script!
  2967. if( !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT )
  2968. {
  2969. if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS) )
  2970. {
  2971. if( !HasCondition(COND_IN_PVS) )
  2972. {
  2973. SetSleepState( AISS_WAITING_FOR_PVS );
  2974. Sleep();
  2975. }
  2976. }
  2977. if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS) )
  2978. {
  2979. if( HasCondition(COND_IN_PVS) )
  2980. {
  2981. // OK, we're in the player's PVS. Now switch to regular old AUTO_PVS sleep rules.
  2982. AddSleepFlags(AI_SLEEP_FLAG_AUTO_PVS);
  2983. RemoveSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS);
  2984. }
  2985. }
  2986. }
  2987. }
  2988. }
  2989. //-----------------------------------------------------------------------------
  2990. struct AIRebalanceInfo_t
  2991. {
  2992. CAI_BaseNPC * pNPC;
  2993. int iNextThinkTick;
  2994. bool bInPVS;
  2995. float dotPlayer;
  2996. float distPlayer;
  2997. };
  2998. int __cdecl ThinkRebalanceCompare( const AIRebalanceInfo_t *pLeft, const AIRebalanceInfo_t *pRight )
  2999. {
  3000. int base = pLeft->iNextThinkTick - pRight->iNextThinkTick;
  3001. if ( base != 0 )
  3002. return base;
  3003. if ( !pLeft->bInPVS && !pRight->bInPVS )
  3004. return 0;
  3005. if ( !pLeft->bInPVS )
  3006. return 1;
  3007. if ( !pRight->bInPVS )
  3008. return -1;
  3009. if ( pLeft->dotPlayer < 0 && pRight->dotPlayer < 0 )
  3010. return 0;
  3011. if ( pLeft->dotPlayer < 0 )
  3012. return 1;
  3013. if ( pRight->dotPlayer < 0 )
  3014. return -1;
  3015. const float NEAR_PLAYER = 50*12;
  3016. if ( pLeft->distPlayer < NEAR_PLAYER && pRight->distPlayer >= NEAR_PLAYER )
  3017. return -1;
  3018. if ( pRight->distPlayer < NEAR_PLAYER && pLeft->distPlayer >= NEAR_PLAYER )
  3019. return 1;
  3020. if ( pLeft->dotPlayer > pRight->dotPlayer )
  3021. return -1;
  3022. if ( pLeft->dotPlayer < pRight->dotPlayer )
  3023. return 1;
  3024. return 0;
  3025. }
  3026. inline bool CAI_BaseNPC::CanThinkRebalance()
  3027. {
  3028. if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink )
  3029. {
  3030. return false;
  3031. }
  3032. if ( m_bInChoreo )
  3033. {
  3034. return false;
  3035. }
  3036. if ( m_NPCState == NPC_STATE_DEAD )
  3037. {
  3038. return false;
  3039. }
  3040. if ( GetSleepState() != AISS_AWAKE )
  3041. {
  3042. return false;
  3043. }
  3044. if ( !m_bUsingStandardThinkTime /*&& m_iFrameBlocked == -1 */ )
  3045. {
  3046. return false;
  3047. }
  3048. return true;
  3049. }
  3050. void CAI_BaseNPC::RebalanceThinks()
  3051. {
  3052. bool bDebugThinkTicks = ai_debug_think_ticks.GetBool();
  3053. if ( bDebugThinkTicks )
  3054. {
  3055. static int iPrevTick;
  3056. static int nThinksInTick;
  3057. static int nRebalanceableThinksInTick;
  3058. if ( gpGlobals->tickcount != iPrevTick )
  3059. {
  3060. DevMsg( "NPC per tick is %d [%d] (tick %d, frame %d)\n", nRebalanceableThinksInTick, nThinksInTick, iPrevTick, gpGlobals->framecount );
  3061. iPrevTick = gpGlobals->tickcount;
  3062. nThinksInTick = 0;
  3063. nRebalanceableThinksInTick = 0;
  3064. }
  3065. nThinksInTick++;
  3066. if ( CanThinkRebalance() )
  3067. nRebalanceableThinksInTick++;
  3068. }
  3069. if ( ShouldRebalanceThinks() && gpGlobals->tickcount >= gm_iNextThinkRebalanceTick )
  3070. {
  3071. AI_PROFILE_SCOPE(AI_Think_Rebalance );
  3072. static CUtlVector<AIRebalanceInfo_t> rebalanceCandidates( 16, 64 );
  3073. gm_iNextThinkRebalanceTick = gpGlobals->tickcount + TIME_TO_TICKS( random->RandomFloat( 3, 5) );
  3074. int i;
  3075. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  3076. Vector vPlayerForward;
  3077. Vector vPlayerEyePosition;
  3078. vPlayerForward.Init();
  3079. vPlayerEyePosition.Init();
  3080. if ( pPlayer )
  3081. {
  3082. pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL );
  3083. }
  3084. int iTicksPer10Hz = TIME_TO_TICKS( .1 );
  3085. int iMinTickRebalance = gpGlobals->tickcount - 1; // -1 needed for alternate ticks
  3086. int iMaxTickRebalance = gpGlobals->tickcount + iTicksPer10Hz;
  3087. for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
  3088. {
  3089. CAI_BaseNPC *pCandidate = g_AI_Manager.AccessAIs()[i];
  3090. if ( pCandidate->CanThinkRebalance() &&
  3091. ( pCandidate->GetNextThinkTick() >= iMinTickRebalance &&
  3092. pCandidate->GetNextThinkTick() < iMaxTickRebalance ) )
  3093. {
  3094. int iInfo = rebalanceCandidates.AddToTail();
  3095. rebalanceCandidates[iInfo].pNPC = pCandidate;
  3096. rebalanceCandidates[iInfo].iNextThinkTick = pCandidate->GetNextThinkTick();
  3097. if ( pCandidate->IsFlaggedEfficient() )
  3098. {
  3099. rebalanceCandidates[iInfo].bInPVS = false;
  3100. }
  3101. else if ( pPlayer )
  3102. {
  3103. Vector vToCandidate = pCandidate->EyePosition() - vPlayerEyePosition;
  3104. rebalanceCandidates[iInfo].bInPVS = ( UTIL_FindClientInPVS( pCandidate->edict() ) != NULL );
  3105. rebalanceCandidates[iInfo].distPlayer = VectorNormalize( vToCandidate );
  3106. rebalanceCandidates[iInfo].dotPlayer = vPlayerForward.Dot( vToCandidate );
  3107. }
  3108. else
  3109. {
  3110. rebalanceCandidates[iInfo].bInPVS = true;
  3111. rebalanceCandidates[iInfo].dotPlayer = 1;
  3112. rebalanceCandidates[iInfo].distPlayer = 0;
  3113. }
  3114. }
  3115. else if ( bDebugThinkTicks )
  3116. DevMsg( " Ignoring %d\n", pCandidate->GetNextThinkTick() );
  3117. }
  3118. if ( rebalanceCandidates.Count() )
  3119. {
  3120. rebalanceCandidates.Sort( ThinkRebalanceCompare );
  3121. int iMaxThinkersPerTick = ceil( (float)( rebalanceCandidates.Count() + 1 ) / (float)iTicksPer10Hz ); // +1 to account for "this"
  3122. int iCurTickDistributing = MIN( gpGlobals->tickcount, rebalanceCandidates[0].iNextThinkTick );
  3123. int iRemainingThinksToDistribute = iMaxThinkersPerTick - 1; // Start with one fewer first time because "this" is
  3124. // always gets a slot in the current tick to avoid complications
  3125. // in the calculation of "last think"
  3126. if ( bDebugThinkTicks )
  3127. {
  3128. DevMsg( "Rebalance %d!\n", rebalanceCandidates.Count() + 1 );
  3129. DevMsg( " Distributing %d\n", iCurTickDistributing );
  3130. }
  3131. for ( i = 0; i < rebalanceCandidates.Count(); i++ )
  3132. {
  3133. if ( iRemainingThinksToDistribute == 0 || rebalanceCandidates[i].iNextThinkTick > iCurTickDistributing )
  3134. {
  3135. if ( rebalanceCandidates[i].iNextThinkTick <= iCurTickDistributing )
  3136. {
  3137. iCurTickDistributing = iCurTickDistributing + 1;
  3138. }
  3139. else
  3140. {
  3141. iCurTickDistributing = rebalanceCandidates[i].iNextThinkTick;
  3142. }
  3143. if ( bDebugThinkTicks )
  3144. DevMsg( " Distributing %d\n", iCurTickDistributing );
  3145. iRemainingThinksToDistribute = iMaxThinkersPerTick;
  3146. }
  3147. if ( rebalanceCandidates[i].pNPC->GetNextThinkTick() != iCurTickDistributing )
  3148. {
  3149. if ( bDebugThinkTicks )
  3150. DevMsg( " Bumping %d to %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick(), iCurTickDistributing );
  3151. rebalanceCandidates[i].pNPC->SetNextThink( TICKS_TO_TIME( iCurTickDistributing ) );
  3152. }
  3153. else if ( bDebugThinkTicks )
  3154. {
  3155. DevMsg( " Leaving %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick() );
  3156. }
  3157. iRemainingThinksToDistribute--;
  3158. }
  3159. }
  3160. rebalanceCandidates.RemoveAll();
  3161. if ( bDebugThinkTicks )
  3162. {
  3163. DevMsg( "New distribution is:\n");
  3164. for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
  3165. {
  3166. DevMsg( " %d\n", g_AI_Manager.AccessAIs()[i]->GetNextThinkTick() );
  3167. }
  3168. }
  3169. Assert( GetNextThinkTick() == TICK_NEVER_THINK ); // never change this objects tick
  3170. }
  3171. }
  3172. static float g_NpcTimeThisFrame;
  3173. static float g_StartTimeCurThink;
  3174. bool CAI_BaseNPC::PreNPCThink()
  3175. {
  3176. static int iPrevFrame = -1;
  3177. static float frameTimeLimit = FLT_MAX;
  3178. bool bUseThinkLimits = ( !m_bInChoreo && ShouldUseFrameThinkLimits() );
  3179. #ifdef _DEBUG
  3180. const float NPC_THINK_LIMIT = 30.0 / 1000.0;
  3181. #else
  3182. const float NPC_THINK_LIMIT = (10.0 / 1000.0);
  3183. #endif
  3184. g_StartTimeCurThink = 0;
  3185. if ( bUseThinkLimits )
  3186. {
  3187. if ( m_iFrameBlocked == gpGlobals->framecount )
  3188. {
  3189. DbgFrameLimitMsg( "Stalled %d (%d)\n", this, gpGlobals->framecount );
  3190. SetNextThink( gpGlobals->curtime );
  3191. return false;
  3192. }
  3193. else if ( gpGlobals->framecount != iPrevFrame )
  3194. {
  3195. DbgFrameLimitMsg( "--- FRAME: %d (%d)\n", this, gpGlobals->framecount );
  3196. float timescale = engine->GetTimescale();
  3197. if ( timescale < 1.0f )
  3198. {
  3199. timescale = 1.0f;
  3200. }
  3201. iPrevFrame = gpGlobals->framecount;
  3202. frameTimeLimit = NPC_THINK_LIMIT * timescale;
  3203. g_NpcTimeThisFrame = 0;
  3204. }
  3205. else
  3206. {
  3207. if ( g_NpcTimeThisFrame > NPC_THINK_LIMIT )
  3208. {
  3209. float timeSinceLastRealThink = gpGlobals->curtime - m_flLastRealThinkTime;
  3210. // Don't bump anyone more that a quarter second
  3211. if ( timeSinceLastRealThink <= .25 )
  3212. {
  3213. DbgFrameLimitMsg( "Bumped %d (%d)\n", this, gpGlobals->framecount );
  3214. m_iFrameBlocked = gpGlobals->framecount;
  3215. SetNextThink( gpGlobals->curtime );
  3216. return false;
  3217. }
  3218. else
  3219. {
  3220. DbgFrameLimitMsg( "(Over %d )\n", this );
  3221. }
  3222. }
  3223. }
  3224. DbgFrameLimitMsg( "Running %d (%d)\n", this, gpGlobals->framecount );
  3225. g_StartTimeCurThink = Plat_FloatTime();
  3226. m_iFrameBlocked = -1;
  3227. m_nLastThinkTick = TIME_TO_TICKS( m_flLastRealThinkTime );
  3228. }
  3229. return true;
  3230. }
  3231. void CAI_BaseNPC::PostNPCThink( void )
  3232. {
  3233. if ( g_StartTimeCurThink != 0.0 )
  3234. {
  3235. g_NpcTimeThisFrame += Plat_FloatTime() - g_StartTimeCurThink;
  3236. }
  3237. }
  3238. void CAI_BaseNPC::CallNPCThink( void )
  3239. {
  3240. RebalanceThinks();
  3241. //---------------------------------
  3242. m_bUsingStandardThinkTime = false;
  3243. //---------------------------------
  3244. if ( !PreNPCThink() )
  3245. {
  3246. return;
  3247. }
  3248. // reduce cache queries by locking model in memory
  3249. MDLCACHE_CRITICAL_SECTION();
  3250. this->NPCThink();
  3251. m_flLastRealThinkTime = gpGlobals->curtime;
  3252. PostNPCThink();
  3253. }
  3254. bool NPC_CheckBrushExclude( CBaseEntity *pEntity, CBaseEntity *pBrush )
  3255. {
  3256. CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
  3257. if ( pNPC )
  3258. {
  3259. return pNPC->GetMoveProbe()->ShouldBrushBeIgnored( pBrush );
  3260. }
  3261. return false;
  3262. }
  3263. class CTraceFilterPlayerAvoidance : public CTraceFilterEntitiesOnly
  3264. {
  3265. public:
  3266. CTraceFilterPlayerAvoidance( const CBaseEntity *pEntity ) { m_pIgnore = pEntity; }
  3267. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  3268. {
  3269. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  3270. if ( m_pIgnore == pEntity )
  3271. return false;
  3272. if ( pEntity->IsPlayer() )
  3273. return true;
  3274. return false;
  3275. }
  3276. private:
  3277. const CBaseEntity *m_pIgnore;
  3278. };
  3279. void CAI_BaseNPC::GetPlayerAvoidBounds( Vector *pMins, Vector *pMaxs )
  3280. {
  3281. *pMins = WorldAlignMins();
  3282. *pMaxs = WorldAlignMaxs();
  3283. }
  3284. ConVar ai_debug_avoidancebounds( "ai_debug_avoidancebounds", "0" );
  3285. void CAI_BaseNPC::SetPlayerAvoidState( void )
  3286. {
  3287. bool bShouldPlayerAvoid = false;
  3288. Vector vNothing;
  3289. GetSequenceLinearMotion( GetSequence(), &vNothing );
  3290. bool bIsMoving = ( IsMoving() || ( vNothing != vec3_origin ) );
  3291. //If we are coming out of a script, check if we are stuck inside the player.
  3292. if ( m_bPerformAvoidance || ( ShouldPlayerAvoid() && bIsMoving ) )
  3293. {
  3294. trace_t trace;
  3295. Vector vMins, vMaxs;
  3296. GetPlayerAvoidBounds( &vMins, &vMaxs );
  3297. CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
  3298. if ( pLocalPlayer )
  3299. {
  3300. bShouldPlayerAvoid = IsBoxIntersectingBox( GetAbsOrigin() + vMins, GetAbsOrigin() + vMaxs,
  3301. pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMins(), pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMaxs() );
  3302. }
  3303. if ( ai_debug_avoidancebounds.GetBool() )
  3304. {
  3305. int iRed = ( bShouldPlayerAvoid == true ) ? 255 : 0;
  3306. NDebugOverlay::Box( GetAbsOrigin(), vMins, vMaxs, iRed, 0, 255, 64, 0.1 );
  3307. }
  3308. }
  3309. m_bPlayerAvoidState = ShouldPlayerAvoid();
  3310. m_bPerformAvoidance = bShouldPlayerAvoid;
  3311. if ( GetCollisionGroup() == COLLISION_GROUP_NPC || GetCollisionGroup() == COLLISION_GROUP_NPC_ACTOR )
  3312. {
  3313. if ( m_bPerformAvoidance == true )
  3314. {
  3315. SetCollisionGroup( COLLISION_GROUP_NPC_ACTOR );
  3316. }
  3317. else
  3318. {
  3319. SetCollisionGroup( COLLISION_GROUP_NPC );
  3320. }
  3321. }
  3322. }
  3323. //-----------------------------------------------------------------------------
  3324. // Purpose: Enables player avoidance when the player's vphysics shadow penetrates our vphysics shadow. This can
  3325. // happen when the player is hit by a combine ball, which pushes them into an adjacent npc. Subclasses should
  3326. // override this if it causes problems, but in general this will solve cases of the player getting stuck in
  3327. // the NPC from being pushed.
  3328. //-----------------------------------------------------------------------------
  3329. void CAI_BaseNPC::PlayerPenetratingVPhysics( void )
  3330. {
  3331. m_bPerformAvoidance = true;
  3332. }
  3333. //-----------------------------------------------------------------------------
  3334. bool CAI_BaseNPC::CheckPVSCondition()
  3335. {
  3336. bool bInPVS = ( UTIL_FindClientInPVS( edict() ) != NULL ) || (UTIL_ClientPVSIsExpanded() && UTIL_FindClientInVisibilityPVS( edict() ));
  3337. if ( bInPVS )
  3338. SetCondition( COND_IN_PVS );
  3339. else
  3340. ClearCondition( COND_IN_PVS );
  3341. return bInPVS;
  3342. }
  3343. //-----------------------------------------------------------------------------
  3344. // NPC Think - calls out to core AI functions and handles this
  3345. // npc's specific animation events
  3346. //
  3347. void CAI_BaseNPC::NPCThink( void )
  3348. {
  3349. if ( m_bCheckContacts )
  3350. {
  3351. CheckPhysicsContacts();
  3352. }
  3353. Assert( !(m_NPCState == NPC_STATE_DEAD && m_lifeState == LIFE_ALIVE) );
  3354. //---------------------------------
  3355. SetNextThink( TICK_NEVER_THINK );
  3356. //---------------------------------
  3357. bool bInPVS = CheckPVSCondition();
  3358. //---------------------------------
  3359. UpdateSleepState( bInPVS );
  3360. //---------------------------------
  3361. bool bRanDecision = false;
  3362. if ( GetEfficiency() < AIE_DORMANT && GetSleepState() == AISS_AWAKE )
  3363. {
  3364. static CFastTimer timer;
  3365. float thinkLimit = ai_show_think_tolerance.GetFloat();
  3366. if ( thinkLimit > 0 )
  3367. timer.Start();
  3368. if ( g_pAINetworkManager && g_pAINetworkManager->IsInitialized() )
  3369. {
  3370. VPROF_BUDGET( "NPCs", VPROF_BUDGETGROUP_NPCS );
  3371. AI_PROFILE_SCOPE_BEGIN_( GetClassScheduleIdSpace()->GetClassName() ); // need to use a string stable from map load to map load
  3372. SetPlayerAvoidState();
  3373. if ( PreThink() )
  3374. {
  3375. if ( m_flNextDecisionTime <= gpGlobals->curtime )
  3376. {
  3377. bRanDecision = true;
  3378. m_ScheduleState.bTaskRanAutomovement = false;
  3379. m_ScheduleState.bTaskUpdatedYaw = false;
  3380. RunAI();
  3381. }
  3382. else
  3383. {
  3384. if ( m_ScheduleState.bTaskRanAutomovement )
  3385. AutoMovement();
  3386. if ( m_ScheduleState.bTaskUpdatedYaw )
  3387. GetMotor()->UpdateYaw();
  3388. }
  3389. PostRun();
  3390. PerformMovement();
  3391. m_bIsMoving = IsMoving();
  3392. PostMovement();
  3393. SetSimulationTime( gpGlobals->curtime );
  3394. }
  3395. else
  3396. m_flTimeLastMovement = FLT_MAX;
  3397. AI_PROFILE_SCOPE_END();
  3398. }
  3399. if ( thinkLimit > 0 )
  3400. {
  3401. timer.End();
  3402. float thinkTime = g_AIRunTimer.GetDuration().GetMillisecondsF();
  3403. if ( thinkTime > thinkLimit )
  3404. {
  3405. int color = (int)RemapVal( thinkTime, thinkLimit, thinkLimit * 3, 96.0, 255.0 );
  3406. if ( color > 255 )
  3407. color = 255;
  3408. else if ( color < 96 )
  3409. color = 96;
  3410. Vector right;
  3411. Vector vecPoint;
  3412. vecPoint = EyePosition() + Vector( 0, 0, 12 );
  3413. GetVectors( NULL, &right, NULL );
  3414. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), color, 0, 0, false , 1.0 );
  3415. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) + right * 16, color, 0, 0, false , 1.0 );
  3416. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) - right * 16, color, 0, 0, false , 1.0 );
  3417. }
  3418. }
  3419. }
  3420. m_bUsingStandardThinkTime = ( GetNextThinkTick() == TICK_NEVER_THINK );
  3421. UpdateEfficiency( bInPVS );
  3422. if ( m_bUsingStandardThinkTime )
  3423. {
  3424. static const char *ppszEfficiencies[] =
  3425. {
  3426. "AIE_NORMAL",
  3427. "AIE_EFFICIENT",
  3428. "AIE_VERY_EFFICIENT",
  3429. "AIE_SUPER_EFFICIENT",
  3430. "AIE_DORMANT",
  3431. };
  3432. static const char *ppszMoveEfficiencies[] =
  3433. {
  3434. "AIME_NORMAL",
  3435. "AIME_EFFICIENT",
  3436. };
  3437. if ( ai_debug_efficiency.GetBool() )
  3438. DevMsg( this, "Eff: %s, Move: %s\n", ppszEfficiencies[GetEfficiency()], ppszMoveEfficiencies[GetMoveEfficiency()] );
  3439. static float g_DecisionIntervals[] =
  3440. {
  3441. .1, // AIE_NORMAL
  3442. .2, // AIE_EFFICIENT
  3443. .4, // AIE_VERY_EFFICIENT
  3444. .6, // AIE_SUPER_EFFICIENT
  3445. };
  3446. if ( bRanDecision )
  3447. {
  3448. m_flNextDecisionTime = gpGlobals->curtime + g_DecisionIntervals[GetEfficiency()];
  3449. }
  3450. if ( GetMoveEfficiency() == AIME_NORMAL || GetEfficiency() == AIE_NORMAL )
  3451. {
  3452. SetNextThink( gpGlobals->curtime + .1 );
  3453. }
  3454. else
  3455. {
  3456. SetNextThink( gpGlobals->curtime + .2 );
  3457. }
  3458. }
  3459. else
  3460. {
  3461. m_flNextDecisionTime = 0;
  3462. }
  3463. }
  3464. //=========================================================
  3465. // CAI_BaseNPC - USE - will make a npc angry at whomever
  3466. // activated it.
  3467. //=========================================================
  3468. void CAI_BaseNPC::NPCUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  3469. {
  3470. return;
  3471. // Can't +USE NPCs running scripts
  3472. if ( GetState() == NPC_STATE_SCRIPT )
  3473. return;
  3474. if ( IsInAScript() )
  3475. return;
  3476. SetIdealState( NPC_STATE_ALERT );
  3477. }
  3478. //-----------------------------------------------------------------------------
  3479. // Purpose: Virtual function that allows us to have any npc ignore a set of
  3480. // shared conditions.
  3481. //
  3482. //-----------------------------------------------------------------------------
  3483. void CAI_BaseNPC::RemoveIgnoredConditions( void )
  3484. {
  3485. m_ConditionsPreIgnore = m_Conditions;
  3486. m_Conditions.And( m_InverseIgnoreConditions, &m_Conditions );
  3487. if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
  3488. m_hCine->RemoveIgnoredConditions();
  3489. }
  3490. //=========================================================
  3491. // RangeAttack1Conditions
  3492. //=========================================================
  3493. int CAI_BaseNPC::RangeAttack1Conditions ( float flDot, float flDist )
  3494. {
  3495. if ( flDist < 64)
  3496. {
  3497. return COND_TOO_CLOSE_TO_ATTACK;
  3498. }
  3499. else if (flDist > 784)
  3500. {
  3501. return COND_TOO_FAR_TO_ATTACK;
  3502. }
  3503. else if (flDot < 0.5)
  3504. {
  3505. return COND_NOT_FACING_ATTACK;
  3506. }
  3507. return COND_CAN_RANGE_ATTACK1;
  3508. }
  3509. //=========================================================
  3510. // RangeAttack2Conditions
  3511. //=========================================================
  3512. int CAI_BaseNPC::RangeAttack2Conditions ( float flDot, float flDist )
  3513. {
  3514. if ( flDist < 64)
  3515. {
  3516. return COND_TOO_CLOSE_TO_ATTACK;
  3517. }
  3518. else if (flDist > 512)
  3519. {
  3520. return COND_TOO_FAR_TO_ATTACK;
  3521. }
  3522. else if (flDot < 0.5)
  3523. {
  3524. return COND_NOT_FACING_ATTACK;
  3525. }
  3526. return COND_CAN_RANGE_ATTACK2;
  3527. }
  3528. //=========================================================
  3529. // MeleeAttack1Conditions
  3530. //=========================================================
  3531. int CAI_BaseNPC::MeleeAttack1Conditions ( float flDot, float flDist )
  3532. {
  3533. if ( flDist > 64)
  3534. {
  3535. return COND_TOO_FAR_TO_ATTACK;
  3536. }
  3537. else if (flDot < 0.7)
  3538. {
  3539. return 0;
  3540. }
  3541. else if (GetEnemy() == NULL)
  3542. {
  3543. return 0;
  3544. }
  3545. // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb)
  3546. if ( GetEnemy()->GetFlags() & FL_ONGROUND )
  3547. {
  3548. return COND_CAN_MELEE_ATTACK1;
  3549. }
  3550. return 0;
  3551. }
  3552. //=========================================================
  3553. // MeleeAttack2Conditions
  3554. //=========================================================
  3555. int CAI_BaseNPC::MeleeAttack2Conditions ( float flDot, float flDist )
  3556. {
  3557. if ( flDist > 64)
  3558. {
  3559. return COND_TOO_FAR_TO_ATTACK;
  3560. }
  3561. else if (flDot < 0.7)
  3562. {
  3563. return 0;
  3564. }
  3565. return COND_CAN_MELEE_ATTACK2;
  3566. }
  3567. // Get capability mask
  3568. int CAI_BaseNPC::CapabilitiesGet( void ) const
  3569. {
  3570. int capability = m_afCapability;
  3571. if ( GetActiveWeapon() )
  3572. {
  3573. capability |= GetActiveWeapon()->CapabilitiesGet();
  3574. }
  3575. return capability;
  3576. }
  3577. // Set capability mask
  3578. int CAI_BaseNPC::CapabilitiesAdd( int capability )
  3579. {
  3580. m_afCapability |= capability;
  3581. return m_afCapability;
  3582. }
  3583. // Set capability mask
  3584. int CAI_BaseNPC::CapabilitiesRemove( int capability )
  3585. {
  3586. m_afCapability &= ~capability;
  3587. return m_afCapability;
  3588. }
  3589. // Clear capability mask
  3590. void CAI_BaseNPC::CapabilitiesClear( void )
  3591. {
  3592. m_afCapability = 0;
  3593. }
  3594. //=========================================================
  3595. // ClearAttacks - clear out all attack conditions
  3596. //=========================================================
  3597. void CAI_BaseNPC::ClearAttackConditions( )
  3598. {
  3599. // Clear all attack conditions
  3600. ClearCondition( COND_CAN_RANGE_ATTACK1 );
  3601. ClearCondition( COND_CAN_RANGE_ATTACK2 );
  3602. ClearCondition( COND_CAN_MELEE_ATTACK1 );
  3603. ClearCondition( COND_CAN_MELEE_ATTACK2 );
  3604. ClearCondition( COND_WEAPON_HAS_LOS );
  3605. ClearCondition( COND_WEAPON_BLOCKED_BY_FRIEND );
  3606. ClearCondition( COND_WEAPON_PLAYER_IN_SPREAD ); // Player in shooting direction
  3607. ClearCondition( COND_WEAPON_PLAYER_NEAR_TARGET ); // Player near shooting position
  3608. ClearCondition( COND_WEAPON_SIGHT_OCCLUDED );
  3609. }
  3610. //=========================================================
  3611. // GatherAttackConditions - sets all of the bits for attacks that the
  3612. // npc is capable of carrying out on the passed entity.
  3613. //=========================================================
  3614. void CAI_BaseNPC::GatherAttackConditions( CBaseEntity *pTarget, float flDist )
  3615. {
  3616. AI_PROFILE_SCOPE(CAI_BaseNPC_GatherAttackConditions);
  3617. Vector vecLOS = ( pTarget->GetAbsOrigin() - GetAbsOrigin() );
  3618. vecLOS.z = 0;
  3619. VectorNormalize( vecLOS );
  3620. Vector vBodyDir = BodyDirection2D( );
  3621. float flDot = DotProduct( vecLOS, vBodyDir );
  3622. // we know the enemy is in front now. We'll find which attacks the npc is capable of by
  3623. // checking for corresponding Activities in the model file, then do the simple checks to validate
  3624. // those attack types.
  3625. int capability;
  3626. Vector targetPos;
  3627. bool bWeaponHasLOS;
  3628. int condition;
  3629. capability = CapabilitiesGet();
  3630. // Clear all attack conditions
  3631. AI_PROFILE_SCOPE_BEGIN( CAI_BaseNPC_GatherAttackConditions_PrimaryWeaponLOS );
  3632. // @TODO (toml 06-15-03): There are simple cases where
  3633. // the upper torso of the enemy is visible, and the NPC is at an angle below
  3634. // them, but the above test fails because BodyTarget returns the center of
  3635. // the target. This needs some better handling/closer evaluation
  3636. // Try the eyes first, as likely to succeed (because can see or else wouldn't be here) thus reducing
  3637. // the odds of the need for a second trace
  3638. ClearAttackConditions();
  3639. targetPos = pTarget->EyePosition();
  3640. bWeaponHasLOS = CurrentWeaponLOSCondition( targetPos, true );
  3641. AI_PROFILE_SCOPE_END();
  3642. if ( !bWeaponHasLOS )
  3643. {
  3644. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_SecondaryWeaponLOS );
  3645. ClearAttackConditions( );
  3646. targetPos = pTarget->BodyTarget( GetAbsOrigin() );
  3647. bWeaponHasLOS = CurrentWeaponLOSCondition( targetPos, true );
  3648. }
  3649. else
  3650. {
  3651. SetCondition( COND_WEAPON_HAS_LOS );
  3652. }
  3653. if ( IsAttackFrozen() )
  3654. {
  3655. // If they are frozen, they can't attack
  3656. SetCondition( COND_TOO_CLOSE_TO_ATTACK ); // Run, Forest, Run
  3657. return;
  3658. }
  3659. bool bWeaponIsReady = (GetActiveWeapon() && !IsWeaponStateChanging());
  3660. // FIXME: move this out of here
  3661. if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK1) && bWeaponIsReady )
  3662. {
  3663. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack1Condition );
  3664. condition = GetActiveWeapon()->WeaponRangeAttack1Condition(flDot, flDist);
  3665. if ( condition == COND_NOT_FACING_ATTACK && FInAimCone( targetPos ) )
  3666. DevMsg( "Warning: COND_NOT_FACING_ATTACK set but FInAimCone is true\n" );
  3667. if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS)
  3668. {
  3669. SetCondition(condition);
  3670. }
  3671. }
  3672. else if ( capability & bits_CAP_INNATE_RANGE_ATTACK1 )
  3673. {
  3674. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack1Condition );
  3675. condition = RangeAttack1Conditions( flDot, flDist );
  3676. if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS)
  3677. {
  3678. SetCondition(condition);
  3679. }
  3680. }
  3681. if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK2) && bWeaponIsReady && ( GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK2 ) )
  3682. {
  3683. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack2Condition );
  3684. condition = GetActiveWeapon()->WeaponRangeAttack2Condition(flDot, flDist);
  3685. if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS)
  3686. {
  3687. SetCondition(condition);
  3688. }
  3689. }
  3690. else if ( capability & bits_CAP_INNATE_RANGE_ATTACK2 )
  3691. {
  3692. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack2Condition );
  3693. condition = RangeAttack2Conditions( flDot, flDist );
  3694. if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS)
  3695. {
  3696. SetCondition(condition);
  3697. }
  3698. }
  3699. if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK1) && bWeaponIsReady)
  3700. {
  3701. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack1Condition );
  3702. SetCondition(GetActiveWeapon()->WeaponMeleeAttack1Condition(flDot, flDist));
  3703. }
  3704. else if ( capability & bits_CAP_INNATE_MELEE_ATTACK1 )
  3705. {
  3706. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack1Condition );
  3707. SetCondition(MeleeAttack1Conditions ( flDot, flDist ));
  3708. }
  3709. if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK2) && bWeaponIsReady)
  3710. {
  3711. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack2Condition );
  3712. SetCondition(GetActiveWeapon()->WeaponMeleeAttack2Condition(flDot, flDist));
  3713. }
  3714. else if ( capability & bits_CAP_INNATE_MELEE_ATTACK2 )
  3715. {
  3716. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack2Condition );
  3717. SetCondition(MeleeAttack2Conditions ( flDot, flDist ));
  3718. }
  3719. // -----------------------------------------------------------------
  3720. // If any attacks are possible clear attack specific bits
  3721. // -----------------------------------------------------------------
  3722. if (HasCondition(COND_CAN_RANGE_ATTACK2) ||
  3723. HasCondition(COND_CAN_RANGE_ATTACK1) ||
  3724. HasCondition(COND_CAN_MELEE_ATTACK2) ||
  3725. HasCondition(COND_CAN_MELEE_ATTACK1) )
  3726. {
  3727. ClearCondition(COND_TOO_CLOSE_TO_ATTACK);
  3728. ClearCondition(COND_TOO_FAR_TO_ATTACK);
  3729. ClearCondition(COND_WEAPON_BLOCKED_BY_FRIEND);
  3730. }
  3731. }
  3732. //=========================================================
  3733. // SetState
  3734. //=========================================================
  3735. void CAI_BaseNPC::SetState( NPC_STATE State )
  3736. {
  3737. NPC_STATE OldState;
  3738. OldState = m_NPCState;
  3739. if ( State != m_NPCState )
  3740. {
  3741. m_flLastStateChangeTime = gpGlobals->curtime;
  3742. }
  3743. switch( State )
  3744. {
  3745. // Drop enemy pointers when going to idle
  3746. case NPC_STATE_IDLE:
  3747. if ( GetEnemy() != NULL )
  3748. {
  3749. SetEnemy( NULL ); // not allowed to have an enemy anymore.
  3750. DevMsg( 2, "Stripped\n" );
  3751. }
  3752. break;
  3753. }
  3754. bool fNotifyChange = false;
  3755. if( m_NPCState != State )
  3756. {
  3757. // Don't notify if we're changing to a state we're already in!
  3758. fNotifyChange = true;
  3759. }
  3760. m_NPCState = State;
  3761. SetIdealState( State );
  3762. // Notify the character that its state has changed.
  3763. if( fNotifyChange )
  3764. {
  3765. OnStateChange( OldState, m_NPCState );
  3766. }
  3767. }
  3768. bool CAI_BaseNPC::WokeThisTick() const
  3769. {
  3770. return m_nWakeTick == gpGlobals->tickcount ? true : false;
  3771. }
  3772. //-----------------------------------------------------------------------------
  3773. //-----------------------------------------------------------------------------
  3774. void CAI_BaseNPC::Wake( bool bFireOutput )
  3775. {
  3776. if ( GetSleepState() != AISS_AWAKE )
  3777. {
  3778. m_nWakeTick = gpGlobals->tickcount;
  3779. SetSleepState( AISS_AWAKE );
  3780. RemoveEffects( EF_NODRAW );
  3781. if ( bFireOutput )
  3782. m_OnWake.FireOutput( this, this );
  3783. if ( m_bWakeSquad && GetSquad() )
  3784. {
  3785. AISquadIter_t iter;
  3786. for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) )
  3787. {
  3788. if ( pSquadMember->IsAlive() && pSquadMember != this )
  3789. {
  3790. pSquadMember->m_bWakeSquad = false;
  3791. pSquadMember->Wake();
  3792. }
  3793. }
  3794. }
  3795. }
  3796. }
  3797. //-----------------------------------------------------------------------------
  3798. //-----------------------------------------------------------------------------
  3799. void CAI_BaseNPC::Sleep()
  3800. {
  3801. // Don't render.
  3802. AddEffects( EF_NODRAW );
  3803. if( GetState() == NPC_STATE_SCRIPT )
  3804. {
  3805. Warning( "%s put to sleep while in Scripted state!\n", STRING( GetEntityName() ) );
  3806. }
  3807. VacateStrategySlot();
  3808. // Slam my schedule.
  3809. SetSchedule( SCHED_SLEEP );
  3810. m_OnSleep.FireOutput( this, this );
  3811. }
  3812. //-----------------------------------------------------------------------------
  3813. // Sets all sensing-related conditions
  3814. //-----------------------------------------------------------------------------
  3815. void CAI_BaseNPC::PerformSensing( void )
  3816. {
  3817. GetSenses()->PerformSensing();
  3818. }
  3819. //-----------------------------------------------------------------------------
  3820. void CAI_BaseNPC::ClearSenseConditions( void )
  3821. {
  3822. static int conditionsToClear[] =
  3823. {
  3824. COND_SEE_HATE,
  3825. COND_SEE_DISLIKE,
  3826. COND_SEE_ENEMY,
  3827. COND_SEE_FEAR,
  3828. COND_SEE_NEMESIS,
  3829. COND_SEE_PLAYER,
  3830. COND_HEAR_DANGER,
  3831. COND_HEAR_COMBAT,
  3832. COND_HEAR_WORLD,
  3833. COND_HEAR_PLAYER,
  3834. COND_HEAR_THUMPER,
  3835. COND_HEAR_BUGBAIT,
  3836. COND_HEAR_PHYSICS_DANGER,
  3837. COND_HEAR_MOVE_AWAY,
  3838. COND_SMELL,
  3839. };
  3840. ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
  3841. }
  3842. //-----------------------------------------------------------------------------
  3843. void CAI_BaseNPC::CheckOnGround( void )
  3844. {
  3845. bool bScriptedWait = ( IsCurSchedule( SCHED_WAIT_FOR_SCRIPT ) || ( m_hCine && m_scriptState == CAI_BaseNPC::SCRIPT_WAIT ) );
  3846. if ( !bScriptedWait && !HasCondition( COND_FLOATING_OFF_GROUND ) )
  3847. {
  3848. // parented objects are never floating
  3849. if (GetMoveParent() != NULL)
  3850. return;
  3851. // NPCs in scripts with the fly flag shouldn't fall.
  3852. // FIXME: should NPCS with FL_FLY ever fall? Doesn't seem like they should.
  3853. if ( ( GetState() == NPC_STATE_SCRIPT ) && ( GetFlags() & FL_FLY ) )
  3854. return;
  3855. if ( ( GetNavType() == NAV_GROUND ) && ( GetMoveType() != MOVETYPE_VPHYSICS ) && ( GetMoveType() != MOVETYPE_NONE ) )
  3856. {
  3857. if ( m_CheckOnGroundTimer.Expired() )
  3858. {
  3859. m_CheckOnGroundTimer.Set(0.5);
  3860. // check a shrunk box centered around the foot
  3861. Vector maxs = WorldAlignMaxs();
  3862. Vector mins = WorldAlignMins();
  3863. if ( mins != maxs ) // some NPCs have no hull, so mins == maxs == vec3_origin
  3864. {
  3865. maxs -= Vector( 0.0f, 0.0f, 0.2f );
  3866. Vector vecStart = GetAbsOrigin() + Vector( 0, 0, .1f );
  3867. Vector vecDown = GetAbsOrigin();
  3868. vecDown.z -= 4.0;
  3869. trace_t trace;
  3870. m_pMoveProbe->TraceHull( vecStart, vecDown, mins, maxs, GetAITraceMask(), &trace );
  3871. if (trace.fraction == 1.0)
  3872. {
  3873. SetCondition( COND_FLOATING_OFF_GROUND );
  3874. SetGroundEntity( NULL );
  3875. }
  3876. else
  3877. {
  3878. if ( trace.startsolid && trace.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS &&
  3879. trace.m_pEnt->VPhysicsGetObject() && trace.m_pEnt->VPhysicsGetObject()->GetMass() < VPHYSICS_LARGE_OBJECT_MASS )
  3880. {
  3881. // stuck inside a small physics object?
  3882. m_CheckOnGroundTimer.Set(0.1f);
  3883. NPCPhysics_CreateSolver( this, trace.m_pEnt, true, 0.25f );
  3884. if ( VPhysicsGetObject() )
  3885. {
  3886. VPhysicsGetObject()->RecheckContactPoints();
  3887. }
  3888. }
  3889. // Check to see if someone changed the ground on us...
  3890. if ( trace.m_pEnt && trace.m_pEnt != GetGroundEntity() )
  3891. {
  3892. SetGroundEntity( trace.m_pEnt );
  3893. }
  3894. }
  3895. }
  3896. }
  3897. }
  3898. }
  3899. else
  3900. {
  3901. // parented objects are never floating
  3902. if ( bScriptedWait || GetMoveParent() != NULL || (GetFlags() & FL_ONGROUND ) || GetNavType() != NAV_GROUND )
  3903. {
  3904. ClearCondition( COND_FLOATING_OFF_GROUND );
  3905. }
  3906. }
  3907. }
  3908. void CAI_BaseNPC::NotifyPushMove()
  3909. {
  3910. // don't recheck ground when I'm being push-moved
  3911. m_CheckOnGroundTimer.Set( 0.5f );
  3912. }
  3913. //-----------------------------------------------------------------------------
  3914. // Purpose:
  3915. //-----------------------------------------------------------------------------
  3916. bool CAI_BaseNPC::CanFlinch( void )
  3917. {
  3918. if ( IsCurSchedule( SCHED_BIG_FLINCH ) )
  3919. return false;
  3920. if ( m_flNextFlinchTime >= gpGlobals->curtime )
  3921. return false;
  3922. return true;
  3923. }
  3924. //-----------------------------------------------------------------------------
  3925. // Purpose:
  3926. //-----------------------------------------------------------------------------
  3927. void CAI_BaseNPC::CheckFlinches( void )
  3928. {
  3929. // If we're currently flinching, don't allow gesture flinches to be overlaid
  3930. if ( IsCurSchedule( SCHED_BIG_FLINCH ) )
  3931. {
  3932. ClearCondition( COND_LIGHT_DAMAGE );
  3933. ClearCondition( COND_HEAVY_DAMAGE );
  3934. }
  3935. // If we've taken heavy damage, try to do a full schedule flinch
  3936. if ( HasCondition(COND_HEAVY_DAMAGE) )
  3937. {
  3938. // If we've already flinched recently, gesture flinch instead.
  3939. if ( HasMemory(bits_MEMORY_FLINCHED) )
  3940. {
  3941. // Clear the heavy damage condition so we don't interrupt schedules
  3942. // when we play a gesture flinch because we recently did a full flinch.
  3943. // Prevents the player from stun-locking enemies, even though they don't full flinch.
  3944. ClearCondition( COND_HEAVY_DAMAGE );
  3945. }
  3946. else if ( !HasInterruptCondition(COND_HEAVY_DAMAGE) )
  3947. {
  3948. // If we have taken heavy damage, but the current schedule doesn't
  3949. // break on that, resort to just playing a gesture flinch.
  3950. PlayFlinchGesture();
  3951. }
  3952. // Otherwise, do nothing. The heavy damage will interrupt our schedule and we'll flinch.
  3953. }
  3954. else if ( HasCondition( COND_LIGHT_DAMAGE ) )
  3955. {
  3956. // If we have taken light damage play gesture flinches
  3957. PlayFlinchGesture();
  3958. }
  3959. // If it's been a while since we did a full flinch, forget that we flinched so we'll flinch fully again
  3960. if ( HasMemory(bits_MEMORY_FLINCHED) && gpGlobals->curtime > m_flNextFlinchTime )
  3961. {
  3962. Forget(bits_MEMORY_FLINCHED);
  3963. }
  3964. }
  3965. //-----------------------------------------------------------------------------
  3966. void CAI_BaseNPC::GatherConditions( void )
  3967. {
  3968. m_bConditionsGathered = true;
  3969. g_AIConditionsTimer.Start();
  3970. if( gpGlobals->curtime > m_flTimePingEffect && m_flTimePingEffect > 0.0f )
  3971. {
  3972. // Turn off the pinging.
  3973. DispatchUpdateTransmitState();
  3974. m_flTimePingEffect = 0.0f;
  3975. }
  3976. if ( m_NPCState != NPC_STATE_NONE && m_NPCState != NPC_STATE_DEAD )
  3977. {
  3978. if ( FacingIdeal() )
  3979. Forget( bits_MEMORY_TURNING );
  3980. bool bForcedGather = m_bForceConditionsGather;
  3981. m_bForceConditionsGather = false;
  3982. if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink )
  3983. {
  3984. if ( UTIL_FindClientInPVS( edict() ) != NULL )
  3985. SetCondition( COND_IN_PVS );
  3986. else
  3987. ClearCondition( COND_IN_PVS );
  3988. }
  3989. // Sample the environment. Do this unconditionally if there is a player in this
  3990. // npc's PVS. NPCs in COMBAT state are allowed to simulate when there is no player in
  3991. // the same PVS. This is so that any fights in progress will continue even if the player leaves the PVS.
  3992. if ( !IsFlaggedEfficient() &&
  3993. ( bForcedGather ||
  3994. HasCondition( COND_IN_PVS ) ||
  3995. ShouldAlwaysThink() ||
  3996. m_NPCState == NPC_STATE_COMBAT ) )
  3997. {
  3998. CheckOnGround();
  3999. if ( ShouldPlayIdleSound() )
  4000. {
  4001. AI_PROFILE_SCOPE(CAI_BaseNPC_IdleSound);
  4002. IdleSound();
  4003. }
  4004. PerformSensing();
  4005. GetEnemies()->RefreshMemories();
  4006. ChooseEnemy();
  4007. // Check to see if there is a better weapon available
  4008. if (Weapon_IsBetterAvailable())
  4009. {
  4010. SetCondition(COND_BETTER_WEAPON_AVAILABLE);
  4011. }
  4012. if ( GetCurSchedule() &&
  4013. ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT) &&
  4014. GetEnemy() &&
  4015. !HasCondition( COND_NEW_ENEMY ) &&
  4016. GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY ) )
  4017. {
  4018. // @Note (toml 05-05-04): There seems to be a case where an NPC can not respond
  4019. // to COND_NEW_ENEMY. Only evidence right now is save
  4020. // games after the fact, so for now, just patching it up
  4021. DevMsg( 2, "Had to force COND_NEW_ENEMY\n" );
  4022. SetCondition(COND_NEW_ENEMY);
  4023. }
  4024. }
  4025. else
  4026. {
  4027. // if not done, can have problems if leave PVS in same frame heard/saw things,
  4028. // since only PerformSensing clears conditions
  4029. ClearSenseConditions();
  4030. }
  4031. // do these calculations if npc has an enemy.
  4032. if ( GetEnemy() != NULL )
  4033. {
  4034. if ( !IsFlaggedEfficient() )
  4035. {
  4036. GatherEnemyConditions( GetEnemy() );
  4037. m_flLastEnemyTime = gpGlobals->curtime;
  4038. }
  4039. else
  4040. {
  4041. DevMsg(2, "Lost enemy because we're flagged efficient\n");
  4042. SetEnemy( NULL );
  4043. }
  4044. }
  4045. // do these calculations if npc has a target
  4046. if ( GetTarget() != NULL )
  4047. {
  4048. CheckTarget( GetTarget() );
  4049. }
  4050. CheckAmmo();
  4051. CheckFlinches();
  4052. CheckSquad();
  4053. }
  4054. else
  4055. ClearCondition( COND_IN_PVS );
  4056. g_AIConditionsTimer.End();
  4057. }
  4058. //-----------------------------------------------------------------------------
  4059. // Purpose:
  4060. //-----------------------------------------------------------------------------
  4061. void CAI_BaseNPC::PrescheduleThink( void )
  4062. {
  4063. #ifdef HL2_EPISODIC
  4064. CheckForScriptedNPCInteractions();
  4065. #endif
  4066. // If we use weapons, and our desired weapon state is not the current, fix it
  4067. if( (CapabilitiesGet() & bits_CAP_USE_WEAPONS) && (m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) )
  4068. {
  4069. if ( IsAlive() && !IsInAScript() )
  4070. {
  4071. if ( !IsCurSchedule( SCHED_MELEE_ATTACK1, false ) && !IsCurSchedule( SCHED_MELEE_ATTACK2, false ) &&
  4072. !IsCurSchedule( SCHED_RANGE_ATTACK1, false ) && !IsCurSchedule( SCHED_RANGE_ATTACK2, false ) )
  4073. {
  4074. if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED )
  4075. {
  4076. HolsterWeapon();
  4077. }
  4078. else if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED )
  4079. {
  4080. UnholsterWeapon();
  4081. }
  4082. }
  4083. }
  4084. else
  4085. {
  4086. // Throw away the request
  4087. m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
  4088. }
  4089. }
  4090. }
  4091. //-----------------------------------------------------------------------------
  4092. // Main entry point for processing AI
  4093. //-----------------------------------------------------------------------------
  4094. void CAI_BaseNPC::RunAI( void )
  4095. {
  4096. AI_PROFILE_SCOPE(CAI_BaseNPC_RunAI);
  4097. g_AIRunTimer.Start();
  4098. if( ai_debug_squads.GetBool() )
  4099. {
  4100. if( IsInSquad() && GetSquad() && !CAI_Squad::IsSilentMember(this ) && ( GetSquad()->IsLeader( this ) || GetSquad()->NumMembers() == 1 ) )
  4101. {
  4102. AISquadIter_t iter;
  4103. CAI_Squad *pSquad = GetSquad();
  4104. Vector right;
  4105. Vector vecPoint;
  4106. vecPoint = EyePosition() + Vector( 0, 0, 12 );
  4107. GetVectors( NULL, &right, NULL );
  4108. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 0, 255, 0, false , 0.1 );
  4109. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 0, 255, 0, false , 0.1 );
  4110. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 0, 255, 0, false , 0.1 );
  4111. NDebugOverlay::Cross3D( GetSquad()->ComputeSquadCentroid(false,NULL), 16, 255, 255, 255, false, 0.1 );
  4112. for ( CAI_BaseNPC *pSquadMember = pSquad->GetFirstMember( &iter, false ); pSquadMember; pSquadMember = pSquad->GetNextMember( &iter, false ) )
  4113. {
  4114. if ( pSquadMember != this )
  4115. NDebugOverlay::Line( EyePosition(), pSquadMember->EyePosition(), 0,
  4116. CAI_Squad::IsSilentMember(pSquadMember) ? 127 : 255, 0, false , 0.1 );
  4117. }
  4118. }
  4119. }
  4120. if( ai_debug_loners.GetBool() && !IsInSquad() && AI_IsSinglePlayer() )
  4121. {
  4122. Vector right;
  4123. Vector vecPoint;
  4124. vecPoint = EyePosition() + Vector( 0, 0, 12 );
  4125. UTIL_GetLocalPlayer()->GetVectors( NULL, &right, NULL );
  4126. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 255, 0, 0, false , 0.1 );
  4127. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 255, 0, 0, false , 0.1 );
  4128. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 255, 0, 0, false , 0.1 );
  4129. }
  4130. #ifdef _DEBUG
  4131. m_bSelected = ( (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) != 0 );
  4132. #endif
  4133. m_bConditionsGathered = false;
  4134. m_bSkippedChooseEnemy = false;
  4135. if ( g_pDeveloper->GetInt() && !GetNavigator()->IsOnNetwork() )
  4136. {
  4137. AddTimedOverlay( "NPC w/no reachable nodes!", 5 );
  4138. }
  4139. AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_RunAI_GatherConditions);
  4140. GatherConditions();
  4141. RemoveIgnoredConditions();
  4142. AI_PROFILE_SCOPE_END();
  4143. if ( !m_bConditionsGathered )
  4144. m_bConditionsGathered = true; // derived class didn't call to base
  4145. TryRestoreHull();
  4146. g_AIPrescheduleThinkTimer.Start();
  4147. AI_PROFILE_SCOPE_BEGIN(CAI_RunAI_PrescheduleThink);
  4148. PrescheduleThink();
  4149. AI_PROFILE_SCOPE_END();
  4150. g_AIPrescheduleThinkTimer.End();
  4151. MaintainSchedule();
  4152. PostscheduleThink();
  4153. ClearTransientConditions();
  4154. g_AIRunTimer.End();
  4155. }
  4156. //-----------------------------------------------------------------------------
  4157. void CAI_BaseNPC::ClearTransientConditions()
  4158. {
  4159. // if the npc didn't use these conditions during the above call to MaintainSchedule()
  4160. // we throw them out cause we don't want them sitting around through the lifespan of a schedule
  4161. // that doesn't use them.
  4162. ClearCondition( COND_LIGHT_DAMAGE );
  4163. ClearCondition( COND_HEAVY_DAMAGE );
  4164. ClearCondition( COND_PHYSICS_DAMAGE );
  4165. ClearCondition( COND_PLAYER_PUSHING );
  4166. }
  4167. //-----------------------------------------------------------------------------
  4168. // Selecting the idle ideal state
  4169. //-----------------------------------------------------------------------------
  4170. NPC_STATE CAI_BaseNPC::SelectIdleIdealState()
  4171. {
  4172. // IDLE goes to ALERT upon hearing a sound
  4173. // IDLE goes to ALERT upon being injured
  4174. // IDLE goes to ALERT upon seeing food
  4175. // IDLE goes to COMBAT upon sighting an enemy
  4176. if ( HasCondition(COND_NEW_ENEMY) ||
  4177. HasCondition(COND_SEE_ENEMY) )
  4178. {
  4179. // new enemy! This means an idle npc has seen someone it dislikes, or
  4180. // that a npc in combat has found a more suitable target to attack
  4181. return NPC_STATE_COMBAT;
  4182. }
  4183. // Set our ideal yaw if we've taken damage
  4184. if ( HasCondition(COND_LIGHT_DAMAGE) ||
  4185. HasCondition(COND_HEAVY_DAMAGE) ||
  4186. (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) )
  4187. {
  4188. Vector vecEnemyLKP;
  4189. // Fill in where we're trying to look
  4190. if ( GetEnemy() )
  4191. {
  4192. vecEnemyLKP = GetEnemyLKP();
  4193. }
  4194. else
  4195. {
  4196. if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) )
  4197. {
  4198. vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY );
  4199. }
  4200. else
  4201. {
  4202. // Don't have an enemy, so face the direction the last attack came from (don't face north)
  4203. vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 );
  4204. }
  4205. }
  4206. // Set the ideal
  4207. GetMotor()->SetIdealYawToTarget( vecEnemyLKP );
  4208. return NPC_STATE_ALERT;
  4209. }
  4210. if ( HasCondition(COND_HEAR_DANGER) ||
  4211. HasCondition(COND_HEAR_COMBAT) ||
  4212. HasCondition(COND_HEAR_WORLD) ||
  4213. HasCondition(COND_HEAR_PLAYER) ||
  4214. HasCondition(COND_HEAR_THUMPER) ||
  4215. HasCondition(COND_HEAR_BULLET_IMPACT) )
  4216. {
  4217. // Interrupted by a sound. So make our ideal yaw the
  4218. // source of the sound!
  4219. CSound *pSound;
  4220. pSound = GetBestSound();
  4221. Assert( pSound != NULL );
  4222. if ( pSound )
  4223. {
  4224. // BRJ 1/7/04: This code used to set the ideal yaw.
  4225. // It's really side-effecty to set the yaw here.
  4226. // That is now done by the FACE_BESTSOUND schedule.
  4227. // Revert this change if it causes problems.
  4228. GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
  4229. if ( pSound->IsSoundType( SOUND_COMBAT | SOUND_DANGER | SOUND_BULLET_IMPACT ) )
  4230. {
  4231. return NPC_STATE_ALERT;
  4232. }
  4233. }
  4234. }
  4235. if ( HasInterruptCondition(COND_SMELL) )
  4236. {
  4237. return NPC_STATE_ALERT;
  4238. }
  4239. return NPC_STATE_INVALID;
  4240. }
  4241. //-----------------------------------------------------------------------------
  4242. // Selecting the alert ideal state
  4243. //-----------------------------------------------------------------------------
  4244. NPC_STATE CAI_BaseNPC::SelectAlertIdealState()
  4245. {
  4246. // ALERT goes to IDLE upon becoming bored
  4247. // ALERT goes to COMBAT upon sighting an enemy
  4248. if ( HasCondition(COND_NEW_ENEMY) ||
  4249. HasCondition(COND_SEE_ENEMY) ||
  4250. GetEnemy() != NULL )
  4251. {
  4252. return NPC_STATE_COMBAT;
  4253. }
  4254. // Set our ideal yaw if we've taken damage
  4255. if ( HasCondition(COND_LIGHT_DAMAGE) ||
  4256. HasCondition(COND_HEAVY_DAMAGE) ||
  4257. (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) )
  4258. {
  4259. Vector vecEnemyLKP;
  4260. // Fill in where we're trying to look
  4261. if ( GetEnemy() )
  4262. {
  4263. vecEnemyLKP = GetEnemyLKP();
  4264. }
  4265. else
  4266. {
  4267. if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) )
  4268. {
  4269. vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY );
  4270. }
  4271. else
  4272. {
  4273. // Don't have an enemy, so face the direction the last attack came from (don't face north)
  4274. vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 );
  4275. }
  4276. }
  4277. // Set the ideal
  4278. GetMotor()->SetIdealYawToTarget( vecEnemyLKP );
  4279. return NPC_STATE_ALERT;
  4280. }
  4281. if ( HasCondition(COND_HEAR_DANGER) ||
  4282. HasCondition(COND_HEAR_COMBAT) )
  4283. {
  4284. CSound *pSound = GetBestSound();
  4285. AssertOnce( pSound != NULL );
  4286. if ( pSound )
  4287. {
  4288. GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
  4289. }
  4290. return NPC_STATE_ALERT;
  4291. }
  4292. if ( ShouldGoToIdleState() )
  4293. {
  4294. return NPC_STATE_IDLE;
  4295. }
  4296. return NPC_STATE_INVALID;
  4297. }
  4298. //-----------------------------------------------------------------------------
  4299. // Selecting the alert ideal state
  4300. //-----------------------------------------------------------------------------
  4301. NPC_STATE CAI_BaseNPC::SelectScriptIdealState()
  4302. {
  4303. if ( HasCondition(COND_TASK_FAILED) ||
  4304. HasCondition(COND_LIGHT_DAMAGE) ||
  4305. HasCondition(COND_HEAVY_DAMAGE) )
  4306. {
  4307. ExitScriptedSequence(); // This will set the ideal state
  4308. }
  4309. if ( m_IdealNPCState == NPC_STATE_IDLE )
  4310. {
  4311. // Exiting a script. Select the ideal state assuming we were idle now.
  4312. m_NPCState = NPC_STATE_IDLE;
  4313. NPC_STATE eIdealState = SelectIdealState();
  4314. m_NPCState = NPC_STATE_SCRIPT;
  4315. return eIdealState;
  4316. }
  4317. return NPC_STATE_INVALID;
  4318. }
  4319. //-----------------------------------------------------------------------------
  4320. // Purpose: Surveys the Conditions information available and finds the best
  4321. // new state for a npc.
  4322. //
  4323. // NOTICE the CAI_BaseNPC implementation of this function does not care about
  4324. // private conditions!
  4325. //
  4326. // Output : NPC_STATE - the suggested ideal state based on current conditions.
  4327. //-----------------------------------------------------------------------------
  4328. NPC_STATE CAI_BaseNPC::SelectIdealState( void )
  4329. {
  4330. // dvs: FIXME: lots of side effecty code in here!! this function should ONLY return an ideal state!
  4331. // ---------------------------
  4332. // Do some squad stuff first
  4333. // ---------------------------
  4334. if (m_pSquad)
  4335. {
  4336. switch( m_NPCState )
  4337. {
  4338. case NPC_STATE_IDLE:
  4339. case NPC_STATE_ALERT:
  4340. if ( HasCondition ( COND_NEW_ENEMY ) )
  4341. {
  4342. m_pSquad->SquadNewEnemy( GetEnemy() );
  4343. }
  4344. break;
  4345. }
  4346. }
  4347. // ---------------------------
  4348. // Set ideal state
  4349. // ---------------------------
  4350. switch ( m_NPCState )
  4351. {
  4352. case NPC_STATE_IDLE:
  4353. {
  4354. NPC_STATE nState = SelectIdleIdealState();
  4355. if ( nState != NPC_STATE_INVALID )
  4356. return nState;
  4357. }
  4358. break;
  4359. case NPC_STATE_ALERT:
  4360. {
  4361. NPC_STATE nState = SelectAlertIdealState();
  4362. if ( nState != NPC_STATE_INVALID )
  4363. return nState;
  4364. }
  4365. break;
  4366. case NPC_STATE_COMBAT:
  4367. // COMBAT goes to ALERT upon death of enemy
  4368. {
  4369. if ( GetEnemy() == NULL )
  4370. {
  4371. DevWarning( 2, "***Combat state with no enemy!\n" );
  4372. return NPC_STATE_ALERT;
  4373. }
  4374. break;
  4375. }
  4376. case NPC_STATE_SCRIPT:
  4377. {
  4378. NPC_STATE nState = SelectScriptIdealState();
  4379. if ( nState != NPC_STATE_INVALID )
  4380. return nState;
  4381. }
  4382. break;
  4383. case NPC_STATE_DEAD:
  4384. return NPC_STATE_DEAD;
  4385. }
  4386. // The best ideal state is the current ideal state.
  4387. return m_IdealNPCState;
  4388. }
  4389. //------------------------------------------------------------------------------
  4390. //------------------------------------------------------------------------------
  4391. void CAI_BaseNPC::GiveWeapon( string_t iszWeaponName )
  4392. {
  4393. CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(iszWeaponName) );
  4394. if ( !pWeapon )
  4395. {
  4396. Warning( "Couldn't create weapon %s to give NPC %s.\n", STRING(iszWeaponName), STRING(GetEntityName()) );
  4397. return;
  4398. }
  4399. // If I have a weapon already, drop it
  4400. if ( GetActiveWeapon() )
  4401. {
  4402. Weapon_Drop( GetActiveWeapon() );
  4403. }
  4404. pWeapon->MakeWeaponNameFromEntity( this );
  4405. Weapon_Equip( pWeapon );
  4406. // Handle this case
  4407. OnGivenWeapon( pWeapon );
  4408. }
  4409. //-----------------------------------------------------------------------------
  4410. // Rather specific function that tells us if an NPC is in the process of
  4411. // moving to a weapon with the intent to pick it up.
  4412. //-----------------------------------------------------------------------------
  4413. bool CAI_BaseNPC::IsMovingToPickupWeapon()
  4414. {
  4415. return IsCurSchedule( SCHED_NEW_WEAPON );
  4416. }
  4417. //-----------------------------------------------------------------------------
  4418. //-----------------------------------------------------------------------------
  4419. bool CAI_BaseNPC::ShouldLookForBetterWeapon()
  4420. {
  4421. if( m_flNextWeaponSearchTime > gpGlobals->curtime )
  4422. return false;
  4423. if( !(CapabilitiesGet() & bits_CAP_USE_WEAPONS) )
  4424. return false;
  4425. // Already armed and currently fighting. Don't try to upgrade.
  4426. if( GetActiveWeapon() && m_NPCState == NPC_STATE_COMBAT )
  4427. return false;
  4428. if( IsMovingToPickupWeapon() )
  4429. return false;
  4430. if( !IsPlayerAlly() && GetActiveWeapon() )
  4431. return false;
  4432. if( IsInAScript() )
  4433. return false;
  4434. return true;
  4435. }
  4436. //-----------------------------------------------------------------------------
  4437. // Purpose: Check if a better weapon is available.
  4438. // For now check if there is a weapon and I don't have one. In
  4439. // the future
  4440. // UNDONE: actually rate the weapons based on there strength
  4441. // Input :
  4442. // Output :
  4443. //-----------------------------------------------------------------------------
  4444. bool CAI_BaseNPC::Weapon_IsBetterAvailable()
  4445. {
  4446. if( m_iszPendingWeapon != NULL_STRING )
  4447. {
  4448. // Some weapon is reserved for us.
  4449. return true;
  4450. }
  4451. if( ShouldLookForBetterWeapon() )
  4452. {
  4453. if( GetActiveWeapon() )
  4454. {
  4455. m_flNextWeaponSearchTime = gpGlobals->curtime + 2;
  4456. }
  4457. else
  4458. {
  4459. if( IsInPlayerSquad() )
  4460. {
  4461. // Look for a weapon frequently.
  4462. m_flNextWeaponSearchTime = gpGlobals->curtime + 1;
  4463. }
  4464. else
  4465. {
  4466. m_flNextWeaponSearchTime = gpGlobals->curtime + 2;
  4467. }
  4468. }
  4469. if ( Weapon_FindUsable( WEAPON_SEARCH_DELTA ) )
  4470. {
  4471. return true;
  4472. }
  4473. }
  4474. return false;
  4475. }
  4476. //-----------------------------------------------------------------------------
  4477. // Purpose: Returns true is weapon has a line of sight. If bSetConditions is
  4478. // true, also sets LOC conditions
  4479. // Input :
  4480. // Output :
  4481. //-----------------------------------------------------------------------------
  4482. bool CAI_BaseNPC::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
  4483. {
  4484. #if 0
  4485. // @TODO (toml 03-07-04): this code might be better (not tested)
  4486. Vector vecLocalRelativePosition;
  4487. VectorITransform( npcOwner->Weapon_ShootPosition(), npcOwner->EntityToWorldTransform(), vecLocalRelativePosition );
  4488. // Compute desired test transform
  4489. // Compute desired x axis
  4490. Vector xaxis;
  4491. VectorSubtract( targetPos, ownerPos, xaxis );
  4492. // FIXME: Insert angle test here?
  4493. float flAngle = acos( xaxis.z / xaxis.Length() );
  4494. xaxis.z = 0.0f;
  4495. float flLength = VectorNormalize( xaxis );
  4496. if ( flLength < 1e-3 )
  4497. return false;
  4498. Vector yaxis( -xaxis.y, xaxis.x, 0.0f );
  4499. matrix3x4_t losTestToWorld;
  4500. MatrixInitialize( losTestToWorld, ownerPos, xaxis, yaxis, zaxis );
  4501. Vector barrelPos;
  4502. VectorTransform( vecLocalRelativePosition, losTestToWorld, barrelPos );
  4503. #endif
  4504. bool bHaveLOS;
  4505. if (GetActiveWeapon())
  4506. {
  4507. bHaveLOS = GetActiveWeapon()->WeaponLOSCondition(ownerPos, targetPos, bSetConditions);
  4508. }
  4509. else if (CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1)
  4510. {
  4511. bHaveLOS = InnateWeaponLOSCondition(ownerPos, targetPos, bSetConditions);
  4512. }
  4513. else
  4514. {
  4515. if (bSetConditions)
  4516. {
  4517. SetCondition( COND_NO_WEAPON );
  4518. }
  4519. bHaveLOS = false;
  4520. }
  4521. // -------------------------------------------
  4522. // Check for friendly fire with the player
  4523. // -------------------------------------------
  4524. if ( CapabilitiesGet() & ( bits_CAP_NO_HIT_PLAYER | bits_CAP_NO_HIT_SQUADMATES ) )
  4525. {
  4526. float spread = 0.92;
  4527. if ( GetActiveWeapon() )
  4528. {
  4529. Vector vSpread = GetAttackSpread( GetActiveWeapon() );
  4530. if ( vSpread.x > VECTOR_CONE_15DEGREES.x )
  4531. spread = TableCos(asin(vSpread.x));
  4532. else // too much error because using point not box
  4533. spread = 0.99145; // "15 degrees"
  4534. }
  4535. if (CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER)
  4536. {
  4537. // Check shoot direction relative to player
  4538. if (PlayerInSpread( ownerPos, targetPos, spread, 8*12 ))
  4539. {
  4540. if (bSetConditions)
  4541. {
  4542. SetCondition( COND_WEAPON_PLAYER_IN_SPREAD );
  4543. }
  4544. bHaveLOS = false;
  4545. }
  4546. /* For grenades etc. check that player is clear?
  4547. // Check player position also
  4548. if (PlayerInRange( targetPos, 100 ))
  4549. {
  4550. SetCondition( COND_WEAPON_PLAYER_NEAR_TARGET );
  4551. }
  4552. */
  4553. }
  4554. if ( bHaveLOS )
  4555. {
  4556. if ( (CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && m_pSquad && GetEnemy() )
  4557. {
  4558. if ( IsSquadmateInSpread( ownerPos, targetPos, spread, 8*12 ) )
  4559. {
  4560. SetCondition( COND_WEAPON_BLOCKED_BY_FRIEND );
  4561. bHaveLOS = false;
  4562. }
  4563. }
  4564. }
  4565. }
  4566. return bHaveLOS;
  4567. }
  4568. //-----------------------------------------------------------------------------
  4569. // Purpose: Check the innate weapon LOS for an owner at an arbitrary position
  4570. // If bSetConditions is true, LOS related conditions will also be set
  4571. // Input :
  4572. // Output :
  4573. //-----------------------------------------------------------------------------
  4574. bool CAI_BaseNPC::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
  4575. {
  4576. // --------------------
  4577. // Check for occlusion
  4578. // --------------------
  4579. // Base class version assumes innate weapon position is at eye level
  4580. Vector barrelPos = ownerPos + GetViewOffset();
  4581. trace_t tr;
  4582. AI_TraceLine( barrelPos, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  4583. if ( tr.fraction == 1.0 )
  4584. {
  4585. return true;
  4586. }
  4587. CBaseEntity *pHitEntity = tr.m_pEnt;
  4588. // Translate a hit vehicle into its passenger if found
  4589. if ( GetEnemy() != NULL )
  4590. {
  4591. CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
  4592. if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
  4593. {
  4594. // Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is
  4595. // Also, check to see if the owner of the entity is the vehicle, in which case it's valid too.
  4596. // This catches vehicles that use bone followers.
  4597. CBaseEntity *pVehicleEnt = pCCEnemy->GetVehicleEntity();
  4598. if ( pHitEntity == pVehicleEnt || pHitEntity->GetOwnerEntity() == pVehicleEnt )
  4599. return true;
  4600. }
  4601. }
  4602. if ( pHitEntity == GetEnemy() )
  4603. {
  4604. return true;
  4605. }
  4606. else if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() )
  4607. {
  4608. if (IRelationType( pHitEntity ) == D_HT)
  4609. {
  4610. return true;
  4611. }
  4612. else if (bSetConditions)
  4613. {
  4614. SetCondition(COND_WEAPON_BLOCKED_BY_FRIEND);
  4615. }
  4616. }
  4617. else if (bSetConditions)
  4618. {
  4619. SetCondition(COND_WEAPON_SIGHT_OCCLUDED);
  4620. SetEnemyOccluder(tr.m_pEnt);
  4621. }
  4622. return false;
  4623. }
  4624. //=========================================================
  4625. // CanCheckAttacks - prequalifies a npc to do more fine
  4626. // checking of potential attacks.
  4627. //=========================================================
  4628. bool CAI_BaseNPC::FCanCheckAttacks( void )
  4629. {
  4630. // Not allowed to check attacks while climbing or jumping
  4631. // Otherwise schedule is interrupted while on ladder/etc
  4632. // which is NOT a legal place to attack from
  4633. if ( GetNavType() == NAV_CLIMB || GetNavType() == NAV_JUMP )
  4634. return false;
  4635. if ( HasCondition(COND_SEE_ENEMY) && !HasCondition( COND_ENEMY_TOO_FAR))
  4636. {
  4637. return true;
  4638. }
  4639. return false;
  4640. }
  4641. //-----------------------------------------------------------------------------
  4642. // Purpose: Return dist. to enemy (closest of origin/head/feet)
  4643. // Input :
  4644. // Output :
  4645. //-----------------------------------------------------------------------------
  4646. float CAI_BaseNPC::EnemyDistance( CBaseEntity *pEnemy )
  4647. {
  4648. Vector enemyDelta = pEnemy->WorldSpaceCenter() - WorldSpaceCenter();
  4649. // NOTE: We ignore rotation for computing height. Assume it isn't an effect
  4650. // we care about, so we simply use OBBSize().z for height.
  4651. // Otherwise you'd do this:
  4652. // pEnemy->CollisionProp()->WorldSpaceSurroundingBounds( &enemyMins, &enemyMaxs );
  4653. // float enemyHeight = enemyMaxs.z - enemyMins.z;
  4654. float enemyHeight = pEnemy->CollisionProp()->OBBSize().z;
  4655. float myHeight = CollisionProp()->OBBSize().z;
  4656. // max distance our centers can be apart with the boxes still overlapping
  4657. float flMaxZDist = ( enemyHeight + myHeight ) * 0.5f;
  4658. // see if the enemy is closer to my head, feet or in between
  4659. if ( enemyDelta.z > flMaxZDist )
  4660. {
  4661. // enemy feet above my head, compute distance from my head to his feet
  4662. enemyDelta.z -= flMaxZDist;
  4663. }
  4664. else if ( enemyDelta.z < -flMaxZDist )
  4665. {
  4666. // enemy head below my feet, return distance between my feet and his head
  4667. enemyDelta.z += flMaxZDist;
  4668. }
  4669. else
  4670. {
  4671. // boxes overlap in Z, no delta
  4672. enemyDelta.z = 0;
  4673. }
  4674. return enemyDelta.Length();
  4675. }
  4676. //-----------------------------------------------------------------------------
  4677. float CAI_BaseNPC::GetReactionDelay( CBaseEntity *pEnemy )
  4678. {
  4679. return ( m_NPCState == NPC_STATE_ALERT || m_NPCState == NPC_STATE_COMBAT ) ?
  4680. ai_reaction_delay_alert.GetFloat() :
  4681. ai_reaction_delay_idle.GetFloat();
  4682. }
  4683. //-----------------------------------------------------------------------------
  4684. // Purpose: Update information on my enemy
  4685. // Input :
  4686. // Output : Returns true is new enemy, false is known enemy
  4687. //-----------------------------------------------------------------------------
  4688. bool CAI_BaseNPC::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
  4689. {
  4690. bool firstHand = ( pInformer == NULL || pInformer == this );
  4691. AI_PROFILE_SCOPE(CAI_BaseNPC_UpdateEnemyMemory);
  4692. if ( GetEnemies() )
  4693. {
  4694. // If the was eluding me and allow the NPC to play a sound
  4695. if (GetEnemies()->HasEludedMe(pEnemy))
  4696. {
  4697. FoundEnemySound();
  4698. }
  4699. float reactionDelay = ( !pInformer || pInformer == this ) ? GetReactionDelay( pEnemy ) : 0.0;
  4700. bool result = GetEnemies()->UpdateMemory(GetNavigator()->GetNetwork(), pEnemy, position, reactionDelay, firstHand);
  4701. if ( !firstHand && pEnemy && result && GetState() == NPC_STATE_IDLE ) // if it's a new potential enemy
  4702. ForceDecisionThink();
  4703. if ( firstHand && pEnemy && m_pSquad )
  4704. {
  4705. m_pSquad->UpdateEnemyMemory( this, pEnemy, position );
  4706. }
  4707. return result;
  4708. }
  4709. return true;
  4710. }
  4711. //-----------------------------------------------------------------------------
  4712. // Purpose: Remembers the thing my enemy is hiding behind. Called when either
  4713. // COND_ENEMY_OCCLUDED or COND_WEAPON_SIGHT_OCCLUDED is set.
  4714. //-----------------------------------------------------------------------------
  4715. void CAI_BaseNPC::SetEnemyOccluder(CBaseEntity *pBlocker)
  4716. {
  4717. m_hEnemyOccluder = pBlocker;
  4718. }
  4719. //-----------------------------------------------------------------------------
  4720. // Purpose: Gets the thing my enemy is hiding behind (assuming they are hiding).
  4721. //-----------------------------------------------------------------------------
  4722. CBaseEntity *CAI_BaseNPC::GetEnemyOccluder(void)
  4723. {
  4724. return m_hEnemyOccluder.Get();
  4725. }
  4726. //-----------------------------------------------------------------------------
  4727. // Purpose: part of the Condition collection process
  4728. // gets and stores data and conditions pertaining to a npc's
  4729. // enemy.
  4730. // @TODO (toml 07-27-03): this should become subservient to the senses. right
  4731. // now, it yields different result
  4732. // Input :
  4733. // Output :
  4734. //-----------------------------------------------------------------------------
  4735. void CAI_BaseNPC::GatherEnemyConditions( CBaseEntity *pEnemy )
  4736. {
  4737. AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions);
  4738. ClearCondition( COND_ENEMY_FACING_ME );
  4739. ClearCondition( COND_BEHIND_ENEMY );
  4740. // ---------------------------
  4741. // Set visibility conditions
  4742. // ---------------------------
  4743. if ( HasCondition( COND_NEW_ENEMY ) || GetSenses()->GetTimeLastUpdate( GetEnemy() ) == gpGlobals->curtime )
  4744. {
  4745. AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_Visibility);
  4746. ClearCondition( COND_HAVE_ENEMY_LOS );
  4747. ClearCondition( COND_ENEMY_OCCLUDED );
  4748. CBaseEntity *pBlocker = NULL;
  4749. SetEnemyOccluder(NULL);
  4750. bool bSensesDidSee = GetSenses()->DidSeeEntity( pEnemy );
  4751. if ( !bSensesDidSee && ( ( EnemyDistance( pEnemy ) >= GetSenses()->GetDistLook() ) || !FVisible( pEnemy, MASK_BLOCKLOS, &pBlocker ) ) )
  4752. {
  4753. // No LOS to enemy
  4754. SetEnemyOccluder(pBlocker);
  4755. SetCondition( COND_ENEMY_OCCLUDED );
  4756. ClearCondition( COND_SEE_ENEMY );
  4757. if (HasMemory( bits_MEMORY_HAD_LOS ))
  4758. {
  4759. AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs);
  4760. // Send output event
  4761. if (GetEnemy()->IsPlayer())
  4762. {
  4763. m_OnLostPlayerLOS.FireOutput( GetEnemy(), this );
  4764. }
  4765. m_OnLostEnemyLOS.FireOutput( GetEnemy(), this );
  4766. }
  4767. Forget( bits_MEMORY_HAD_LOS );
  4768. }
  4769. else
  4770. {
  4771. // Have LOS but may not be in view cone
  4772. SetCondition( COND_HAVE_ENEMY_LOS );
  4773. if ( bSensesDidSee )
  4774. {
  4775. // Have LOS and in view cone
  4776. SetCondition( COND_SEE_ENEMY );
  4777. }
  4778. else
  4779. {
  4780. ClearCondition( COND_SEE_ENEMY );
  4781. }
  4782. if (!HasMemory( bits_MEMORY_HAD_LOS ))
  4783. {
  4784. AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs);
  4785. // Send output event
  4786. EHANDLE hEnemy;
  4787. hEnemy.Set( GetEnemy() );
  4788. if (GetEnemy()->IsPlayer())
  4789. {
  4790. m_OnFoundPlayer.Set(hEnemy, this, this);
  4791. m_OnFoundEnemy.Set(hEnemy, this, this);
  4792. }
  4793. else
  4794. {
  4795. m_OnFoundEnemy.Set(hEnemy, this, this);
  4796. }
  4797. }
  4798. Remember( bits_MEMORY_HAD_LOS );
  4799. }
  4800. AI_PROFILE_SCOPE_END();
  4801. }
  4802. // -------------------
  4803. // If enemy is dead
  4804. // -------------------
  4805. if ( !pEnemy->IsAlive() )
  4806. {
  4807. SetCondition( COND_ENEMY_DEAD );
  4808. ClearCondition( COND_SEE_ENEMY );
  4809. ClearCondition( COND_ENEMY_OCCLUDED );
  4810. return;
  4811. }
  4812. float flDistToEnemy = EnemyDistance(pEnemy);
  4813. AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_SeeEnemy);
  4814. if ( HasCondition( COND_SEE_ENEMY ) )
  4815. {
  4816. // Trail the enemy a bit if he's moving
  4817. if (pEnemy->GetSmoothedVelocity() != vec3_origin)
  4818. {
  4819. Vector vTrailPos = pEnemy->GetAbsOrigin() - pEnemy->GetSmoothedVelocity() * random->RandomFloat( -0.05, 0 );
  4820. UpdateEnemyMemory(pEnemy,vTrailPos);
  4821. }
  4822. else
  4823. {
  4824. UpdateEnemyMemory(pEnemy,pEnemy->GetAbsOrigin());
  4825. }
  4826. // If it's not an NPC, assume it can't see me
  4827. if ( pEnemy->MyCombatCharacterPointer() && pEnemy->MyCombatCharacterPointer()->FInViewCone ( this ) )
  4828. {
  4829. SetCondition ( COND_ENEMY_FACING_ME );
  4830. ClearCondition ( COND_BEHIND_ENEMY );
  4831. }
  4832. else
  4833. {
  4834. ClearCondition( COND_ENEMY_FACING_ME );
  4835. SetCondition ( COND_BEHIND_ENEMY );
  4836. }
  4837. }
  4838. else if ( (!HasCondition(COND_ENEMY_OCCLUDED) && !HasCondition(COND_SEE_ENEMY)) && ( flDistToEnemy <= 256 ) )
  4839. {
  4840. // if the enemy is not occluded, and unseen, that means it is behind or beside the npc.
  4841. // if the enemy is near enough the npc, we go ahead and let the npc know where the
  4842. // enemy is. Send the enemy in as the informer so this knowledge will be regarded as
  4843. // secondhand so that the NPC doesn't
  4844. UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), pEnemy );
  4845. }
  4846. AI_PROFILE_SCOPE_END();
  4847. float tooFar = m_flDistTooFar;
  4848. if ( GetActiveWeapon() && HasCondition(COND_SEE_ENEMY) )
  4849. {
  4850. tooFar = MAX( m_flDistTooFar, GetActiveWeapon()->m_fMaxRange1 );
  4851. }
  4852. if ( flDistToEnemy >= tooFar )
  4853. {
  4854. // enemy is very far away from npc
  4855. SetCondition( COND_ENEMY_TOO_FAR );
  4856. }
  4857. else
  4858. {
  4859. ClearCondition( COND_ENEMY_TOO_FAR );
  4860. }
  4861. if ( FCanCheckAttacks() )
  4862. {
  4863. // This may also call SetEnemyOccluder!
  4864. GatherAttackConditions( GetEnemy(), flDistToEnemy );
  4865. }
  4866. else
  4867. {
  4868. ClearAttackConditions();
  4869. }
  4870. // If my enemy has moved significantly, or if the enemy has changed update my path
  4871. UpdateEnemyPos();
  4872. // If my target entity has moved significantly, update my path
  4873. // This is an odd place to put this, but where else should it go?
  4874. UpdateTargetPos();
  4875. // ----------------------------------------------------------------------------
  4876. // Check if enemy is reachable via the node graph unless I'm not on a network
  4877. // ----------------------------------------------------------------------------
  4878. if (GetNavigator()->IsOnNetwork())
  4879. {
  4880. // Note that unreachablity times out
  4881. if (IsUnreachable(GetEnemy()))
  4882. {
  4883. SetCondition(COND_ENEMY_UNREACHABLE);
  4884. }
  4885. }
  4886. //-----------------------------------------------------------------------
  4887. // If I haven't seen the enemy in a while he may have eluded me
  4888. //-----------------------------------------------------------------------
  4889. if (gpGlobals->curtime - GetEnemyLastTimeSeen() > 8)
  4890. {
  4891. //-----------------------------------------------------------------------
  4892. // I'm at last known position at enemy isn't in sight then has eluded me
  4893. // ----------------------------------------------------------------------
  4894. Vector flEnemyLKP = GetEnemyLKP();
  4895. if (((flEnemyLKP - GetAbsOrigin()).Length2D() < 48) &&
  4896. !HasCondition(COND_SEE_ENEMY))
  4897. {
  4898. MarkEnemyAsEluded();
  4899. }
  4900. //-------------------------------------------------------------------
  4901. // If enemy isn't reachable, I can see last known position and enemy
  4902. // isn't there, then he has eluded me
  4903. // ------------------------------------------------------------------
  4904. if (!HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_UNREACHABLE))
  4905. {
  4906. if ( !FVisible( flEnemyLKP ) )
  4907. {
  4908. MarkEnemyAsEluded();
  4909. }
  4910. }
  4911. }
  4912. }
  4913. //-----------------------------------------------------------------------------
  4914. // In the case of goaltype enemy, update the goal position
  4915. //-----------------------------------------------------------------------------
  4916. float CAI_BaseNPC::GetGoalRepathTolerance( CBaseEntity *pGoalEnt, GoalType_t type, const Vector &curGoal, const Vector &curTargetPos )
  4917. {
  4918. float distToGoal = ( GetAbsOrigin() - curTargetPos ).Length() - GetNavigator()->GetArrivalDistance();
  4919. float distMoved1Sec = GetSmoothedVelocity().Length();
  4920. float result = 120; // FIXME: why 120?
  4921. if (distMoved1Sec > 0.0)
  4922. {
  4923. float t = distToGoal / distMoved1Sec;
  4924. result = clamp( 120 * t, 0, 120 );
  4925. // Msg("t %.2f : d %.0f (%.0f)\n", t, result, distMoved1Sec );
  4926. }
  4927. if ( !pGoalEnt->IsPlayer() )
  4928. result *= 1.20;
  4929. return result;
  4930. }
  4931. //-----------------------------------------------------------------------------
  4932. // In the case of goaltype enemy, update the goal position
  4933. //-----------------------------------------------------------------------------
  4934. void CAI_BaseNPC::UpdateEnemyPos()
  4935. {
  4936. // Don't perform path recomputations during a climb or a jump
  4937. if ( !GetNavigator()->IsInterruptable() )
  4938. return;
  4939. if ( m_AnyUpdateEnemyPosTimer.Expired() && m_UpdateEnemyPosTimer.Expired() )
  4940. {
  4941. // FIXME: does GetGoalRepathTolerance() limit re-routing enough to remove this?
  4942. // m_UpdateEnemyPosTimer.Set( 0.5, 1.0 );
  4943. // If my enemy has moved significantly, or if the enemy has changed update my path
  4944. if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY )
  4945. {
  4946. if (m_hEnemy != GetNavigator()->GetGoalTarget())
  4947. {
  4948. GetNavigator()->SetGoalTarget( m_hEnemy, vec3_origin );
  4949. }
  4950. else
  4951. {
  4952. Vector vEnemyLKP = GetEnemyLKP();
  4953. TranslateNavGoal( GetEnemy(), vEnemyLKP );
  4954. float tolerance = GetGoalRepathTolerance( GetEnemy(), GOALTYPE_ENEMY, GetNavigator()->GetGoalPos(), vEnemyLKP);
  4955. if ( (GetNavigator()->GetGoalPos() - vEnemyLKP).Length() > tolerance )
  4956. {
  4957. // FIXME: when fleeing crowds, won't this severely limit the effectiveness of each individual? Shouldn't this be a mutex that's held for some period so that at least one attacker is effective?
  4958. m_AnyUpdateEnemyPosTimer.Set( 0.1 ); // FIXME: what's a reasonable interval?
  4959. if ( !GetNavigator()->RefindPathToGoal( false ) )
  4960. {
  4961. TaskFail( FAIL_NO_ROUTE );
  4962. }
  4963. }
  4964. }
  4965. }
  4966. }
  4967. }
  4968. //-----------------------------------------------------------------------------
  4969. // In the case of goaltype targetent, update the goal position
  4970. //-----------------------------------------------------------------------------
  4971. void CAI_BaseNPC::UpdateTargetPos()
  4972. {
  4973. // BRJ 10/7/02
  4974. // FIXME: make this check time based instead of distance based!
  4975. // Don't perform path recomputations during a climb or a jump
  4976. if ( !GetNavigator()->IsInterruptable() )
  4977. return;
  4978. // If my target entity has moved significantly, or has changed, update my path
  4979. // This is an odd place to put this, but where else should it go?
  4980. if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT )
  4981. {
  4982. if (m_hTargetEnt != GetNavigator()->GetGoalTarget())
  4983. {
  4984. GetNavigator()->SetGoalTarget( m_hTargetEnt, vec3_origin );
  4985. }
  4986. else if ( GetNavigator()->GetGoalFlags() & AIN_UPDATE_TARGET_POS )
  4987. {
  4988. if ( GetTarget() == NULL || (GetNavigator()->GetGoalPos() - GetTarget()->GetAbsOrigin()).Length() > GetGoalRepathTolerance( GetTarget(), GOALTYPE_TARGETENT, GetNavigator()->GetGoalPos(), GetTarget()->GetAbsOrigin()) )
  4989. {
  4990. if ( !GetNavigator()->RefindPathToGoal( false ) )
  4991. {
  4992. TaskFail( FAIL_NO_ROUTE );
  4993. }
  4994. }
  4995. }
  4996. }
  4997. }
  4998. //-----------------------------------------------------------------------------
  4999. // Purpose: part of the Condition collection process
  5000. // gets and stores data and conditions pertaining to a npc's
  5001. // enemy.
  5002. // Input :
  5003. // Output :
  5004. //-----------------------------------------------------------------------------
  5005. void CAI_BaseNPC::CheckTarget( CBaseEntity *pTarget )
  5006. {
  5007. AI_PROFILE_SCOPE(CAI_Enemies_CheckTarget);
  5008. ClearCondition ( COND_HAVE_TARGET_LOS );
  5009. ClearCondition ( COND_TARGET_OCCLUDED );
  5010. // ---------------------------
  5011. // Set visibility conditions
  5012. // ---------------------------
  5013. if ( ( EnemyDistance( pTarget ) >= GetSenses()->GetDistLook() ) || !FVisible( pTarget ) )
  5014. {
  5015. // No LOS to target
  5016. SetCondition( COND_TARGET_OCCLUDED );
  5017. }
  5018. else
  5019. {
  5020. // Have LOS (may not be in view cone)
  5021. SetCondition( COND_HAVE_TARGET_LOS );
  5022. }
  5023. UpdateTargetPos();
  5024. }
  5025. //-----------------------------------------------------------------------------
  5026. // Purpose: Creates a bullseye of limited lifespan at the provided position
  5027. // Input : vecOrigin - Where to create the bullseye
  5028. // duration - The lifespan of the bullseye
  5029. // Output : A BaseNPC pointer to the bullseye
  5030. //
  5031. // NOTES : It is the caller's responsibility to set up relationships with
  5032. // this bullseye!
  5033. //-----------------------------------------------------------------------------
  5034. CAI_BaseNPC *CAI_BaseNPC::CreateCustomTarget( const Vector &vecOrigin, float duration )
  5035. {
  5036. #ifdef HL2_DLL
  5037. CNPC_Bullseye *pTarget = (CNPC_Bullseye*)CreateEntityByName( "npc_bullseye" );
  5038. ASSERT( pTarget != NULL );
  5039. // Build a nonsolid bullseye and place it in the desired location
  5040. // The bullseye must take damage or the SetHealth 0 call will not be able
  5041. pTarget->AddSpawnFlags( SF_BULLSEYE_NONSOLID );
  5042. pTarget->SetAbsOrigin( vecOrigin );
  5043. pTarget->Spawn();
  5044. // Set it up to remove itself, unless told to be infinite (-1)
  5045. if( duration > -1 )
  5046. {
  5047. variant_t value;
  5048. value.SetFloat(0);
  5049. g_EventQueue.AddEvent( pTarget, "SetHealth", value, duration, this, this );
  5050. }
  5051. return pTarget;
  5052. #else
  5053. return NULL;
  5054. #endif// HL2_DLL
  5055. }
  5056. //-----------------------------------------------------------------------------
  5057. // Purpose:
  5058. // Input : eNewActivity -
  5059. // Output : Activity
  5060. //-----------------------------------------------------------------------------
  5061. Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity )
  5062. {
  5063. Assert( eNewActivity != ACT_INVALID );
  5064. if (eNewActivity == ACT_RANGE_ATTACK1)
  5065. {
  5066. if ( IsCrouching() )
  5067. {
  5068. eNewActivity = ACT_RANGE_ATTACK1_LOW;
  5069. }
  5070. }
  5071. else if (eNewActivity == ACT_RELOAD)
  5072. {
  5073. if (IsCrouching())
  5074. {
  5075. eNewActivity = ACT_RELOAD_LOW;
  5076. }
  5077. }
  5078. else if ( eNewActivity == ACT_IDLE )
  5079. {
  5080. if ( IsCrouching() )
  5081. {
  5082. eNewActivity = ACT_CROUCHIDLE;
  5083. }
  5084. }
  5085. // ====
  5086. // HACK : LEIPZIG 06 - The underlying problem is that the AR2 and SMG1 cannot map IDLE_ANGRY to a crouched equivalent automatically
  5087. // which causes the character to pop up and down in their idle state of firing while crouched. -- jdw
  5088. else if ( eNewActivity == ACT_IDLE_ANGRY_SMG1 )
  5089. {
  5090. if ( IsCrouching() )
  5091. {
  5092. eNewActivity = ACT_RANGE_AIM_LOW;
  5093. }
  5094. }
  5095. // ====
  5096. if (CapabilitiesGet() & bits_CAP_DUCK)
  5097. {
  5098. if (eNewActivity == ACT_RELOAD)
  5099. {
  5100. return GetReloadActivity(GetHintNode());
  5101. }
  5102. else if ((eNewActivity == ACT_COVER ) ||
  5103. (eNewActivity == ACT_IDLE && HasMemory(bits_MEMORY_INCOVER)))
  5104. {
  5105. Activity nCoverActivity = GetCoverActivity(GetHintNode());
  5106. // ---------------------------------------------------------------
  5107. // Some NPCs don't have a cover activity defined so just use idle
  5108. // ---------------------------------------------------------------
  5109. if (SelectWeightedSequence( nCoverActivity ) == ACTIVITY_NOT_AVAILABLE)
  5110. {
  5111. nCoverActivity = ACT_IDLE;
  5112. }
  5113. return nCoverActivity;
  5114. }
  5115. }
  5116. return eNewActivity;
  5117. }
  5118. //-----------------------------------------------------------------------------
  5119. Activity CAI_BaseNPC::TranslateActivity( Activity idealActivity, Activity *pIdealWeaponActivity )
  5120. {
  5121. const int MAX_TRIES = 5;
  5122. int count = 0;
  5123. bool bIdealWeaponRequired = false;
  5124. Activity idealWeaponActivity;
  5125. Activity baseTranslation;
  5126. bool bWeaponRequired = false;
  5127. Activity weaponTranslation;
  5128. Activity last;
  5129. Activity current;
  5130. idealWeaponActivity = Weapon_TranslateActivity( idealActivity, &bIdealWeaponRequired );
  5131. if ( pIdealWeaponActivity )
  5132. *pIdealWeaponActivity = idealWeaponActivity;
  5133. baseTranslation = idealActivity;
  5134. weaponTranslation = idealActivity;
  5135. last = idealActivity;
  5136. while ( count++ < MAX_TRIES )
  5137. {
  5138. current = NPC_TranslateActivity( last );
  5139. if ( current != last )
  5140. baseTranslation = current;
  5141. weaponTranslation = Weapon_TranslateActivity( current, &bWeaponRequired );
  5142. if ( weaponTranslation == last )
  5143. break;
  5144. last = weaponTranslation;
  5145. }
  5146. AssertMsg( count < MAX_TRIES, "Circular activity translation!" );
  5147. if ( last == ACT_SCRIPT_CUSTOM_MOVE )
  5148. return ACT_SCRIPT_CUSTOM_MOVE;
  5149. if ( HaveSequenceForActivity( weaponTranslation ) )
  5150. return weaponTranslation;
  5151. if ( bWeaponRequired )
  5152. {
  5153. // only complain about an activity once
  5154. static CUtlVector< Activity > sUniqueActivities;
  5155. if (!sUniqueActivities.Find( weaponTranslation))
  5156. {
  5157. // FIXME: warning
  5158. DevWarning( "%s missing activity \"%s\" needed by weapon\"%s\"\n",
  5159. GetClassname(), GetActivityName( weaponTranslation ), GetActiveWeapon()->GetClassname() );
  5160. sUniqueActivities.AddToTail( weaponTranslation );
  5161. }
  5162. }
  5163. if ( baseTranslation != weaponTranslation && HaveSequenceForActivity( baseTranslation ) )
  5164. return baseTranslation;
  5165. if ( idealWeaponActivity != baseTranslation && HaveSequenceForActivity( idealWeaponActivity ) )
  5166. return idealActivity;
  5167. if ( idealActivity != idealWeaponActivity && HaveSequenceForActivity( idealActivity ) )
  5168. return idealActivity;
  5169. Assert( !HaveSequenceForActivity( idealActivity ) );
  5170. if ( idealActivity == ACT_RUN )
  5171. {
  5172. idealActivity = ACT_WALK;
  5173. }
  5174. else if ( idealActivity == ACT_WALK )
  5175. {
  5176. idealActivity = ACT_RUN;
  5177. }
  5178. return idealActivity;
  5179. }
  5180. //-----------------------------------------------------------------------------
  5181. // Purpose:
  5182. // Input : NewActivity -
  5183. // iSequence -
  5184. // translatedActivity -
  5185. // weaponActivity -
  5186. //-----------------------------------------------------------------------------
  5187. void CAI_BaseNPC::ResolveActivityToSequence(Activity NewActivity, int &iSequence, Activity &translatedActivity, Activity &weaponActivity)
  5188. {
  5189. AI_PROFILE_SCOPE( CAI_BaseNPC_ResolveActivityToSequence );
  5190. if ( NewActivity == ACT_SPECIFIC_SEQUENCE )
  5191. {
  5192. translatedActivity = weaponActivity = ACT_SPECIFIC_SEQUENCE;
  5193. iSequence = m_nIdealSequence;
  5194. return;
  5195. }
  5196. iSequence = ACTIVITY_NOT_AVAILABLE;
  5197. translatedActivity = TranslateActivity( NewActivity, &weaponActivity );
  5198. if ( ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE ) )
  5199. {
  5200. iSequence = GetScriptCustomMoveSequence();
  5201. }
  5202. else
  5203. {
  5204. iSequence = SelectWeightedSequence( translatedActivity );
  5205. if ( iSequence == ACTIVITY_NOT_AVAILABLE )
  5206. {
  5207. static CAI_BaseNPC *pLastWarn;
  5208. static Activity lastWarnActivity;
  5209. static float timeLastWarn;
  5210. if ( ( pLastWarn != this && lastWarnActivity != translatedActivity ) || gpGlobals->curtime - timeLastWarn > 5.0 )
  5211. {
  5212. DevWarning( "%s:%s:%s has no sequence for act:%s\n", GetClassname(), GetDebugName(), STRING( GetModelName() ), ActivityList_NameForIndex(translatedActivity) );
  5213. pLastWarn = this;
  5214. lastWarnActivity = translatedActivity;
  5215. timeLastWarn = gpGlobals->curtime;
  5216. }
  5217. if ( translatedActivity == ACT_RUN )
  5218. {
  5219. translatedActivity = ACT_WALK;
  5220. iSequence = SelectWeightedSequence( translatedActivity );
  5221. }
  5222. }
  5223. }
  5224. if ( iSequence == ACT_INVALID )
  5225. {
  5226. // Abject failure. Use sequence zero.
  5227. iSequence = 0;
  5228. }
  5229. }
  5230. //-----------------------------------------------------------------------------
  5231. // Purpose:
  5232. // Input : NewActivity -
  5233. // iSequence -
  5234. // translatedActivity -
  5235. // weaponActivity -
  5236. //-----------------------------------------------------------------------------
  5237. extern ConVar ai_sequence_debug;
  5238. void CAI_BaseNPC::SetActivityAndSequence(Activity NewActivity, int iSequence, Activity translatedActivity, Activity weaponActivity)
  5239. {
  5240. m_translatedActivity = translatedActivity;
  5241. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5242. {
  5243. DevMsg("SetActivityAndSequence : %s: %s:%s -> %s:%s / %s:%s\n", GetClassname(),
  5244. GetActivityName(GetActivity()), GetSequenceName(GetSequence()),
  5245. GetActivityName(NewActivity), GetSequenceName(iSequence),
  5246. GetActivityName(translatedActivity), GetActivityName(weaponActivity) );
  5247. }
  5248. // Set to the desired anim, or default anim if the desired is not present
  5249. if ( iSequence > ACTIVITY_NOT_AVAILABLE )
  5250. {
  5251. if ( GetSequence() != iSequence || !SequenceLoops() )
  5252. {
  5253. //
  5254. // Don't reset frame between movement phased animations
  5255. if (!IsActivityMovementPhased( m_Activity ) ||
  5256. !IsActivityMovementPhased( NewActivity ))
  5257. {
  5258. SetCycle( 0 );
  5259. }
  5260. }
  5261. ResetSequence( iSequence );
  5262. Weapon_SetActivity( weaponActivity, SequenceDuration( iSequence ) );
  5263. }
  5264. else
  5265. {
  5266. // Not available try to get default anim
  5267. ResetSequence( 0 );
  5268. }
  5269. // Set the view position based on the current activity
  5270. SetViewOffset( EyeOffset(m_translatedActivity) );
  5271. if (m_Activity != NewActivity)
  5272. {
  5273. OnChangeActivity(NewActivity);
  5274. }
  5275. // NOTE: We DO NOT write the translated activity here.
  5276. // This is to abstract the activity translation from the AI code.
  5277. // As far as the code is concerned, a translation is merely a new set of sequences
  5278. // that should be regarded as the activity in question.
  5279. // Go ahead and set this so it doesn't keep trying when the anim is not present
  5280. m_Activity = NewActivity;
  5281. // this cannot be called until m_Activity stores NewActivity!
  5282. GetMotor()->RecalculateYawSpeed();
  5283. }
  5284. //-----------------------------------------------------------------------------
  5285. // Purpose: Sets the activity to the desired activity immediately, skipping any
  5286. // transition sequences.
  5287. // Input : NewActivity -
  5288. //-----------------------------------------------------------------------------
  5289. void CAI_BaseNPC::SetActivity( Activity NewActivity )
  5290. {
  5291. // If I'm already doing the NewActivity I can bail.
  5292. // FIXME: Should this be based on the current translated activity and ideal translated activity (calculated below)?
  5293. // The old code only cared about the logical activity, not translated.
  5294. if (m_Activity == NewActivity)
  5295. {
  5296. return;
  5297. }
  5298. // Don't do this if I'm playing a transition, unless it's ACT_RESET.
  5299. if ( NewActivity != ACT_RESET && m_Activity == ACT_TRANSITION && m_IdealActivity != ACT_DO_NOT_DISTURB )
  5300. {
  5301. return;
  5302. }
  5303. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5304. {
  5305. DevMsg("SetActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity));
  5306. }
  5307. if ( !GetModelPtr() )
  5308. return;
  5309. // In case someone calls this with something other than the ideal activity.
  5310. m_IdealActivity = NewActivity;
  5311. // Resolve to ideals and apply directly, skipping transitions.
  5312. ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5313. //DevMsg("%s: SLAM %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
  5314. SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5315. }
  5316. //-----------------------------------------------------------------------------
  5317. // Purpose: Sets the activity that we would like to transition toward.
  5318. // Input : NewActivity -
  5319. //-----------------------------------------------------------------------------
  5320. void CAI_BaseNPC::SetIdealActivity( Activity NewActivity )
  5321. {
  5322. // ignore if it's an ACT_TRANSITION, it means somewhere we're setting IdealActivity with a bogus intermediate value
  5323. if (NewActivity == ACT_TRANSITION)
  5324. {
  5325. Assert( 0 );
  5326. return;
  5327. }
  5328. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5329. {
  5330. DevMsg("SetIdealActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity));
  5331. }
  5332. if (NewActivity == ACT_RESET)
  5333. {
  5334. // They probably meant to call SetActivity(ACT_RESET)... we'll fix it for them.
  5335. SetActivity(ACT_RESET);
  5336. return;
  5337. }
  5338. m_IdealActivity = NewActivity;
  5339. if( NewActivity == ACT_DO_NOT_DISTURB )
  5340. {
  5341. // Don't resolve anything! Leave it the way the user has it right now.
  5342. return;
  5343. }
  5344. if ( !GetModelPtr() )
  5345. return;
  5346. // Perform translation in case we need to change sequences within a single activity,
  5347. // such as between a standing idle and a crouching idle.
  5348. ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5349. }
  5350. //-----------------------------------------------------------------------------
  5351. // Purpose: Moves toward the ideal activity through any transition sequences.
  5352. //-----------------------------------------------------------------------------
  5353. void CAI_BaseNPC::AdvanceToIdealActivity(void)
  5354. {
  5355. // If there is a transition sequence between the current sequence and the ideal sequence...
  5356. int nNextSequence = FindTransitionSequence(GetSequence(), m_nIdealSequence, NULL);
  5357. if (nNextSequence != -1)
  5358. {
  5359. // We found a transition sequence or possibly went straight to
  5360. // the ideal sequence.
  5361. if (nNextSequence != m_nIdealSequence)
  5362. {
  5363. // DevMsg("%s: TRANSITION %s -> %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(nNextSequence), GetSequenceName(m_nIdealSequence));
  5364. Activity eWeaponActivity = ACT_TRANSITION;
  5365. Activity eTranslatedActivity = ACT_TRANSITION;
  5366. // Figure out if the transition sequence has an associated activity that
  5367. // we can use for our weapon. Do activity translation also.
  5368. Activity eTransitionActivity = GetSequenceActivity(nNextSequence);
  5369. if (eTransitionActivity != ACT_INVALID)
  5370. {
  5371. int nDiscard;
  5372. ResolveActivityToSequence(eTransitionActivity, nDiscard, eTranslatedActivity, eWeaponActivity);
  5373. }
  5374. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5375. {
  5376. DevMsg("AdvanceToIdealActivity calling SetActivityAndSequence because of change %s\n", GetSequenceName(nNextSequence));
  5377. }
  5378. // Set activity and sequence to the transition stuff. Set the activity to ACT_TRANSITION
  5379. // so we know we're in a transition.
  5380. SetActivityAndSequence(ACT_TRANSITION, nNextSequence, eTranslatedActivity, eWeaponActivity);
  5381. }
  5382. else
  5383. {
  5384. /*
  5385. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5386. {
  5387. DevMsg("%s: IDEAL %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
  5388. }
  5389. */
  5390. // Set activity and sequence to the ideal stuff that was set up in MaintainActivity.
  5391. SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5392. }
  5393. }
  5394. // Else go straight there to the ideal activity.
  5395. else
  5396. {
  5397. /*
  5398. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5399. {
  5400. DevMsg("%s: Unable to get from sequence %s to %s!\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
  5401. }
  5402. */
  5403. SetActivity(m_IdealActivity);
  5404. }
  5405. }
  5406. //-----------------------------------------------------------------------------
  5407. // Purpose: Tries to achieve our ideal animation state, playing any transition
  5408. // sequences that we need to play to get there.
  5409. //-----------------------------------------------------------------------------
  5410. void CAI_BaseNPC::MaintainActivity(void)
  5411. {
  5412. AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainActivity );
  5413. if ( m_lifeState == LIFE_DEAD )
  5414. {
  5415. // Don't maintain activities if we're daid.
  5416. // Blame Speyrer
  5417. return;
  5418. }
  5419. if ((GetState() == NPC_STATE_SCRIPT))
  5420. {
  5421. // HACK: finish any transitions we might be playing before we yield control to the script
  5422. if (GetActivity() != ACT_TRANSITION)
  5423. {
  5424. // Our animation state is being controlled by a script.
  5425. return;
  5426. }
  5427. }
  5428. if( m_IdealActivity == ACT_DO_NOT_DISTURB || !GetModelPtr() )
  5429. {
  5430. return;
  5431. }
  5432. // We may have work to do if we aren't playing our ideal activity OR if we
  5433. // aren't playing our ideal sequence.
  5434. if ((GetActivity() != m_IdealActivity) || (GetSequence() != m_nIdealSequence))
  5435. {
  5436. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5437. {
  5438. DevMsg("MaintainActivity %s : %s:%s -> %s:%s\n", GetClassname(),
  5439. GetActivityName(GetActivity()), GetSequenceName(GetSequence()),
  5440. GetActivityName(m_IdealActivity), GetSequenceName(m_nIdealSequence));
  5441. }
  5442. bool bAdvance = false;
  5443. // If we're in a transition activity, see if we are done with the transition.
  5444. if (GetActivity() == ACT_TRANSITION)
  5445. {
  5446. // If the current sequence is finished, try to go to the next one
  5447. // closer to our ideal sequence.
  5448. if (IsSequenceFinished())
  5449. {
  5450. bAdvance = true;
  5451. }
  5452. // Else a transition sequence is in progress, do nothing.
  5453. }
  5454. // Else get a specific sequence for the activity and try to transition to that.
  5455. else
  5456. {
  5457. // Save off a target sequence and translated activities to apply when we finish
  5458. // playing all the transitions and finally arrive at our ideal activity.
  5459. ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5460. bAdvance = true;
  5461. }
  5462. if (bAdvance)
  5463. {
  5464. // Try to go to the next sequence closer to our ideal sequence.
  5465. AdvanceToIdealActivity();
  5466. }
  5467. }
  5468. }
  5469. //-----------------------------------------------------------------------------
  5470. // Purpose: Returns true if our ideal activity has finished playing.
  5471. //-----------------------------------------------------------------------------
  5472. bool CAI_BaseNPC::IsActivityFinished( void )
  5473. {
  5474. return (IsSequenceFinished() && (GetSequence() == m_nIdealSequence));
  5475. }
  5476. //-----------------------------------------------------------------------------
  5477. // Purpose: Checks to see if the activity is one of the standard phase-matched movement activities
  5478. // Input : activity
  5479. //-----------------------------------------------------------------------------
  5480. bool CAI_BaseNPC::IsActivityMovementPhased( Activity activity )
  5481. {
  5482. switch( activity )
  5483. {
  5484. case ACT_WALK:
  5485. case ACT_WALK_AIM:
  5486. case ACT_WALK_CROUCH:
  5487. case ACT_WALK_CROUCH_AIM:
  5488. case ACT_RUN:
  5489. case ACT_RUN_AIM:
  5490. case ACT_RUN_CROUCH:
  5491. case ACT_RUN_CROUCH_AIM:
  5492. case ACT_RUN_PROTECTED:
  5493. return true;
  5494. }
  5495. return false;
  5496. }
  5497. //-----------------------------------------------------------------------------
  5498. // Purpose:
  5499. //-----------------------------------------------------------------------------
  5500. void CAI_BaseNPC::OnChangeActivity( Activity eNewActivity )
  5501. {
  5502. if ( eNewActivity == ACT_RUN ||
  5503. eNewActivity == ACT_RUN_AIM ||
  5504. eNewActivity == ACT_WALK )
  5505. {
  5506. Stand();
  5507. }
  5508. }
  5509. //=========================================================
  5510. // SetSequenceByName
  5511. //=========================================================
  5512. void CAI_BaseNPC::SetSequenceByName( char *szSequence )
  5513. {
  5514. int iSequence = LookupSequence( szSequence );
  5515. if ( iSequence > ACTIVITY_NOT_AVAILABLE )
  5516. SetSequenceById( iSequence );
  5517. else
  5518. {
  5519. DevWarning( 2, "%s has no sequence to match %s request\n", GetClassname(), szSequence );
  5520. SetSequence( 0 ); // Set to the reset anim (if it's there)
  5521. }
  5522. }
  5523. //-----------------------------------------------------------------------------
  5524. void CAI_BaseNPC::SetSequenceById( int iSequence )
  5525. {
  5526. // Set to the desired anim, or default anim if the desired is not present
  5527. if ( iSequence > ACTIVITY_NOT_AVAILABLE )
  5528. {
  5529. if ( GetSequence() != iSequence || !SequenceLoops() )
  5530. {
  5531. SetCycle( 0 );
  5532. }
  5533. ResetSequence( iSequence ); // Set to the reset anim (if it's there)
  5534. GetMotor()->RecalculateYawSpeed();
  5535. }
  5536. else
  5537. {
  5538. // Not available try to get default anim
  5539. DevWarning( 2, "%s invalid sequence requested\n", GetClassname() );
  5540. SetSequence( 0 ); // Set to the reset anim (if it's there)
  5541. }
  5542. }
  5543. //-----------------------------------------------------------------------------
  5544. // Purpose: Returns the target entity
  5545. // Input :
  5546. // Output :
  5547. //-----------------------------------------------------------------------------
  5548. CBaseEntity *CAI_BaseNPC::GetNavTargetEntity(void)
  5549. {
  5550. if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY )
  5551. return m_hEnemy;
  5552. else if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT )
  5553. return m_hTargetEnt;
  5554. return NULL;
  5555. }
  5556. //-----------------------------------------------------------------------------
  5557. // Purpose: returns zero if the caller can jump from
  5558. // vecStart to vecEnd ignoring collisions with pTarget
  5559. //
  5560. // if the throw fails, returns the distance
  5561. // that can be travelled before an obstacle is hit
  5562. //-----------------------------------------------------------------------------
  5563. #include "ai_initutils.h"
  5564. //#define _THROWDEBUG
  5565. float CAI_BaseNPC::ThrowLimit( const Vector &vecStart,
  5566. const Vector &vecEnd,
  5567. float fGravity,
  5568. float fArcSize,
  5569. const Vector &mins,
  5570. const Vector &maxs,
  5571. CBaseEntity *pTarget,
  5572. Vector *jumpVel,
  5573. CBaseEntity **pBlocker)
  5574. {
  5575. // Get my jump velocity
  5576. Vector rawJumpVel = CalcThrowVelocity(vecStart, vecEnd, fGravity, fArcSize);
  5577. *jumpVel = rawJumpVel;
  5578. Vector vecFrom = vecStart;
  5579. // Calculate the total time of the jump minus a tiny fraction
  5580. float jumpTime = (vecStart - vecEnd).Length2D()/rawJumpVel.Length2D();
  5581. float timeStep = jumpTime / 10.0;
  5582. Vector gravity = Vector(0,0,fGravity);
  5583. // this loop takes single steps to the goal.
  5584. for (float flTime = 0 ; flTime < jumpTime-0.1 ; flTime += timeStep )
  5585. {
  5586. // Calculate my position after the time step (average velocity over this time step)
  5587. Vector nextPos = vecFrom + (rawJumpVel - 0.5 * gravity * timeStep) * timeStep;
  5588. // If last time step make next position the target position
  5589. if ((flTime + timeStep) > jumpTime)
  5590. {
  5591. nextPos = vecEnd;
  5592. }
  5593. trace_t tr;
  5594. AI_TraceHull( vecFrom, nextPos, mins, maxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  5595. if (tr.startsolid || tr.fraction < 1.0)
  5596. {
  5597. CBaseEntity *pEntity = tr.m_pEnt;
  5598. // If we hit the target we are good to go!
  5599. if (pEntity == pTarget)
  5600. {
  5601. return 0;
  5602. }
  5603. #ifdef _THROWDEBUG
  5604. NDebugOverlay::Line( vecFrom, nextPos, 255, 0, 0, true, 1.0 );
  5605. #endif
  5606. // ----------------------------------------------------------
  5607. // If blocked by an npc remember
  5608. // ----------------------------------------------------------
  5609. *pBlocker = pEntity;
  5610. // Return distance sucessfully traveled before block encountered
  5611. return ((tr.endpos - vecStart).Length());
  5612. }
  5613. #ifdef _THROWDEBUG
  5614. else
  5615. {
  5616. NDebugOverlay::Line( vecFrom, nextPos, 255, 255, 255, true, 1.0 );
  5617. }
  5618. #endif
  5619. rawJumpVel = rawJumpVel - gravity * timeStep;
  5620. vecFrom = nextPos;
  5621. }
  5622. return 0;
  5623. }
  5624. //-----------------------------------------------------------------------------
  5625. // Purpose: Called to initialize or re-initialize the vphysics hull when the size
  5626. // of the NPC changes
  5627. //-----------------------------------------------------------------------------
  5628. void CAI_BaseNPC::SetupVPhysicsHull()
  5629. {
  5630. // Bail if we don't need a physics shadow.
  5631. if ( GetMoveType() == MOVETYPE_VPHYSICS )
  5632. return;
  5633. if ( VPhysicsGetObject() )
  5634. {
  5635. // Disable collisions to get
  5636. VPhysicsGetObject()->EnableCollisions(false);
  5637. VPhysicsDestroyObject();
  5638. }
  5639. VPhysicsInitShadow( true, false );
  5640. IPhysicsObject *pPhysObj = VPhysicsGetObject();
  5641. if ( pPhysObj )
  5642. {
  5643. float mass = Studio_GetMass(GetModelPtr());
  5644. if ( mass > 0 )
  5645. {
  5646. pPhysObj->SetMass( mass );
  5647. }
  5648. #if _DEBUG
  5649. else
  5650. {
  5651. DevMsg("Warning: %s has no physical mass\n", STRING(GetModelName()));
  5652. }
  5653. #endif
  5654. IPhysicsShadowController *pController = pPhysObj->GetShadowController();
  5655. float avgsize = (WorldAlignSize().x + WorldAlignSize().y) * 0.5;
  5656. pController->SetTeleportDistance( avgsize * 0.5 );
  5657. m_bCheckContacts = true;
  5658. }
  5659. }
  5660. // Check for problematic physics objects resting on this NPC.
  5661. // They can screw up his navigation, so attach a controller to
  5662. // help separate the NPC & physics when you encounter these.
  5663. ConVar ai_auto_contact_solver( "ai_auto_contact_solver", "1" );
  5664. void CAI_BaseNPC::CheckPhysicsContacts()
  5665. {
  5666. if ( gpGlobals->frametime <= 0.0f || !ai_auto_contact_solver.GetBool() )
  5667. return;
  5668. m_bCheckContacts = false;
  5669. if ( GetMoveType() == MOVETYPE_STEP && VPhysicsGetObject())
  5670. {
  5671. IPhysicsObject *pPhysics = VPhysicsGetObject();
  5672. IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
  5673. CBaseEntity *pGroundEntity = GetGroundEntity();
  5674. float heightCheck = GetAbsOrigin().z + GetHullMaxs().z;
  5675. Vector npcVel;
  5676. pPhysics->GetVelocity( &npcVel, NULL );
  5677. CBaseEntity *pOtherEntity = NULL;
  5678. bool createSolver = false;
  5679. float solverTime = 0.0f;
  5680. while ( pSnapshot->IsValid() )
  5681. {
  5682. IPhysicsObject *pOther = pSnapshot->GetObject(1);
  5683. pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
  5684. if ( pOtherEntity && pGroundEntity != pOtherEntity )
  5685. {
  5686. float otherMass = PhysGetEntityMass(pOtherEntity);
  5687. if ( pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() &&
  5688. otherMass < VPHYSICS_LARGE_OBJECT_MASS && !pOtherEntity->GetServerVehicle() )
  5689. {
  5690. m_bCheckContacts = true;
  5691. Vector vel, point;
  5692. pOther->GetVelocity( &vel, NULL );
  5693. pSnapshot->GetContactPoint( point );
  5694. // compare the relative velocity
  5695. vel -= npcVel;
  5696. // slow moving object probably won't clear itself.
  5697. // Either set ignore, or disable collisions entirely
  5698. if ( vel.LengthSqr() < 5.0f*5.0f )
  5699. {
  5700. float topdist = fabs(point.z-heightCheck);
  5701. // 4 seconds to ignore this for nav
  5702. solverTime = 4.0f;
  5703. if ( topdist < 2.0f )
  5704. {
  5705. // Resting on my head so disable collisions for a bit
  5706. solverTime = 0.5f; // UNDONE: Tune
  5707. if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  5708. {
  5709. // player is being a monkey
  5710. solverTime = 0.25f;
  5711. }
  5712. //Msg("Dropping %s from %s\n", pOtherEntity->GetClassname(), GetClassname() );
  5713. Assert( !NPCPhysics_SolverExists(this, pOtherEntity) );
  5714. createSolver = true;
  5715. break;
  5716. }
  5717. }
  5718. }
  5719. }
  5720. pSnapshot->NextFrictionData();
  5721. }
  5722. pPhysics->DestroyFrictionSnapshot( pSnapshot );
  5723. if ( createSolver )
  5724. {
  5725. // turn collisions back on once we've been separated for enough time
  5726. NPCPhysics_CreateSolver( this, pOtherEntity, true, solverTime );
  5727. pPhysics->RecheckContactPoints();
  5728. }
  5729. }
  5730. }
  5731. void CAI_BaseNPC::StartTouch( CBaseEntity *pOther )
  5732. {
  5733. BaseClass::StartTouch(pOther);
  5734. if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
  5735. {
  5736. m_bCheckContacts = true;
  5737. }
  5738. }
  5739. //-----------------------------------------------------------------------------
  5740. // Purpose: To be called instead of UTIL_SetSize, so pathfinding hull
  5741. // and actual hull agree
  5742. // Input :
  5743. // Output :
  5744. //-----------------------------------------------------------------------------
  5745. void CAI_BaseNPC::SetHullSizeNormal( bool force )
  5746. {
  5747. if ( m_fIsUsingSmallHull || force )
  5748. {
  5749. // Find out what the height difference will be between the versions and adjust our bbox accordingly to keep us level
  5750. const float flScale = MIN( 1.0f, GetModelHierarchyScale() ); // NOTE: Cannot scale NPC bounding box up, as pathfinding will fail (hull needs to match the traces used for the node network)
  5751. Vector vecMins = ( GetHullMins() * flScale );
  5752. Vector vecMaxs = ( GetHullMaxs() * flScale );
  5753. UTIL_SetSize( this, vecMins, vecMaxs );
  5754. m_fIsUsingSmallHull = false;
  5755. if ( VPhysicsGetObject() )
  5756. {
  5757. SetupVPhysicsHull();
  5758. }
  5759. }
  5760. }
  5761. //-----------------------------------------------------------------------------
  5762. // Purpose: To be called instead of UTIL_SetSize, so pathfinding hull
  5763. // and actual hull agree
  5764. // Input :
  5765. // Output :
  5766. //-----------------------------------------------------------------------------
  5767. bool CAI_BaseNPC::SetHullSizeSmall( bool force )
  5768. {
  5769. if ( !m_fIsUsingSmallHull || force )
  5770. {
  5771. UTIL_SetSize(this, NAI_Hull::SmallMins(GetHullType()),NAI_Hull::SmallMaxs(GetHullType()));
  5772. m_fIsUsingSmallHull = true;
  5773. if ( VPhysicsGetObject() )
  5774. {
  5775. SetupVPhysicsHull();
  5776. }
  5777. }
  5778. return true;
  5779. }
  5780. //-----------------------------------------------------------------------------
  5781. // Checks to see that the nav hull is valid for the NPC
  5782. //-----------------------------------------------------------------------------
  5783. bool CAI_BaseNPC::IsNavHullValid() const
  5784. {
  5785. Assert( GetSolid() != SOLID_BSP );
  5786. Vector hullMin = GetHullMins();
  5787. Vector hullMax = GetHullMaxs();
  5788. Vector vecMins, vecMaxs;
  5789. if ( GetSolid() == SOLID_BBOX )
  5790. {
  5791. vecMins = WorldAlignMins();
  5792. vecMaxs = WorldAlignMaxs();
  5793. }
  5794. else if ( GetSolid() == SOLID_VPHYSICS )
  5795. {
  5796. Assert( VPhysicsGetObject() );
  5797. const CPhysCollide *pPhysCollide = VPhysicsGetObject()->GetCollide();
  5798. physcollision->CollideGetAABB( &vecMins, &vecMaxs, pPhysCollide, GetAbsOrigin(), GetAbsAngles() );
  5799. vecMins -= GetAbsOrigin();
  5800. vecMaxs -= GetAbsOrigin();
  5801. }
  5802. else
  5803. {
  5804. vecMins = hullMin;
  5805. vecMaxs = hullMax;
  5806. }
  5807. if ( (hullMin.x > vecMins.x) || (hullMax.x < vecMaxs.x) ||
  5808. (hullMin.y > vecMins.y) || (hullMax.y < vecMaxs.y) ||
  5809. (hullMin.z > vecMins.z) || (hullMax.z < vecMaxs.z) )
  5810. {
  5811. return false;
  5812. }
  5813. return true;
  5814. }
  5815. //=========================================================
  5816. // NPCInit - after a npc is spawned, it needs to
  5817. // be dropped into the world, checked for mobility problems,
  5818. // and put on the proper path, if any. This function does
  5819. // all of those things after the npc spawns. Any
  5820. // initialization that should take place for all npcs
  5821. // goes here.
  5822. //=========================================================
  5823. void CAI_BaseNPC::NPCInit ( void )
  5824. {
  5825. if (!g_pGameRules->FAllowNPCs())
  5826. {
  5827. UTIL_Remove( this );
  5828. return;
  5829. }
  5830. if( IsWaitingToRappel() )
  5831. {
  5832. // If this guy's supposed to rappel, keep him from
  5833. // falling to the ground when he spawns.
  5834. AddFlag( FL_FLY );
  5835. }
  5836. #ifdef _DEBUG
  5837. // Make sure that the bounding box is appropriate for the hull size...
  5838. // FIXME: We can't test vphysics objects because NPCInit occurs before VPhysics is set up
  5839. if ( GetSolid() != SOLID_VPHYSICS && !IsSolidFlagSet(FSOLID_NOT_SOLID) )
  5840. {
  5841. if ( !IsNavHullValid() )
  5842. {
  5843. Warning("NPC Entity %s (%d) has a bounding box which extends outside its nav box!\n",
  5844. STRING(m_iClassname), entindex() );
  5845. }
  5846. }
  5847. #endif
  5848. // Set fields common to all npcs
  5849. AddFlag( FL_AIMTARGET | FL_NPC );
  5850. AddSolidFlags( FSOLID_NOT_STANDABLE );
  5851. m_flOriginalYaw = GetAbsAngles().y;
  5852. m_bClientSideRagdoll = false;
  5853. SetBlocksLOS( false );
  5854. SetGravity(1.0); // Don't change
  5855. m_takedamage = DAMAGE_YES;
  5856. GetMotor()->SetIdealYaw( GetLocalAngles().y );
  5857. m_iMaxHealth = m_iHealth;
  5858. m_lifeState = LIFE_ALIVE;
  5859. SetIdealState( NPC_STATE_IDLE );// Assume npc will be idle, until proven otherwise
  5860. SetIdealActivity( ACT_IDLE );
  5861. SetActivity( ACT_IDLE );
  5862. ClearCommandGoal();
  5863. ClearSchedule( "Initializing NPC" );
  5864. GetNavigator()->ClearGoal();
  5865. InitBoneControllers( ); // FIX: should be done in Spawn
  5866. if ( GetModelPtr() )
  5867. {
  5868. ResetActivityIndexes();
  5869. ResetEventIndexes();
  5870. }
  5871. SetHintNode( NULL );
  5872. m_afMemory = MEMORY_CLEAR;
  5873. SetEnemy( NULL );
  5874. m_flDistTooFar = 1024.0;
  5875. SetDistLook( 2048.0 );
  5876. if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) )
  5877. {
  5878. m_flDistTooFar = 1e9f;
  5879. SetDistLook( 6000.0 );
  5880. }
  5881. // Clear conditions
  5882. m_Conditions.ClearAll();
  5883. // set eye position
  5884. SetDefaultEyeOffset();
  5885. // Only give weapon of allowed to have one
  5886. if (CapabilitiesGet() & bits_CAP_USE_WEAPONS)
  5887. { // Does this npc spawn with a weapon
  5888. if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0"))
  5889. {
  5890. CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(m_spawnEquipment) );
  5891. if ( pWeapon )
  5892. {
  5893. pWeapon->MakeWeaponNameFromEntity( this );
  5894. if ( GetEffects() & EF_NOSHADOW )
  5895. {
  5896. // BUGBUG: if this NPC drops this weapon it will forevermore have no shadow
  5897. pWeapon->AddEffects( EF_NOSHADOW );
  5898. }
  5899. Weapon_Equip( pWeapon );
  5900. }
  5901. }
  5902. }
  5903. // Robin: Removed this, since it stomps the weapon's settings, and it's stomped
  5904. // by OnUpdateShotRegulator() as soon as they finish firing the first time.
  5905. //GetShotRegulator()->SetParameters( 2, 6, 0.3f, 0.8f );
  5906. SetUse ( &CAI_BaseNPC::NPCUse );
  5907. // NOTE: Can't call NPC Init Think directly... logic changed about
  5908. // what time it is when worldspawn happens..
  5909. // We must put off the rest of our initialization
  5910. // until we're sure everything else has had a chance to spawn. Otherwise
  5911. // we may try to reference entities that haven't spawned yet.(sjb)
  5912. SetThink( &CAI_BaseNPC::NPCInitThink );
  5913. SetNextThink( gpGlobals->curtime + 0.01f );
  5914. ForceGatherConditions();
  5915. // HACKHACK: set up a pre idle animation
  5916. // NOTE: Must do this before CreateVPhysics() so bone followers have the correct initial positions.
  5917. if ( HasSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT ) )
  5918. {
  5919. const char *pStartSequence = CAI_ScriptedSequence::GetSpawnPreIdleSequenceForScript( this );
  5920. if ( pStartSequence )
  5921. {
  5922. SetSequence( LookupSequence( pStartSequence ) );
  5923. }
  5924. }
  5925. CreateVPhysics();
  5926. if ( HasSpawnFlags( SF_NPC_START_EFFICIENT ) )
  5927. {
  5928. SetEfficiency( AIE_EFFICIENT );
  5929. }
  5930. m_bFadeCorpse = ShouldFadeOnDeath();
  5931. m_GiveUpOnDeadEnemyTimer.Set( 0.75, 2.0 );
  5932. m_flTimeLastMovement = FLT_MAX;
  5933. m_flIgnoreDangerSoundsUntil = 0;
  5934. SetDeathPose( ACT_INVALID );
  5935. SetDeathPoseFrame( 0 );
  5936. m_EnemiesSerialNumber = -1;
  5937. for( int i = 0; i < m_Behaviors.Count(); i++ )
  5938. {
  5939. m_Behaviors[i]->Spawn();
  5940. }
  5941. }
  5942. //-----------------------------------------------------------------------------
  5943. bool CAI_BaseNPC::CreateVPhysics()
  5944. {
  5945. if ( IsAlive() && !VPhysicsGetObject() )
  5946. {
  5947. SetupVPhysicsHull();
  5948. }
  5949. return true;
  5950. }
  5951. //-----------------------------------------------------------------------------
  5952. // Set up the shot regulator based on the equipped weapon
  5953. //-----------------------------------------------------------------------------
  5954. void CAI_BaseNPC::OnUpdateShotRegulator( )
  5955. {
  5956. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  5957. if ( !pWeapon )
  5958. return;
  5959. // Default values
  5960. m_ShotRegulator.SetBurstInterval( pWeapon->GetFireRate(), pWeapon->GetFireRate() );
  5961. m_ShotRegulator.SetBurstShotCountRange( pWeapon->GetMinBurst(), pWeapon->GetMaxBurst() );
  5962. m_ShotRegulator.SetRestInterval( pWeapon->GetMinRestTime(), pWeapon->GetMaxRestTime() );
  5963. // Let the behavior have a whack at it.
  5964. if ( GetPrimaryBehavior() )
  5965. {
  5966. GetPrimaryBehavior()->OnUpdateShotRegulator();
  5967. }
  5968. }
  5969. //-----------------------------------------------------------------------------
  5970. // Set up the shot regulator based on the equipped weapon
  5971. //-----------------------------------------------------------------------------
  5972. void CAI_BaseNPC::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
  5973. {
  5974. BaseClass::OnChangeActiveWeapon( pOldWeapon, pNewWeapon );
  5975. // Shot regulator code
  5976. if ( pNewWeapon )
  5977. {
  5978. OnUpdateShotRegulator();
  5979. m_ShotRegulator.Reset( true );
  5980. }
  5981. }
  5982. //-----------------------------------------------------------------------------
  5983. // Purpose: Tests to see if NPC can holster their weapon (if animation exists to holster weapon)
  5984. // Output : true if holster weapon animation exists
  5985. //-----------------------------------------------------------------------------
  5986. bool CAI_BaseNPC::CanHolsterWeapon( void )
  5987. {
  5988. int seq = SelectWeightedSequence( ACT_DISARM );
  5989. return (seq >= 0);
  5990. }
  5991. //-----------------------------------------------------------------------------
  5992. // Purpose:
  5993. //-----------------------------------------------------------------------------
  5994. int CAI_BaseNPC::HolsterWeapon( void )
  5995. {
  5996. if ( IsWeaponHolstered() )
  5997. return -1;
  5998. int iHolsterGesture = FindGestureLayer( ACT_DISARM );
  5999. if ( iHolsterGesture != -1 )
  6000. return iHolsterGesture;
  6001. int iLayer = AddGesture( ACT_DISARM, true );
  6002. //iLayer = AddGesture( ACT_GESTURE_DISARM, true );
  6003. if (iLayer != -1)
  6004. {
  6005. // Prevent firing during the holster / unholster
  6006. float flDuration = GetLayerDuration( iLayer );
  6007. m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 );
  6008. if( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED )
  6009. {
  6010. m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING_DESTROY;
  6011. }
  6012. else
  6013. {
  6014. m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING;
  6015. }
  6016. // Make sure we don't try to reload while we're holstering
  6017. ClearCondition(COND_LOW_PRIMARY_AMMO);
  6018. ClearCondition(COND_NO_PRIMARY_AMMO);
  6019. ClearCondition(COND_NO_SECONDARY_AMMO);
  6020. }
  6021. return iLayer;
  6022. }
  6023. //-----------------------------------------------------------------------------
  6024. // Purpose:
  6025. //-----------------------------------------------------------------------------
  6026. int CAI_BaseNPC::UnholsterWeapon( void )
  6027. {
  6028. if ( !IsWeaponHolstered() )
  6029. return -1;
  6030. int iHolsterGesture = FindGestureLayer( ACT_ARM );
  6031. if ( iHolsterGesture != -1 )
  6032. return iHolsterGesture;
  6033. // Deploy the first weapon you can find
  6034. for (int i = 0; i < WeaponCount(); i++)
  6035. {
  6036. if ( GetWeapon( i ))
  6037. {
  6038. SetActiveWeapon( GetWeapon(i) );
  6039. int iLayer = AddGesture( ACT_ARM, true );
  6040. //iLayer = AddGesture( ACT_GESTURE_ARM, true );
  6041. if (iLayer != -1)
  6042. {
  6043. // Prevent firing during the holster / unholster
  6044. float flDuration = GetLayerDuration( iLayer );
  6045. m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 );
  6046. m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING;
  6047. }
  6048. // Refill the clip
  6049. if ( GetActiveWeapon()->UsesClipsForAmmo1() )
  6050. {
  6051. GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
  6052. }
  6053. // Make sure we don't try to reload while we're unholstering
  6054. ClearCondition(COND_LOW_PRIMARY_AMMO);
  6055. ClearCondition(COND_NO_PRIMARY_AMMO);
  6056. ClearCondition(COND_NO_SECONDARY_AMMO);
  6057. return iLayer;
  6058. }
  6059. }
  6060. return -1;
  6061. }
  6062. //-----------------------------------------------------------------------------
  6063. // Purpose:
  6064. //-----------------------------------------------------------------------------
  6065. void CAI_BaseNPC::InputHolsterWeapon( inputdata_t &inputdata )
  6066. {
  6067. m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED;
  6068. }
  6069. //-----------------------------------------------------------------------------
  6070. // Purpose:
  6071. //-----------------------------------------------------------------------------
  6072. void CAI_BaseNPC::InputHolsterAndDestroyWeapon( inputdata_t &inputdata )
  6073. {
  6074. m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED_DESTROYED;
  6075. }
  6076. //-----------------------------------------------------------------------------
  6077. // Purpose:
  6078. //-----------------------------------------------------------------------------
  6079. void CAI_BaseNPC::InputUnholsterWeapon( inputdata_t &inputdata )
  6080. {
  6081. m_iDesiredWeaponState = DESIREDWEAPONSTATE_UNHOLSTERED;
  6082. }
  6083. //-----------------------------------------------------------------------------
  6084. // Purpose:
  6085. //-----------------------------------------------------------------------------
  6086. bool CAI_BaseNPC::IsWeaponHolstered( void )
  6087. {
  6088. if( !GetActiveWeapon() )
  6089. return true;
  6090. if( GetActiveWeapon()->IsEffectActive(EF_NODRAW) )
  6091. return true;
  6092. return false;
  6093. }
  6094. //-----------------------------------------------------------------------------
  6095. // Purpose:
  6096. //-----------------------------------------------------------------------------
  6097. bool CAI_BaseNPC::IsWeaponStateChanging( void )
  6098. {
  6099. return ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING || m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY );
  6100. }
  6101. //-----------------------------------------------------------------------------
  6102. // Set up the shot regulator based on the equipped weapon
  6103. //-----------------------------------------------------------------------------
  6104. void CAI_BaseNPC::OnRangeAttack1()
  6105. {
  6106. SetLastAttackTime( gpGlobals->curtime );
  6107. // Houston, there is a problem!
  6108. AssertOnce( GetShotRegulator()->ShouldShoot() );
  6109. m_ShotRegulator.OnFiredWeapon();
  6110. if ( m_ShotRegulator.IsInRestInterval() )
  6111. {
  6112. OnUpdateShotRegulator();
  6113. }
  6114. SetNextAttack( m_ShotRegulator.NextShotTime() );
  6115. }
  6116. //-----------------------------------------------------------------------------
  6117. // Purpose: Initialze the relationship table from the keyvalues
  6118. // Input :
  6119. // Output :
  6120. //-----------------------------------------------------------------------------
  6121. void CAI_BaseNPC::InitRelationshipTable(void)
  6122. {
  6123. AddRelationship( STRING( m_RelationshipString ), NULL );
  6124. }
  6125. //-----------------------------------------------------------------------------
  6126. // Purpose:
  6127. //-----------------------------------------------------------------------------
  6128. void CAI_BaseNPC::AddRelationship( const char *pszRelationship, CBaseEntity *pActivator )
  6129. {
  6130. // Parse the keyvalue data
  6131. char parseString[1000];
  6132. Q_strncpy(parseString, pszRelationship, sizeof(parseString));
  6133. // Look for an entity string
  6134. char *entityString = strtok(parseString," ");
  6135. while (entityString)
  6136. {
  6137. // Get the disposition
  6138. char *dispositionString = strtok(NULL," ");
  6139. Disposition_t disposition = D_NU;
  6140. if ( dispositionString )
  6141. {
  6142. if (!stricmp(dispositionString,"D_HT"))
  6143. {
  6144. disposition = D_HT;
  6145. }
  6146. else if (!stricmp(dispositionString,"D_FR"))
  6147. {
  6148. disposition = D_FR;
  6149. }
  6150. else if (!stricmp(dispositionString,"D_LI"))
  6151. {
  6152. disposition = D_LI;
  6153. }
  6154. else if (!stricmp(dispositionString,"D_NU"))
  6155. {
  6156. disposition = D_NU;
  6157. }
  6158. else
  6159. {
  6160. disposition = D_NU;
  6161. Warning( "***ERROR***\nBad relationship type (%s) to unknown entity (%s)!\n", dispositionString,entityString );
  6162. Assert( 0 );
  6163. return;
  6164. }
  6165. }
  6166. else
  6167. {
  6168. Warning("Can't parse relationship info (%s) - Expecting 'name [D_HT, D_FR, D_LI, D_NU] [1-99]'\n", pszRelationship );
  6169. Assert(0);
  6170. return;
  6171. }
  6172. // Get the priority
  6173. char *priorityString = strtok(NULL," ");
  6174. int priority = ( priorityString ) ? atoi(priorityString) : DEF_RELATIONSHIP_PRIORITY;
  6175. bool bFoundEntity = false;
  6176. // Try to get pointer to an entity of this name
  6177. CBaseEntity *entity = gEntList.FindEntityByName( NULL, entityString );
  6178. while( entity )
  6179. {
  6180. // make sure you catch all entities of this name.
  6181. bFoundEntity = true;
  6182. AddEntityRelationship(entity, disposition, priority );
  6183. entity = gEntList.FindEntityByName( entity, entityString );
  6184. }
  6185. if( !bFoundEntity )
  6186. {
  6187. // Need special condition for player as we can only have one
  6188. if (!stricmp("player", entityString) || !stricmp("!player", entityString))
  6189. {
  6190. AddClassRelationship( CLASS_PLAYER, disposition, priority );
  6191. }
  6192. // Otherwise try to create one too see if a valid classname and get class type
  6193. else
  6194. {
  6195. // HACKHACK:
  6196. CBaseEntity *pEntity = CanCreateEntityClass( entityString ) ? CreateEntityByName( entityString ) : NULL;
  6197. if (pEntity)
  6198. {
  6199. AddClassRelationship( pEntity->Classify(), disposition, priority );
  6200. UTIL_RemoveImmediate(pEntity);
  6201. }
  6202. else
  6203. {
  6204. DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString );
  6205. }
  6206. }
  6207. }
  6208. // Check for another entity in the list
  6209. entityString = strtok(NULL," ");
  6210. }
  6211. }
  6212. //-----------------------------------------------------------------------------
  6213. //-----------------------------------------------------------------------------
  6214. void CAI_BaseNPC::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
  6215. {
  6216. #if 0
  6217. ForceGatherConditions();
  6218. #endif
  6219. BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
  6220. }
  6221. //-----------------------------------------------------------------------------
  6222. //-----------------------------------------------------------------------------
  6223. void CAI_BaseNPC::AddClassRelationship( Class_T nClass, Disposition_t nDisposition, int nPriority )
  6224. {
  6225. #if 0
  6226. ForceGatherConditions();
  6227. #endif
  6228. BaseClass::AddClassRelationship( nClass, nDisposition, nPriority );
  6229. }
  6230. //=========================================================
  6231. // NPCInitThink - Calls StartNPC. Startnpc is
  6232. // virtual, but this function cannot be
  6233. //=========================================================
  6234. void CAI_BaseNPC::NPCInitThink ( void )
  6235. {
  6236. // Initialize the relationship table
  6237. InitRelationshipTable();
  6238. StartNPC();
  6239. PostNPCInit();
  6240. if( GetSleepState() == AISS_AUTO_PVS )
  6241. {
  6242. // This code is a bit wonky, but it makes it easier for level designers to
  6243. // select this option in Hammer. So we set a sleep flag to indicate the choice,
  6244. // and then set the sleep state to awake (normal)
  6245. AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS );
  6246. SetSleepState( AISS_AWAKE );
  6247. }
  6248. if( GetSleepState() == AISS_AUTO_PVS_AFTER_PVS )
  6249. {
  6250. AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS );
  6251. SetSleepState( AISS_AWAKE );
  6252. }
  6253. if ( GetSleepState() > AISS_AWAKE )
  6254. {
  6255. Sleep();
  6256. }
  6257. m_flLastRealThinkTime = gpGlobals->curtime;
  6258. }
  6259. //=========================================================
  6260. // StartNPC - final bit of initization before a npc
  6261. // is turned over to the AI.
  6262. //=========================================================
  6263. void CAI_BaseNPC::StartNPC( void )
  6264. {
  6265. // Raise npc off the floor one unit, then drop to floor
  6266. if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) &&
  6267. !(CapabilitiesGet() & bits_CAP_MOVE_FLY) &&
  6268. !HasSpawnFlags( SF_NPC_FALL_TO_GROUND ) && !IsWaitingToRappel() && !GetMoveParent() )
  6269. {
  6270. Vector origin = GetLocalOrigin();
  6271. if (!GetMoveProbe()->FloorPoint( origin + Vector(0, 0, 0.1), GetAITraceMask(), 0, -2048, &origin ))
  6272. {
  6273. Warning( "NPC %s stuck in wall--level design error at (%.2f %.2f %.2f)\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  6274. if ( g_pDeveloper->GetInt() > 1 )
  6275. {
  6276. m_debugOverlays |= OVERLAY_BBOX_BIT;
  6277. }
  6278. }
  6279. SetLocalOrigin( origin );
  6280. }
  6281. else
  6282. {
  6283. SetGroundEntity( NULL );
  6284. }
  6285. if ( m_target != NULL_STRING )// this npc has a target
  6286. {
  6287. // Find the npc's initial target entity, stash it
  6288. SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) );
  6289. if ( !GetGoalEnt() )
  6290. {
  6291. Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target));
  6292. }
  6293. else
  6294. {
  6295. StartTargetHandling( GetGoalEnt() );
  6296. }
  6297. }
  6298. //SetState ( m_IdealNPCState );
  6299. //SetActivity ( m_IdealActivity );
  6300. InitSquad();
  6301. //---------------------------------
  6302. //
  6303. // Spread think times of simultaneously spawned NPCs so that they don't all happen at the same time
  6304. //
  6305. // Think distribution based on spawn order is:
  6306. //
  6307. // Tick offset Think time Spawn order
  6308. // 0 0 1
  6309. // 1 0.015 13
  6310. // 2 0.03 5
  6311. // 3 0.045 9
  6312. // 4 0.06 18
  6313. // 5 0.075 3
  6314. // 6 0.09 15
  6315. // 7 0.105 11
  6316. // 8 0.12 7
  6317. // 9 0.135 17
  6318. // 10 0.15 2
  6319. // 11 0.165 14
  6320. // 12 0.18 6
  6321. // 13 0.195 19
  6322. // 14 0.21 10
  6323. // 15 0.225 4
  6324. // 16 0.24 16
  6325. // 17 0.255 12
  6326. // 18 0.27 8
  6327. // 19 0.285 20
  6328. // If this NPC is spawning late in the game, just push through the rest of the initialization
  6329. // start thinking right now. Some spread is added to handle triggered spawns that bring
  6330. // a bunch of NPCs into the level
  6331. SetThink ( &CAI_BaseNPC::CallNPCThink );
  6332. if ( gm_flTimeLastSpawn != gpGlobals->curtime )
  6333. {
  6334. gm_nSpawnedThisFrame = 0;
  6335. gm_flTimeLastSpawn = gpGlobals->curtime;
  6336. }
  6337. static const float nextThinkTimes[20] =
  6338. {
  6339. .0, .150, .075, .225, .030, .180, .120, .270, .045, .210, .105, .255, .015, .165, .090, .240, .135, .060, .195, .285
  6340. };
  6341. SetNextThink( gpGlobals->curtime + nextThinkTimes[gm_nSpawnedThisFrame % 20] );
  6342. gm_nSpawnedThisFrame++;
  6343. //---------------------------------
  6344. m_ScriptArrivalActivity = AIN_DEF_ACTIVITY;
  6345. m_strScriptArrivalSequence = NULL_STRING;
  6346. if ( HasSpawnFlags(SF_NPC_WAIT_FOR_SCRIPT) )
  6347. {
  6348. SetState( NPC_STATE_IDLE );
  6349. m_Activity = m_IdealActivity;
  6350. m_nIdealSequence = GetSequence();
  6351. SetSchedule( SCHED_WAIT_FOR_SCRIPT );
  6352. }
  6353. }
  6354. //-----------------------------------------------------------------------------
  6355. void CAI_BaseNPC::StartTargetHandling( CBaseEntity *pTargetEnt )
  6356. {
  6357. // set the npc up to walk a path corner path.
  6358. // !!!BUGBUG - this is a minor bit of a hack.
  6359. // JAYJAY
  6360. // NPC will start turning towards his destination
  6361. bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY);
  6362. AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pTargetEnt->GetAbsOrigin(),
  6363. bIsFlying ? ACT_FLY : ACT_WALK,
  6364. AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
  6365. SetState( NPC_STATE_IDLE );
  6366. SetSchedule( SCHED_IDLE_WALK );
  6367. if ( !GetNavigator()->SetGoal( goal ) )
  6368. {
  6369. DevWarning( 2, "Can't Create Route!\n" );
  6370. }
  6371. }
  6372. //-----------------------------------------------------------------------------
  6373. // Purpose: Connect my memory to the squad's
  6374. //-----------------------------------------------------------------------------
  6375. bool CAI_BaseNPC::InitSquad( void )
  6376. {
  6377. // -------------------------------------------------------
  6378. // If I form squads add me to a squad
  6379. // -------------------------------------------------------
  6380. if (!m_pSquad && ( CapabilitiesGet() & bits_CAP_SQUAD ))
  6381. {
  6382. if ( !m_SquadName )
  6383. {
  6384. DevMsg(2, "Found %s that isn't in a squad\n",GetClassname());
  6385. }
  6386. else
  6387. {
  6388. m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName);
  6389. }
  6390. }
  6391. return ( m_pSquad != NULL );
  6392. }
  6393. //-----------------------------------------------------------------------------
  6394. // Purpose: Get the memory for this NPC
  6395. //-----------------------------------------------------------------------------
  6396. CAI_Enemies *CAI_BaseNPC::GetEnemies( void )
  6397. {
  6398. return m_pEnemies;
  6399. }
  6400. //-----------------------------------------------------------------------------
  6401. // Purpose: Remove this NPC's memory
  6402. //-----------------------------------------------------------------------------
  6403. void CAI_BaseNPC::RemoveMemory( void )
  6404. {
  6405. delete m_pEnemies;
  6406. }
  6407. //-----------------------------------------------------------------------------
  6408. // Purpose: Change faction NPC belongs to
  6409. //-----------------------------------------------------------------------------
  6410. void CAI_BaseNPC::ChangeFaction( int nNewFaction )
  6411. {
  6412. BaseClass::ChangeFaction( nNewFaction );
  6413. // Remove anyone who is no longer an enemy
  6414. AIEnemiesIter_t iter;
  6415. AI_EnemyInfo_t *pNextEMemory;
  6416. for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = pNextEMemory )
  6417. {
  6418. CBaseEntity *pEnemy = pEMemory->hEnemy;
  6419. pNextEMemory = GetEnemies()->GetNext(&iter);
  6420. if ( pEnemy && ( IRelationType( pEnemy ) == D_LI ) )
  6421. GetEnemies()->ClearMemory( pEnemy );
  6422. }
  6423. }
  6424. //-----------------------------------------------------------------------------
  6425. // Purpose:
  6426. //-----------------------------------------------------------------------------
  6427. void CAI_BaseNPC::TaskComplete( bool fIgnoreSetFailedCondition )
  6428. {
  6429. EndTaskOverlay();
  6430. // Handy thing to use for debugging
  6431. //if (IsCurSchedule(SCHED_PUT_HERE) &&
  6432. // GetTask()->iTask == TASK_PUT_HERE)
  6433. //{
  6434. // int put_breakpoint_here = 5;
  6435. //}
  6436. if ( fIgnoreSetFailedCondition || !HasCondition(COND_TASK_FAILED) )
  6437. {
  6438. SetTaskStatus( TASKSTATUS_COMPLETE );
  6439. }
  6440. }
  6441. void CAI_BaseNPC::TaskMovementComplete( void )
  6442. {
  6443. switch( GetTaskStatus() )
  6444. {
  6445. case TASKSTATUS_NEW:
  6446. case TASKSTATUS_RUN_MOVE_AND_TASK:
  6447. SetTaskStatus( TASKSTATUS_RUN_TASK );
  6448. break;
  6449. case TASKSTATUS_RUN_MOVE:
  6450. TaskComplete();
  6451. break;
  6452. case TASKSTATUS_RUN_TASK:
  6453. // FIXME: find out how to safely restart movement
  6454. //Warning( "Movement completed twice!\n" );
  6455. //Assert( 0 );
  6456. break;
  6457. case TASKSTATUS_COMPLETE:
  6458. break;
  6459. }
  6460. // JAY: Put this back in.
  6461. // UNDONE: Figure out how much of the timestep was consumed by movement
  6462. // this frame and restart the movement/schedule engine if necessary
  6463. if ( m_scriptState != SCRIPT_CUSTOM_MOVE_TO_MARK )
  6464. {
  6465. SetIdealActivity( GetStoppedActivity() );
  6466. }
  6467. // Advance past the last node (in case there is some event at this node)
  6468. if ( GetNavigator()->IsGoalActive() )
  6469. {
  6470. GetNavigator()->AdvancePath();
  6471. }
  6472. // Now clear the path, it's done.
  6473. GetNavigator()->ClearGoal();
  6474. OnMovementComplete();
  6475. }
  6476. int CAI_BaseNPC::TaskIsRunning( void )
  6477. {
  6478. if ( GetTaskStatus() != TASKSTATUS_COMPLETE &&
  6479. GetTaskStatus() != TASKSTATUS_RUN_MOVE )
  6480. return 1;
  6481. return 0;
  6482. }
  6483. //-----------------------------------------------------------------------------
  6484. // Purpose:
  6485. // Input :
  6486. // Output :
  6487. //-----------------------------------------------------------------------------
  6488. void CAI_BaseNPC::TaskFail( AI_TaskFailureCode_t code )
  6489. {
  6490. EndTaskOverlay();
  6491. // Handy tool for debugging
  6492. //if (IsCurSchedule(SCHED_PUT_NAME_HERE))
  6493. //{
  6494. // int put_breakpoint_here = 5;
  6495. //}
  6496. // If in developer mode save the fail text for debug output
  6497. if (g_pDeveloper->GetInt())
  6498. {
  6499. m_failText = TaskFailureToString( code );
  6500. m_interuptSchedule = NULL;
  6501. m_failedSchedule = GetCurSchedule();
  6502. if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
  6503. {
  6504. DevMsg(this, AIMF_IGNORE_SELECTED, " TaskFail -> %s\n", m_failText );
  6505. }
  6506. ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): TaskFail -> %s\n", GetDebugName(), entindex(), m_failText ) );
  6507. //AddTimedOverlay( fail_text, 5);
  6508. }
  6509. m_ScheduleState.taskFailureCode = code;
  6510. SetCondition(COND_TASK_FAILED);
  6511. Forget( bits_MEMORY_TURNING );
  6512. }
  6513. //------------------------------------------------------------------------------
  6514. // Purpose : Remember that this entity wasn't reachable
  6515. // Input :
  6516. // Output :
  6517. //------------------------------------------------------------------------------
  6518. void CAI_BaseNPC::RememberUnreachable(CBaseEntity *pEntity, float duration )
  6519. {
  6520. if ( pEntity == GetEnemy() )
  6521. {
  6522. ForceChooseNewEnemy();
  6523. }
  6524. const float NPC_UNREACHABLE_TIMEOUT = ( duration > 0.0 ) ? duration : 3;
  6525. // Only add to list if not already on it
  6526. for (int i=m_UnreachableEnts.Count()-1;i>=0;i--)
  6527. {
  6528. // If record already exists just update mark time
  6529. if (pEntity == m_UnreachableEnts[i].hUnreachableEnt)
  6530. {
  6531. m_UnreachableEnts[i].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT;
  6532. m_UnreachableEnts[i].vLocationWhenUnreachable = pEntity->GetAbsOrigin();
  6533. return;
  6534. }
  6535. }
  6536. // Add new unreachabe entity to list
  6537. int nNewIndex = m_UnreachableEnts.AddToTail();
  6538. m_UnreachableEnts[nNewIndex].hUnreachableEnt = pEntity;
  6539. m_UnreachableEnts[nNewIndex].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT;
  6540. m_UnreachableEnts[nNewIndex].vLocationWhenUnreachable = pEntity->GetAbsOrigin();
  6541. }
  6542. //------------------------------------------------------------------------------
  6543. // Purpose : Returns true is entity was remembered as unreachable.
  6544. // After a time delay reachability is checked
  6545. // Input :
  6546. // Output :
  6547. //------------------------------------------------------------------------------
  6548. bool CAI_BaseNPC::IsUnreachable(CBaseEntity *pEntity)
  6549. {
  6550. float UNREACHABLE_DIST_TOLERANCE_SQ = (120*120);
  6551. // Note that it's ok to remove elements while I'm iterating
  6552. // as long as I iterate backwards and remove them using FastRemove
  6553. for (int i=m_UnreachableEnts.Count()-1;i>=0;i--)
  6554. {
  6555. // Remove any dead elements
  6556. if (m_UnreachableEnts[i].hUnreachableEnt == NULL)
  6557. {
  6558. m_UnreachableEnts.FastRemove(i);
  6559. }
  6560. else if (pEntity == m_UnreachableEnts[i].hUnreachableEnt)
  6561. {
  6562. // Test for reachablility on any elements that have timed out
  6563. if ( gpGlobals->curtime > m_UnreachableEnts[i].fExpireTime ||
  6564. pEntity->GetAbsOrigin().DistToSqr(m_UnreachableEnts[i].vLocationWhenUnreachable) > UNREACHABLE_DIST_TOLERANCE_SQ)
  6565. {
  6566. m_UnreachableEnts.FastRemove(i);
  6567. return false;
  6568. }
  6569. return true;
  6570. }
  6571. }
  6572. return false;
  6573. }
  6574. bool CAI_BaseNPC::IsValidEnemy( CBaseEntity *pEnemy )
  6575. {
  6576. CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer();
  6577. if ( pEnemyNPC && pEnemyNPC->CanBeAnEnemyOf( this ) == false )
  6578. return false;
  6579. // Test our enemy filter
  6580. if ( m_hEnemyFilter.Get()!= NULL && m_hEnemyFilter->PassesFilter( this, pEnemy ) == false )
  6581. return false;
  6582. return true;
  6583. }
  6584. bool CAI_BaseNPC::CanBeAnEnemyOf( CBaseEntity *pEnemy )
  6585. {
  6586. if ( GetSleepState() > AISS_WAITING_FOR_THREAT )
  6587. return false;
  6588. return true;
  6589. }
  6590. //-----------------------------------------------------------------------------
  6591. // Purpose: Picks best enemy from my list of enemies
  6592. // Prefers reachable enemies over enemies that are unreachable,
  6593. // regardless of priority. For enemies that are both reachable or
  6594. // unreachable picks by priority. If priority is the same, picks
  6595. // by distance.
  6596. // Input :
  6597. // Output :
  6598. //-----------------------------------------------------------------------------
  6599. CBaseEntity *CAI_BaseNPC::BestEnemy( void )
  6600. {
  6601. AI_PROFILE_SCOPE( CAI_BaseNPC_BestEnemy );
  6602. // TODO - may want to consider distance, attack types, back turned, etc.
  6603. CBaseEntity* pBestEnemy = NULL;
  6604. int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE;// so first visible entity will become the closest.
  6605. int iBestPriority = -1000;
  6606. bool bBestUnreachable = true; // Forces initial check
  6607. ThreeState_t fBestSeen = TRS_NONE;
  6608. ThreeState_t fBestVisible = TRS_NONE;
  6609. int iDistSq;
  6610. bool bUnreachable = false;
  6611. AIEnemiesIter_t iter;
  6612. DbgEnemyMsg( this, "BestEnemy() {\n" );
  6613. for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  6614. {
  6615. CBaseEntity *pEnemy = pEMemory->hEnemy;
  6616. if (!pEnemy || !pEnemy->IsAlive())
  6617. {
  6618. if ( pEnemy )
  6619. DbgEnemyMsg( this, " %s rejected: dead\n", pEnemy->GetDebugName() );
  6620. continue;
  6621. }
  6622. if ( (pEnemy->GetFlags() & FL_NOTARGET) )
  6623. {
  6624. DbgEnemyMsg( this, " %s rejected: no target\n", pEnemy->GetDebugName() );
  6625. continue;
  6626. }
  6627. if ( m_bIgnoreUnseenEnemies )
  6628. {
  6629. const float TIME_CONSIDER_ENEMY_UNSEEN = .4;
  6630. if ( pEMemory->timeLastSeen < gpGlobals->curtime - TIME_CONSIDER_ENEMY_UNSEEN )
  6631. {
  6632. DbgEnemyMsg( this, " %s rejected: not seen and set to ignore unseen enemies\n", pEnemy->GetDebugName() );
  6633. continue;
  6634. }
  6635. }
  6636. // UNDONE: Move relationship checks into IsValidEnemy?
  6637. Disposition_t relation = IRelationType( pEnemy );
  6638. if ( (relation != D_HT && relation != D_FR) )
  6639. {
  6640. DbgEnemyMsg( this, " %s rejected: no hate/fear\n", pEnemy->GetDebugName() );
  6641. continue;
  6642. }
  6643. if ( m_flAcceptableTimeSeenEnemy > 0.0 && pEMemory->timeLastSeen < m_flAcceptableTimeSeenEnemy )
  6644. {
  6645. DbgEnemyMsg( this, " %s rejected: old\n", pEnemy->GetDebugName() );
  6646. continue;
  6647. }
  6648. if ( pEMemory->timeValidEnemy > gpGlobals->curtime )
  6649. {
  6650. DbgEnemyMsg( this, " %s rejected: not yet valid\n", pEnemy->GetDebugName() );
  6651. continue;
  6652. }
  6653. // Skip enemies that have eluded me to prevent infinite loops
  6654. if ( pEMemory->bEludedMe )
  6655. {
  6656. DbgEnemyMsg( this, " %s rejected: eluded\n", pEnemy->GetDebugName() );
  6657. continue;
  6658. }
  6659. // Skip enemies I fear that I've never seen. (usually seen through an enemy finder)
  6660. if ( relation == D_FR && !pEMemory->bUnforgettable && pEMemory->timeFirstSeen == AI_INVALID_TIME )
  6661. {
  6662. DbgEnemyMsg( this, " %s rejected: feared, but never seen\n", pEnemy->GetDebugName() );
  6663. continue;
  6664. }
  6665. if ( !IsValidEnemy( pEnemy ) )
  6666. {
  6667. DbgEnemyMsg( this, " %s rejected: not valid\n", pEnemy->GetDebugName() );
  6668. continue;
  6669. }
  6670. // establish the reachability of this enemy
  6671. bUnreachable = IsUnreachable(pEnemy);
  6672. // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority
  6673. if (!bBestUnreachable && bUnreachable)
  6674. {
  6675. DbgEnemyMsg( this, " %s rejected: unreachable\n", pEnemy->GetDebugName() );
  6676. continue;
  6677. }
  6678. // If best is unreachable and current is reachable, always pick the current regardless of priority
  6679. if (bBestUnreachable && !bUnreachable)
  6680. {
  6681. DbgEnemyMsg( this, " %s accepted (1)\n", pEnemy->GetDebugName() );
  6682. if ( pBestEnemy )
  6683. DbgEnemyMsg( this, " (%s displaced)\n", pBestEnemy->GetDebugName() );
  6684. iBestPriority = IRelationPriority ( pEnemy );
  6685. iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  6686. pBestEnemy = pEnemy;
  6687. bBestUnreachable = bUnreachable;
  6688. fBestSeen = TRS_NONE;
  6689. fBestVisible = TRS_NONE;
  6690. }
  6691. // If both are unreachable or both are reachable, choose enemy based on priority and distance
  6692. else if ( IRelationPriority( pEnemy ) > iBestPriority )
  6693. {
  6694. DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() );
  6695. if ( pBestEnemy )
  6696. DbgEnemyMsg( this, " (%s displaced due to priority, %d > %d )\n", pBestEnemy->GetDebugName(), IRelationPriority( pEnemy ), iBestPriority );
  6697. // this entity is disliked MORE than the entity that we
  6698. // currently think is the best visible enemy. No need to do
  6699. // a distance check, just get mad at this one for now.
  6700. iBestPriority = IRelationPriority ( pEnemy );
  6701. iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  6702. pBestEnemy = pEnemy;
  6703. bBestUnreachable = bUnreachable;
  6704. fBestSeen = TRS_NONE;
  6705. fBestVisible = TRS_NONE;
  6706. }
  6707. else if ( IRelationPriority( pEnemy ) == iBestPriority )
  6708. {
  6709. // this entity is disliked just as much as the entity that
  6710. // we currently think is the best visible enemy, so we only
  6711. // get mad at it if it is closer.
  6712. iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  6713. bool bAcceptCurrent = false;
  6714. bool bCloser = ( ( iBestDistSq - iDistSq ) > EnemyDistTolerance() );
  6715. ThreeState_t fCurSeen = TRS_NONE;
  6716. ThreeState_t fCurVisible = TRS_NONE;
  6717. // The following code is constructed in such a verbose manner to
  6718. // ensure the expensive calls only occur if absolutely needed
  6719. // If current is farther, and best has previously been confirmed as seen or visible, move on
  6720. if ( !bCloser)
  6721. {
  6722. if ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE )
  6723. {
  6724. DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
  6725. continue;
  6726. }
  6727. }
  6728. // If current is closer, and best has previously been confirmed as not seen and not visible, take it
  6729. if ( bCloser)
  6730. {
  6731. if ( fBestSeen == TRS_FALSE && fBestVisible == TRS_FALSE )
  6732. {
  6733. bAcceptCurrent = true;
  6734. }
  6735. }
  6736. if ( !bAcceptCurrent )
  6737. {
  6738. // If current is closer, and seen, take it
  6739. if ( bCloser )
  6740. {
  6741. fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6742. bAcceptCurrent = ( fCurSeen == TRS_TRUE );
  6743. }
  6744. }
  6745. if ( !bAcceptCurrent )
  6746. {
  6747. // If current is farther, and best is seen, move on
  6748. if ( !bCloser )
  6749. {
  6750. if ( fBestSeen == TRS_NONE )
  6751. {
  6752. fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6753. }
  6754. if ( fBestSeen == TRS_TRUE )
  6755. {
  6756. DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
  6757. continue;
  6758. }
  6759. }
  6760. // At this point, need to start performing expensive tests
  6761. if ( bCloser && fBestVisible == TRS_NONE )
  6762. {
  6763. // Perform shortest FVisible
  6764. fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6765. bAcceptCurrent = ( fCurVisible == TRS_TRUE );
  6766. }
  6767. // Alas, must do the most expensive comparison
  6768. if ( !bAcceptCurrent )
  6769. {
  6770. if ( fBestSeen == TRS_NONE )
  6771. {
  6772. fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6773. }
  6774. if ( fBestVisible == TRS_NONE )
  6775. {
  6776. fBestVisible = ( ( EnemyDistance( pBestEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6777. }
  6778. if ( fCurSeen == TRS_NONE )
  6779. {
  6780. fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6781. }
  6782. if ( fCurVisible == TRS_NONE )
  6783. {
  6784. fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6785. }
  6786. bool bBestSeenOrVisible = ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE );
  6787. bool bCurSeenOrVisible = ( fCurSeen == TRS_TRUE || fCurVisible == TRS_TRUE );
  6788. if ( !bCloser)
  6789. {
  6790. if ( bBestSeenOrVisible )
  6791. {
  6792. DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
  6793. continue;
  6794. }
  6795. else if ( !bCurSeenOrVisible )
  6796. {
  6797. DbgEnemyMsg( this, " %s rejected: current is closer and neither is seen\n", pEnemy->GetDebugName() );
  6798. continue;
  6799. }
  6800. }
  6801. else // Closer
  6802. {
  6803. if ( !bCurSeenOrVisible && bBestSeenOrVisible )
  6804. {
  6805. DbgEnemyMsg( this, " %s rejected: current is father but seen\n", pEnemy->GetDebugName() );
  6806. continue;
  6807. }
  6808. }
  6809. }
  6810. }
  6811. DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() );
  6812. if ( pBestEnemy )
  6813. DbgEnemyMsg( this, " (%s displaced due to distance/visibility)\n", pBestEnemy->GetDebugName() );
  6814. fBestSeen = fCurSeen;
  6815. fBestVisible = fCurVisible;
  6816. iBestDistSq = iDistSq;
  6817. iBestPriority = IRelationPriority ( pEnemy );
  6818. pBestEnemy = pEnemy;
  6819. bBestUnreachable = bUnreachable;
  6820. }
  6821. else
  6822. DbgEnemyMsg( this, " %s rejected: lower priority\n", pEnemy->GetDebugName() );
  6823. }
  6824. DbgEnemyMsg( this, "} == %s\n", pBestEnemy->GetDebugName() );
  6825. return pBestEnemy;
  6826. }
  6827. //-----------------------------------------------------------------------------
  6828. // Purpose: Given a node returns the appropriate reload activity
  6829. // Input :
  6830. // Output :
  6831. //-----------------------------------------------------------------------------
  6832. Activity CAI_BaseNPC::GetReloadActivity( CAI_Hint* pHint )
  6833. {
  6834. Activity nReloadActivity = ACT_RELOAD;
  6835. if (pHint && GetEnemy()!=NULL)
  6836. {
  6837. switch (pHint->HintType())
  6838. {
  6839. case HINT_TACTICAL_COVER_LOW:
  6840. case HINT_TACTICAL_COVER_MED:
  6841. {
  6842. if (SelectWeightedSequence( ACT_RELOAD_LOW ) != ACTIVITY_NOT_AVAILABLE)
  6843. {
  6844. Vector vEyePos = GetAbsOrigin() + EyeOffset(ACT_RELOAD_LOW);
  6845. // Check if this location will block the threat's line of sight to me
  6846. trace_t tr;
  6847. AI_TraceLOS( vEyePos, GetEnemy()->EyePosition(), this, &tr );
  6848. if (tr.fraction != 1.0)
  6849. {
  6850. nReloadActivity = ACT_RELOAD_LOW;
  6851. }
  6852. }
  6853. break;
  6854. }
  6855. }
  6856. }
  6857. return nReloadActivity;
  6858. }
  6859. //-----------------------------------------------------------------------------
  6860. // Purpose: Given a node returns the appropriate cover activity
  6861. // Input :
  6862. // Output :
  6863. //-----------------------------------------------------------------------------
  6864. Activity CAI_BaseNPC::GetCoverActivity( CAI_Hint *pHint )
  6865. {
  6866. Activity nCoverActivity = ACT_INVALID;
  6867. // ---------------------------------------------------------------
  6868. // Check if hint node specifies different cover type
  6869. // ---------------------------------------------------------------
  6870. if (pHint)
  6871. {
  6872. switch (pHint->HintType())
  6873. {
  6874. case HINT_TACTICAL_COVER_MED:
  6875. {
  6876. nCoverActivity = ACT_COVER_MED;
  6877. break;
  6878. }
  6879. case HINT_TACTICAL_COVER_LOW:
  6880. {
  6881. nCoverActivity = ACT_COVER_LOW;
  6882. break;
  6883. }
  6884. }
  6885. }
  6886. if ( nCoverActivity == ACT_INVALID )
  6887. nCoverActivity = ACT_COVER;
  6888. return nCoverActivity;
  6889. }
  6890. //=========================================================
  6891. // CalcIdealYaw - gets a yaw value for the caller that would
  6892. // face the supplied vector. Value is stuffed into the npc's
  6893. // ideal_yaw
  6894. //=========================================================
  6895. float CAI_BaseNPC::CalcIdealYaw( const Vector &vecTarget )
  6896. {
  6897. Vector vecProjection;
  6898. // strafing npc needs to face 90 degrees away from its goal
  6899. if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_LEFT )
  6900. {
  6901. vecProjection.x = -vecTarget.y;
  6902. vecProjection.y = vecTarget.x;
  6903. return UTIL_VecToYaw( vecProjection - GetLocalOrigin() );
  6904. }
  6905. else if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_RIGHT )
  6906. {
  6907. vecProjection.x = vecTarget.y;
  6908. vecProjection.y = vecTarget.x;
  6909. return UTIL_VecToYaw( vecProjection - GetLocalOrigin() );
  6910. }
  6911. else
  6912. {
  6913. return UTIL_VecToYaw ( vecTarget - GetLocalOrigin() );
  6914. }
  6915. }
  6916. //=========================================================
  6917. // SetEyePosition
  6918. //
  6919. // queries the npc's model for $eyeposition and copies
  6920. // that vector to the npc's m_vDefaultEyeOffset and m_vecViewOffset
  6921. //
  6922. //=========================================================
  6923. void CAI_BaseNPC::SetDefaultEyeOffset ( void )
  6924. {
  6925. if ( GetModelPtr() )
  6926. {
  6927. GetEyePosition( GetModelPtr(), m_vDefaultEyeOffset );
  6928. if ( m_vDefaultEyeOffset == vec3_origin )
  6929. {
  6930. if ( Classify() != CLASS_NONE )
  6931. {
  6932. DevMsg( "WARNING: %s(%s) has no eye offset in .qc!\n", GetClassname(), STRING(GetModelName()) );
  6933. }
  6934. VectorAdd( WorldAlignMins(), WorldAlignMaxs(), m_vDefaultEyeOffset );
  6935. m_vDefaultEyeOffset *= 0.75;
  6936. }
  6937. }
  6938. else
  6939. m_vDefaultEyeOffset = vec3_origin;
  6940. SetViewOffset( m_vDefaultEyeOffset );
  6941. }
  6942. //------------------------------------------------------------------------------
  6943. // Purpose : Returns eye offset for an NPC for the given activity
  6944. // Input :
  6945. // Output :
  6946. //------------------------------------------------------------------------------
  6947. Vector CAI_BaseNPC::EyeOffset( Activity nActivity )
  6948. {
  6949. if ( CapabilitiesGet() & bits_CAP_DUCK )
  6950. {
  6951. if ( IsCrouchedActivity( nActivity ) )
  6952. return GetCrouchEyeOffset();
  6953. }
  6954. // if the hint doesn't tell anything, assume current state
  6955. if ( IsCrouching() )
  6956. return GetCrouchEyeOffset();
  6957. return m_vDefaultEyeOffset * GetModelHierarchyScale();
  6958. }
  6959. //-----------------------------------------------------------------------------
  6960. // Purpose:
  6961. // Output : Vector
  6962. //-----------------------------------------------------------------------------
  6963. Vector CAI_BaseNPC::EyePosition( void )
  6964. {
  6965. if ( IsCrouching() )
  6966. return GetAbsOrigin() + GetCrouchEyeOffset();
  6967. return BaseClass::EyePosition();
  6968. }
  6969. //------------------------------------------------------------------------------
  6970. // Purpose :
  6971. // Input :
  6972. // Output :
  6973. //------------------------------------------------------------------------------
  6974. void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent )
  6975. {
  6976. // UNDONE: Share this code into CBaseAnimating as appropriate?
  6977. int nEvent = pEvent->Event();
  6978. switch( nEvent )
  6979. {
  6980. case SCRIPT_EVENT_DEAD:
  6981. if ( m_NPCState == NPC_STATE_SCRIPT )
  6982. {
  6983. m_lifeState = LIFE_DYING;
  6984. // Kill me now! (and fade out when CineCleanup() is called)
  6985. #if _DEBUG
  6986. DevMsg( 2, "Death event: %s\n", GetClassname() );
  6987. #endif
  6988. m_iHealth = 0;
  6989. }
  6990. #if _DEBUG
  6991. else
  6992. DevWarning( 2, "INVALID death event:%s\n", GetClassname() );
  6993. #endif
  6994. break;
  6995. case SCRIPT_EVENT_NOT_DEAD:
  6996. if ( m_NPCState == NPC_STATE_SCRIPT )
  6997. {
  6998. m_lifeState = LIFE_ALIVE;
  6999. // This is for life/death sequences where the player can determine whether a character is dead or alive after the script
  7000. m_iHealth = m_iMaxHealth;
  7001. }
  7002. break;
  7003. case SCRIPT_EVENT_SOUND: // Play a named wave file
  7004. {
  7005. EmitSound( pEvent->options );
  7006. }
  7007. break;
  7008. case SCRIPT_EVENT_SOUND_VOICE:
  7009. {
  7010. EmitSound( pEvent->options );
  7011. }
  7012. break;
  7013. case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time
  7014. if (random->RandomInt(0,2) == 0)
  7015. break;
  7016. // fall through...
  7017. case SCRIPT_EVENT_SENTENCE: // Play a named sentence group
  7018. SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, SNDLVL_TALKING, 0, 100 );
  7019. break;
  7020. case SCRIPT_EVENT_FIREEVENT:
  7021. {
  7022. //
  7023. // Fire a script event. The number of the script event to fire is in the options string.
  7024. //
  7025. if ( m_hCine != NULL )
  7026. {
  7027. m_hCine->FireScriptEvent( atoi( pEvent->options ) );
  7028. }
  7029. else
  7030. {
  7031. // FIXME: look so see if it's playing a vcd and fire those instead
  7032. // AssertOnce( 0 );
  7033. }
  7034. break;
  7035. }
  7036. case SCRIPT_EVENT_FIRE_INPUT:
  7037. {
  7038. variant_t emptyVariant;
  7039. this->AcceptInput( pEvent->options, this, this, emptyVariant, 0 );
  7040. break;
  7041. }
  7042. case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on
  7043. if ( m_hCine )
  7044. m_hCine->AllowInterrupt( false );
  7045. break;
  7046. case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now
  7047. if ( m_hCine )
  7048. m_hCine->AllowInterrupt( true );
  7049. break;
  7050. #if 0
  7051. case SCRIPT_EVENT_INAIR: // Don't engine->DropToFloor()
  7052. case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to
  7053. break;
  7054. #endif
  7055. case SCRIPT_EVENT_BODYGROUPON:
  7056. case SCRIPT_EVENT_BODYGROUPOFF:
  7057. case SCRIPT_EVENT_BODYGROUPTEMP:
  7058. DevMsg( "Bodygroup!\n" );
  7059. break;
  7060. case AE_NPC_ATTACK_BROADCAST:
  7061. break;
  7062. case NPC_EVENT_BODYDROP_HEAVY:
  7063. if ( GetFlags() & FL_ONGROUND )
  7064. {
  7065. EmitSound( "AI_BaseNPC.BodyDrop_Heavy" );
  7066. }
  7067. break;
  7068. case NPC_EVENT_BODYDROP_LIGHT:
  7069. if ( GetFlags() & FL_ONGROUND )
  7070. {
  7071. EmitSound( "AI_BaseNPC.BodyDrop_Light" );
  7072. }
  7073. break;
  7074. case NPC_EVENT_SWISHSOUND:
  7075. {
  7076. // NO NPC may use this anim event unless that npc's precache precaches this sound!!!
  7077. EmitSound( "AI_BaseNPC.SwishSound" );
  7078. break;
  7079. }
  7080. case NPC_EVENT_180TURN:
  7081. {
  7082. //DevMsg( "Turned!\n" );
  7083. SetIdealActivity( ACT_IDLE );
  7084. Forget( bits_MEMORY_TURNING );
  7085. SetBoneController( 0, GetLocalAngles().y );
  7086. AddEffects( EF_NOINTERP );
  7087. break;
  7088. }
  7089. case NPC_EVENT_ITEM_PICKUP:
  7090. {
  7091. CBaseEntity *pPickup = NULL;
  7092. //
  7093. // Figure out what we're supposed to pick up.
  7094. //
  7095. if ( pEvent->options && strlen( pEvent->options ) > 0 )
  7096. {
  7097. // Pick up the weapon or item that was specified in the anim event.
  7098. pPickup = gEntList.FindEntityGenericNearest( pEvent->options, GetAbsOrigin(), 256, this );
  7099. }
  7100. else
  7101. {
  7102. // Pick up the weapon or item that was found earlier and cached in our target pointer.
  7103. pPickup = GetTarget();
  7104. }
  7105. // Make sure we found something to pick up.
  7106. if ( !pPickup )
  7107. {
  7108. TaskFail("Item no longer available!\n");
  7109. break;
  7110. }
  7111. // Make sure the item hasn't moved.
  7112. float flDist = ( pPickup->WorldSpaceCenter() - GetAbsOrigin() ).Length2D();
  7113. if ( flDist > ITEM_PICKUP_TOLERANCE )
  7114. {
  7115. TaskFail("Item has moved!\n");
  7116. break;
  7117. }
  7118. CBaseCombatWeapon *pWeapon = ToBaseCombatWeapon( pPickup );
  7119. if ( pWeapon )
  7120. {
  7121. // Picking up a weapon.
  7122. CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
  7123. if ( pOwner )
  7124. {
  7125. TaskFail( "Weapon in use by someone else" );
  7126. }
  7127. else if ( !pWeapon )
  7128. {
  7129. TaskFail( "Weapon doesn't exist" );
  7130. }
  7131. else if (!Weapon_CanUse( pWeapon ))
  7132. {
  7133. TaskFail( "Can't use this weapon type" );
  7134. }
  7135. else
  7136. {
  7137. PickupWeapon( pWeapon );
  7138. TaskComplete();
  7139. break;
  7140. }
  7141. }
  7142. else
  7143. {
  7144. // Picking up an item.
  7145. PickupItem( pPickup );
  7146. TaskComplete();
  7147. }
  7148. break;
  7149. }
  7150. case NPC_EVENT_WEAPON_SET_SEQUENCE_NUMBER:
  7151. {
  7152. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7153. if ((pWeapon) && (pEvent->options))
  7154. {
  7155. int nSequence = atoi(pEvent->options);
  7156. if (nSequence != -1)
  7157. {
  7158. pWeapon->ResetSequence(nSequence);
  7159. }
  7160. }
  7161. break;
  7162. }
  7163. case NPC_EVENT_WEAPON_SET_SEQUENCE_NAME:
  7164. {
  7165. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7166. if ((pWeapon) && (pEvent->options))
  7167. {
  7168. int nSequence = pWeapon->LookupSequence(pEvent->options);
  7169. if (nSequence != -1)
  7170. {
  7171. pWeapon->ResetSequence(nSequence);
  7172. }
  7173. }
  7174. break;
  7175. }
  7176. case NPC_EVENT_WEAPON_SET_ACTIVITY:
  7177. {
  7178. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7179. if ((pWeapon) && (pEvent->options))
  7180. {
  7181. Activity act = (Activity)pWeapon->LookupActivity(pEvent->options);
  7182. if (act != ACT_INVALID)
  7183. {
  7184. // FIXME: where should the duration come from? normally it would come from the current sequence
  7185. Weapon_SetActivity(act, 0);
  7186. }
  7187. }
  7188. break;
  7189. }
  7190. case NPC_EVENT_WEAPON_DROP:
  7191. {
  7192. //
  7193. // Drop our active weapon (or throw it at the specified target entity).
  7194. //
  7195. CBaseEntity *pTarget = NULL;
  7196. if (pEvent->options)
  7197. {
  7198. pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this);
  7199. }
  7200. if (pTarget)
  7201. {
  7202. Vector vecTargetPos = pTarget->WorldSpaceCenter();
  7203. Weapon_Drop(GetActiveWeapon(), &vecTargetPos);
  7204. }
  7205. else
  7206. {
  7207. Weapon_Drop(GetActiveWeapon());
  7208. }
  7209. break;
  7210. }
  7211. case EVENT_WEAPON_RELOAD:
  7212. {
  7213. if ( GetActiveWeapon() )
  7214. {
  7215. GetActiveWeapon()->WeaponSound( RELOAD_NPC );
  7216. GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
  7217. ClearCondition(COND_LOW_PRIMARY_AMMO);
  7218. ClearCondition(COND_NO_PRIMARY_AMMO);
  7219. ClearCondition(COND_NO_SECONDARY_AMMO);
  7220. }
  7221. break;
  7222. }
  7223. case EVENT_WEAPON_RELOAD_SOUND:
  7224. {
  7225. if ( GetActiveWeapon() )
  7226. {
  7227. GetActiveWeapon()->WeaponSound( RELOAD_NPC );
  7228. }
  7229. break;
  7230. }
  7231. case EVENT_WEAPON_RELOAD_FILL_CLIP:
  7232. {
  7233. if ( GetActiveWeapon() )
  7234. {
  7235. GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
  7236. ClearCondition(COND_LOW_PRIMARY_AMMO);
  7237. ClearCondition(COND_NO_PRIMARY_AMMO);
  7238. ClearCondition(COND_NO_SECONDARY_AMMO);
  7239. }
  7240. break;
  7241. }
  7242. case NPC_EVENT_LEFTFOOT:
  7243. case NPC_EVENT_RIGHTFOOT:
  7244. // For right now, do nothing. All functionality for this lives in individual npcs.
  7245. break;
  7246. case NPC_EVENT_OPEN_DOOR:
  7247. {
  7248. CBasePropDoor *pDoor = (CBasePropDoor *)(CBaseEntity *)GetNavigator()->GetPath()->GetCurWaypoint()->GetEHandleData();
  7249. if (pDoor != NULL)
  7250. {
  7251. OpenPropDoorNow( pDoor );
  7252. }
  7253. break;
  7254. }
  7255. default:
  7256. if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_SERVER))
  7257. {
  7258. if (nEvent == AE_NPC_HOLSTER)
  7259. {
  7260. // Cache off the weapon.
  7261. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7262. Assert( pWeapon != NULL );
  7263. GetActiveWeapon()->Holster();
  7264. SetActiveWeapon( NULL );
  7265. //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now that we don't have a weapon out.
  7266. GetNavigator()->SetArrivalSequence( ACT_INVALID );
  7267. if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY )
  7268. {
  7269. // Get rid of it!
  7270. UTIL_Remove( pWeapon );
  7271. }
  7272. if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE )
  7273. {
  7274. m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
  7275. m_Activity = ACT_RESET;
  7276. }
  7277. return;
  7278. }
  7279. else if (nEvent == AE_NPC_DRAW)
  7280. {
  7281. if (GetActiveWeapon())
  7282. {
  7283. GetActiveWeapon()->Deploy();
  7284. //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now.
  7285. GetNavigator()->SetArrivalSequence( ACT_INVALID );
  7286. if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE )
  7287. {
  7288. m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
  7289. m_Activity = ACT_RESET;
  7290. }
  7291. }
  7292. return;
  7293. }
  7294. else if ( nEvent == AE_NPC_BODYDROP_HEAVY )
  7295. {
  7296. if ( GetFlags() & FL_ONGROUND )
  7297. {
  7298. EmitSound( "AI_BaseNPC.BodyDrop_Heavy" );
  7299. }
  7300. return;
  7301. }
  7302. else if ( nEvent == AE_NPC_LEFTFOOT || nEvent == AE_NPC_RIGHTFOOT )
  7303. {
  7304. return;
  7305. }
  7306. else if ( nEvent == AE_NPC_RAGDOLL )
  7307. {
  7308. // Convert to ragdoll immediately
  7309. BecomeRagdollOnClient( vec3_origin );
  7310. return;
  7311. }
  7312. else if ( nEvent == AE_NPC_ADDGESTURE )
  7313. {
  7314. Activity act = ( Activity )LookupActivity( pEvent->options );
  7315. if (act != ACT_INVALID)
  7316. {
  7317. act = TranslateActivity( act );
  7318. if (act != ACT_INVALID)
  7319. {
  7320. AddGesture( act );
  7321. }
  7322. }
  7323. return;
  7324. }
  7325. else if ( nEvent == AE_NPC_RESTARTGESTURE )
  7326. {
  7327. Activity act = ( Activity )LookupActivity( pEvent->options );
  7328. if (act != ACT_INVALID)
  7329. {
  7330. act = TranslateActivity( act );
  7331. if (act != ACT_INVALID)
  7332. {
  7333. RestartGesture( act );
  7334. }
  7335. }
  7336. return;
  7337. }
  7338. else if ( nEvent == AE_NPC_WEAPON_DROP )
  7339. {
  7340. // Drop our active weapon (or throw it at the specified target entity).
  7341. CBaseEntity *pTarget = NULL;
  7342. if (pEvent->options)
  7343. {
  7344. pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this);
  7345. }
  7346. if (pTarget)
  7347. {
  7348. Vector vecTargetPos = pTarget->WorldSpaceCenter();
  7349. Weapon_Drop(GetActiveWeapon(), &vecTargetPos);
  7350. }
  7351. else
  7352. {
  7353. Weapon_Drop(GetActiveWeapon());
  7354. }
  7355. return;
  7356. }
  7357. else if ( nEvent == AE_NPC_WEAPON_SET_ACTIVITY )
  7358. {
  7359. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7360. if ((pWeapon) && (pEvent->options))
  7361. {
  7362. Activity act = (Activity)pWeapon->LookupActivity(pEvent->options);
  7363. if (act == ACT_INVALID)
  7364. {
  7365. // Try and translate it
  7366. act = Weapon_TranslateActivity( (Activity)CAI_BaseNPC::GetActivityID(pEvent->options), NULL );
  7367. }
  7368. if (act != ACT_INVALID)
  7369. {
  7370. // FIXME: where should the duration come from? normally it would come from the current sequence
  7371. Weapon_SetActivity(act, 0);
  7372. }
  7373. }
  7374. return;
  7375. }
  7376. else if ( nEvent == AE_NPC_SET_INTERACTION_CANTDIE )
  7377. {
  7378. SetInteractionCantDie( (atoi(pEvent->options) != 0) );
  7379. return;
  7380. }
  7381. else if ( nEvent == AE_NPC_HURT_INTERACTION_PARTNER )
  7382. {
  7383. // If we're currently interacting with an enemy, hurt them/me
  7384. if ( m_hInteractionPartner )
  7385. {
  7386. CAI_BaseNPC *pTarget = NULL;
  7387. CAI_BaseNPC *pAttacker = NULL;
  7388. if ( pEvent->options )
  7389. {
  7390. char szEventOptions[128];
  7391. Q_strncpy( szEventOptions, pEvent->options, sizeof(szEventOptions) );
  7392. char *pszParam = strtok( szEventOptions, " " );
  7393. if ( pszParam )
  7394. {
  7395. if ( StringHasPrefixCaseSensitive( pszParam, "ME" ) )
  7396. {
  7397. pTarget = this;
  7398. pAttacker = m_hInteractionPartner;
  7399. }
  7400. else if ( StringHasPrefixCaseSensitive( pszParam, "THEM" ) )
  7401. {
  7402. pAttacker = this;
  7403. pTarget = m_hInteractionPartner;
  7404. }
  7405. pszParam = strtok(NULL," ");
  7406. if ( pAttacker && pTarget && pszParam )
  7407. {
  7408. int iDamage = atoi( pszParam );
  7409. if ( iDamage )
  7410. {
  7411. // We've got a target, and damage. Now hurt them.
  7412. CTakeDamageInfo info;
  7413. info.SetDamage( iDamage );
  7414. info.SetAttacker( pAttacker );
  7415. info.SetInflictor( pAttacker );
  7416. info.SetDamageType( DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE );
  7417. pTarget->TakeDamage( info );
  7418. return;
  7419. }
  7420. }
  7421. }
  7422. }
  7423. // Bad data. Explain how to use this anim event.
  7424. const char *pName = EventList_NameForIndex( nEvent );
  7425. DevWarning( 1, "Bad %s format. Should be: { AE_NPC_HURT_INTERACTION_PARTNER <frame number> \"<ME/THEM> <Amount of damage done>\" }\n", pName );
  7426. return;
  7427. }
  7428. DevWarning( "%s received AE_NPC_HURT_INTERACTION_PARTNER anim event, but it's not interacting with anything.\n", GetDebugName() );
  7429. return;
  7430. }
  7431. }
  7432. // FIXME: why doesn't this code pass unhandled events down to its parent?
  7433. // Came from my weapon?
  7434. //Adrian I'll clean this up once the old event system is phased out.
  7435. if ( pEvent->pSource != this || ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM && pEvent->type & AE_TYPE_WEAPON ) || ( nEvent >= EVENT_WEAPON && nEvent <= EVENT_WEAPON_LAST ) )
  7436. {
  7437. Weapon_HandleAnimEvent( pEvent );
  7438. }
  7439. else
  7440. {
  7441. BaseClass::HandleAnimEvent( pEvent );
  7442. }
  7443. break;
  7444. }
  7445. }
  7446. //-----------------------------------------------------------------------------
  7447. // Purpose: Override base class to add display of routes
  7448. // Input :
  7449. // Output : Current text offset from the top
  7450. //-----------------------------------------------------------------------------
  7451. void CAI_BaseNPC::DrawDebugGeometryOverlays(void)
  7452. {
  7453. // Handy for debug
  7454. //NDebugOverlay::Cross3D(EyePosition(),Vector(-2,-2,-2),Vector(2,2,2),0,255,0,true);
  7455. // ------------------------------
  7456. // Remove me if requested
  7457. // ------------------------------
  7458. if (m_debugOverlays & OVERLAY_NPC_ZAP_BIT)
  7459. {
  7460. VacateStrategySlot();
  7461. Weapon_Drop( GetActiveWeapon() );
  7462. m_iHealth = 0;
  7463. SetThink( &CAI_BaseNPC::SUB_Remove );
  7464. }
  7465. // ------------------------------
  7466. // properly kill an NPC.
  7467. // ------------------------------
  7468. if (m_debugOverlays & OVERLAY_NPC_KILL_BIT)
  7469. {
  7470. CTakeDamageInfo info;
  7471. info.SetDamage( m_iHealth );
  7472. info.SetAttacker( this );
  7473. info.SetInflictor( ( AI_IsSinglePlayer() ) ? (CBaseEntity *)AI_GetSinglePlayer() : (CBaseEntity *)this );
  7474. info.SetDamageType( DMG_GENERIC );
  7475. m_debugOverlays &= ~OVERLAY_NPC_KILL_BIT;
  7476. TakeDamage( info );
  7477. return;
  7478. }
  7479. // ------------------------------
  7480. // Draw route if requested
  7481. // ------------------------------
  7482. if ((m_debugOverlays & OVERLAY_NPC_ROUTE_BIT))
  7483. {
  7484. GetNavigator()->DrawDebugRouteOverlay();
  7485. if ( IsMoving() )
  7486. {
  7487. float yaw = GetMotor()->GetIdealYaw();
  7488. Vector vecYaw = UTIL_YawToVector(yaw);
  7489. NDebugOverlay::Line(WorldSpaceCenter(),WorldSpaceCenter() + vecYaw * GetHullWidth() * .5,255,255,255,true,0.0);
  7490. }
  7491. }
  7492. if (!(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) && (IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN)))
  7493. {
  7494. NDebugOverlay::Box(m_vecLastPosition, Vector(-5,-5,-5),Vector(5,5,5), 255, 0, 255, 0, 0);
  7495. NDebugOverlay::HorzArrow( GetAbsOrigin(), m_vecLastPosition, 16, 255, 0, 255, 64, true, 0 );
  7496. }
  7497. // ------------------------------
  7498. // Draw red box around if selected
  7499. // ------------------------------
  7500. if ((m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) && !ai_no_select_box.GetBool())
  7501. {
  7502. NDebugOverlay::EntityBounds(this, 255, 0, 0, 20, 0);
  7503. }
  7504. // ------------------------------
  7505. // Draw nearest node if selected
  7506. // ------------------------------
  7507. if ((m_debugOverlays & OVERLAY_NPC_NEAREST_BIT))
  7508. {
  7509. int iNodeID = GetPathfinder()->NearestNodeToNPC();
  7510. if (iNodeID != NO_NODE)
  7511. {
  7512. NDebugOverlay::Box(GetNavigator()->GetNetwork()->AccessNodes()[iNodeID]->GetPosition(GetHullType()), Vector(-10,-10,-10),Vector(10,10,10), 255, 255, 255, 0, 0);
  7513. }
  7514. }
  7515. // ------------------------------
  7516. // Draw viewcone if selected
  7517. // ------------------------------
  7518. if ((m_debugOverlays & OVERLAY_NPC_VIEWCONE_BIT))
  7519. {
  7520. float flViewRange = acos(m_flFieldOfView);
  7521. Vector vEyeDir = EyeDirection2D( );
  7522. Vector vLeftDir, vRightDir;
  7523. float fSin, fCos;
  7524. SinCos( flViewRange, &fSin, &fCos );
  7525. vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
  7526. vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
  7527. vLeftDir.z = vEyeDir.z;
  7528. fSin = sin(-flViewRange);
  7529. fCos = cos(-flViewRange);
  7530. vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
  7531. vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
  7532. vRightDir.z = vEyeDir.z;
  7533. // Visualize it
  7534. NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vLeftDir * 200 ), 64, 255, 0, 0, 50, false, 0 );
  7535. NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vRightDir * 200 ), 64, 255, 0, 0, 50, false, 0 );
  7536. NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vEyeDir * 100 ), 8, 0, 255, 0, 50, false, 0 );
  7537. NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
  7538. }
  7539. // ----------------------------------------------
  7540. // Draw the relationships for this NPC to others
  7541. // ----------------------------------------------
  7542. if ( m_debugOverlays & OVERLAY_NPC_RELATION_BIT )
  7543. {
  7544. // Show the relationships to entities around us
  7545. int r = 0;
  7546. int g = 0;
  7547. int b = 0;
  7548. int nRelationship;
  7549. CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  7550. // Rate all NPCs
  7551. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  7552. {
  7553. if ( ppAIs[i] == NULL || ppAIs[i] == this )
  7554. continue;
  7555. // Get our relation to the target
  7556. nRelationship = IRelationType( ppAIs[i] );
  7557. // Get the color for the arrow
  7558. UTIL_GetDebugColorForRelationship( nRelationship, r, g, b );
  7559. // Draw an arrow
  7560. NDebugOverlay::HorzArrow( GetAbsOrigin(), ppAIs[i]->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f );
  7561. }
  7562. // Also include all players
  7563. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  7564. {
  7565. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  7566. if ( pPlayer == NULL )
  7567. continue;
  7568. // Get our relation to the target
  7569. nRelationship = IRelationType( pPlayer );
  7570. // Get the color for the arrow
  7571. UTIL_GetDebugColorForRelationship( nRelationship, r, g, b );
  7572. // Draw an arrow
  7573. NDebugOverlay::HorzArrow( GetAbsOrigin(), pPlayer->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f );
  7574. }
  7575. }
  7576. // ------------------------------
  7577. // Draw enemies if selected
  7578. // ------------------------------
  7579. if ((m_debugOverlays & OVERLAY_NPC_ENEMIES_BIT))
  7580. {
  7581. AIEnemiesIter_t iter;
  7582. for( AI_EnemyInfo_t *eMemory = GetEnemies()->GetFirst(&iter); eMemory != NULL; eMemory = GetEnemies()->GetNext(&iter) )
  7583. {
  7584. if (eMemory->hEnemy)
  7585. {
  7586. CBaseCombatCharacter *npcEnemy = (eMemory->hEnemy)->MyCombatCharacterPointer();
  7587. if (npcEnemy)
  7588. {
  7589. float r,g,b;
  7590. char debugText[255];
  7591. debugText[0] = NULL;
  7592. if (npcEnemy == GetEnemy())
  7593. {
  7594. Q_strncat(debugText,"Current Enemy", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7595. }
  7596. else if (npcEnemy == GetTarget())
  7597. {
  7598. Q_strncat(debugText,"Current Target", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7599. }
  7600. else
  7601. {
  7602. Q_strncat(debugText,"Other Memory", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7603. }
  7604. if (IsUnreachable(npcEnemy))
  7605. {
  7606. Q_strncat(debugText," (Unreachable)", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7607. }
  7608. if (eMemory->bEludedMe)
  7609. {
  7610. Q_strncat(debugText," (Eluded)", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7611. }
  7612. // Unreachable enemy drawn in green
  7613. if (IsUnreachable(npcEnemy))
  7614. {
  7615. r = 0;
  7616. g = 255;
  7617. b = 0;
  7618. }
  7619. // Eluded enemy drawn in blue
  7620. else if (eMemory->bEludedMe)
  7621. {
  7622. r = 0;
  7623. g = 0;
  7624. b = 255;
  7625. }
  7626. // Current enemy drawn in red
  7627. else if (npcEnemy == GetEnemy())
  7628. {
  7629. r = 255;
  7630. g = 0;
  7631. b = 0;
  7632. }
  7633. // Current traget drawn in magenta
  7634. else if (npcEnemy == GetTarget())
  7635. {
  7636. r = 255;
  7637. g = 0;
  7638. b = 255;
  7639. }
  7640. // All other enemies drawn in pink
  7641. else
  7642. {
  7643. r = 255;
  7644. g = 100;
  7645. b = 100;
  7646. }
  7647. Vector drawPos = eMemory->vLastKnownLocation;
  7648. NDebugOverlay::Text( drawPos, debugText, false, 0.0 );
  7649. // If has a line on the player draw cross slightly in front so player can see
  7650. if (npcEnemy->IsPlayer() &&
  7651. (eMemory->vLastKnownLocation - npcEnemy->GetAbsOrigin()).Length()<10 )
  7652. {
  7653. Vector vEnemyFacing = npcEnemy->BodyDirection2D( );
  7654. Vector eyePos = npcEnemy->EyePosition() + vEnemyFacing*10.0;
  7655. Vector upVec = Vector(0,0,2);
  7656. Vector sideVec;
  7657. CrossProduct( vEnemyFacing, upVec, sideVec);
  7658. NDebugOverlay::Line(eyePos+sideVec+upVec, eyePos-sideVec-upVec, r,g,b, false,0);
  7659. NDebugOverlay::Line(eyePos+sideVec-upVec, eyePos-sideVec+upVec, r,g,b, false,0);
  7660. NDebugOverlay::Text( eyePos, debugText, false, 0.0 );
  7661. }
  7662. else
  7663. {
  7664. NDebugOverlay::Cross3D(drawPos,NAI_Hull::Mins(npcEnemy->GetHullType()),NAI_Hull::Maxs(npcEnemy->GetHullType()),r,g,b,false,0);
  7665. }
  7666. }
  7667. }
  7668. }
  7669. }
  7670. // ----------------------------------------------
  7671. // Draw line to target and enemy entity if exist
  7672. // ----------------------------------------------
  7673. if ((m_debugOverlays & OVERLAY_NPC_FOCUS_BIT))
  7674. {
  7675. if (GetEnemy() != NULL)
  7676. {
  7677. NDebugOverlay::Line(EyePosition(),GetEnemy()->EyePosition(),255,0,0,true,0.0);
  7678. }
  7679. if (GetTarget() != NULL)
  7680. {
  7681. NDebugOverlay::Line(EyePosition(),GetTarget()->EyePosition(),0,0,255,true,0.0);
  7682. }
  7683. }
  7684. GetPathfinder()->DrawDebugGeometryOverlays(m_debugOverlays);
  7685. CBaseEntity::DrawDebugGeometryOverlays();
  7686. }
  7687. //-----------------------------------------------------------------------------
  7688. // Purpose: Draw any debug text overlays
  7689. // Input :
  7690. // Output : Current text offset from the top
  7691. //-----------------------------------------------------------------------------
  7692. int CAI_BaseNPC::DrawDebugTextOverlays(void)
  7693. {
  7694. int text_offset = 0;
  7695. // ---------------------
  7696. // Print Baseclass text
  7697. // ---------------------
  7698. text_offset = BaseClass::DrawDebugTextOverlays();
  7699. if (m_debugOverlays & OVERLAY_NPC_SQUAD_BIT)
  7700. {
  7701. // Print health
  7702. char tempstr[512];
  7703. Q_snprintf(tempstr,sizeof(tempstr),"Health: %i",m_iHealth.Get());
  7704. EntityText(text_offset,tempstr,0);
  7705. text_offset++;
  7706. // Print squad name
  7707. Q_strncpy(tempstr,"Squad: ",sizeof(tempstr));
  7708. if (m_pSquad)
  7709. {
  7710. Q_strncat(tempstr,m_pSquad->GetName(),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7711. if( m_pSquad->GetLeader() == this )
  7712. {
  7713. Q_strncat(tempstr," (LEADER)",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7714. }
  7715. Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7716. }
  7717. else
  7718. {
  7719. Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7720. }
  7721. EntityText(text_offset,tempstr,0);
  7722. text_offset++;
  7723. // Print enemy name
  7724. Q_strncpy(tempstr,"Enemy: ",sizeof(tempstr));
  7725. if (GetEnemy())
  7726. {
  7727. if (GetEnemy()->GetEntityName() != NULL_STRING)
  7728. {
  7729. Q_strncat(tempstr,STRING(GetEnemy()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7730. Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7731. }
  7732. else
  7733. {
  7734. Q_strncat(tempstr,STRING(GetEnemy()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7735. Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7736. }
  7737. }
  7738. else
  7739. {
  7740. Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7741. }
  7742. EntityText(text_offset,tempstr,0);
  7743. text_offset++;
  7744. // Print slot
  7745. Q_snprintf(tempstr,sizeof(tempstr),"Slot: %s (%d)\n",
  7746. SquadSlotName(m_iMySquadSlot), m_iMySquadSlot);
  7747. EntityText(text_offset,tempstr,0);
  7748. text_offset++;
  7749. }
  7750. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  7751. {
  7752. int r = 0;
  7753. int g = 255;
  7754. int b = 255;
  7755. char tempstr[512];
  7756. // --------------
  7757. // Print Health
  7758. // --------------
  7759. Q_snprintf(tempstr,sizeof(tempstr),"Health: %i (DACC:%1.2f)",m_iHealth.Get(), GetDamageAccumulator() );
  7760. EntityText(text_offset,tempstr,0,r,g,b);
  7761. text_offset++;
  7762. // --------------
  7763. // Print State
  7764. // --------------
  7765. static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" };
  7766. if ( (int)m_NPCState < ARRAYSIZE(pStateNames) )
  7767. {
  7768. Q_snprintf(tempstr,sizeof(tempstr),"Stat: %s, ", pStateNames[m_NPCState] );
  7769. EntityText(text_offset,tempstr,0,r,g,b);
  7770. text_offset++;
  7771. }
  7772. // -----------------
  7773. // Start Scripting?
  7774. // -----------------
  7775. if( IsInAScript() )
  7776. {
  7777. Q_snprintf(tempstr,sizeof(tempstr),"STARTSCRIPTING" );
  7778. EntityText(text_offset,tempstr,0,r,g,b);
  7779. text_offset++;
  7780. }
  7781. // -----------------
  7782. // Hint Group?
  7783. // -----------------
  7784. if( GetHintGroup() != NULL_STRING )
  7785. {
  7786. Q_snprintf(tempstr,sizeof(tempstr),"Hint Group: %s", STRING(GetHintGroup()) );
  7787. EntityText(text_offset,tempstr,0,r,g,b);
  7788. text_offset++;
  7789. }
  7790. // -----------------
  7791. // Print MotionType
  7792. // -----------------
  7793. int navTypeIndex = (int)GetNavType() + 1;
  7794. static const char *pMoveNames[] = { "None", "Ground", "Jump", "Fly", "Climb", "Crawl" };
  7795. Assert( navTypeIndex >= 0 && navTypeIndex < ARRAYSIZE(pMoveNames) );
  7796. if ( navTypeIndex < ARRAYSIZE(pMoveNames) )
  7797. {
  7798. Q_snprintf(tempstr,sizeof(tempstr),"Move: %s, ", pMoveNames[navTypeIndex] );
  7799. EntityText(text_offset,tempstr,0,r,g,b);
  7800. text_offset++;
  7801. }
  7802. // --------------
  7803. // Print Schedule
  7804. // --------------
  7805. if ( GetCurSchedule() )
  7806. {
  7807. CAI_BehaviorBase *pBehavior = GetPrimaryBehavior();
  7808. if ( pBehavior )
  7809. {
  7810. Q_snprintf(tempstr,sizeof(tempstr),"Behv: %s, ", pBehavior->GetName() );
  7811. EntityText(text_offset,tempstr,0,r,g,b);
  7812. text_offset++;
  7813. }
  7814. const char *pName = NULL;
  7815. pName = GetCurSchedule()->GetName();
  7816. if ( !pName )
  7817. {
  7818. pName = "Unknown";
  7819. }
  7820. Q_snprintf(tempstr,sizeof(tempstr),"Schd: %s, ", pName );
  7821. EntityText(text_offset,tempstr,0,r,g,b);
  7822. text_offset++;
  7823. if (m_debugOverlays & OVERLAY_NPC_TASK_BIT)
  7824. {
  7825. for (int i = 0 ; i < GetCurSchedule()->NumTasks(); i++)
  7826. {
  7827. Q_snprintf(tempstr,sizeof(tempstr),"%s%s%s%s",
  7828. ((i==0) ? "Task:":" "),
  7829. ((i==GetScheduleCurTaskIndex()) ? "->" :" "),
  7830. TaskName(GetCurSchedule()->GetTaskList()[i].iTask),
  7831. ((i==GetScheduleCurTaskIndex()) ? "<-" :""));
  7832. EntityText(text_offset,tempstr,0,r,g,b);
  7833. text_offset++;
  7834. }
  7835. }
  7836. else
  7837. {
  7838. const Task_t *pTask = GetTask();
  7839. if ( pTask )
  7840. {
  7841. Q_snprintf(tempstr,sizeof(tempstr),"Task: %s (#%d), ", TaskName(pTask->iTask), GetScheduleCurTaskIndex() );
  7842. }
  7843. else
  7844. {
  7845. Q_strncpy(tempstr,"Task: None",sizeof(tempstr));
  7846. }
  7847. EntityText(text_offset,tempstr,0,r,g,b);
  7848. text_offset++;
  7849. }
  7850. }
  7851. // --------------
  7852. // Print Acitivity
  7853. // --------------
  7854. if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID && m_Activity != ACT_RESET)
  7855. {
  7856. Activity iActivity = TranslateActivity( m_Activity );
  7857. Activity iIdealActivity = Weapon_TranslateActivity( m_IdealActivity );
  7858. iIdealActivity = NPC_TranslateActivity( iIdealActivity );
  7859. const char *pszActivity = GetActivityName( iActivity );
  7860. const char *pszIdealActivity = GetActivityName( iIdealActivity );
  7861. const char *pszRootActivity = GetActivityName( m_Activity );
  7862. Q_snprintf(tempstr,sizeof(tempstr),"Actv: %s (%s) [%s]\n", pszActivity, pszIdealActivity, pszRootActivity );
  7863. }
  7864. else if (m_Activity == ACT_RESET)
  7865. {
  7866. Q_strncpy(tempstr,"Actv: RESET",sizeof(tempstr) );
  7867. }
  7868. else
  7869. {
  7870. Q_strncpy(tempstr,"Actv: INVALID", sizeof(tempstr) );
  7871. }
  7872. EntityText(text_offset,tempstr,0,r,g,b);
  7873. text_offset++;
  7874. //
  7875. // Print all the current conditions.
  7876. //
  7877. if (m_debugOverlays & OVERLAY_NPC_CONDITIONS_BIT)
  7878. {
  7879. bool bHasConditions = false;
  7880. for (int i = 0; i < MAX_CONDITIONS; i++)
  7881. {
  7882. if (m_Conditions.IsBitSet(i))
  7883. {
  7884. Q_snprintf(tempstr, sizeof(tempstr), "Cond: %s\n", ConditionName(AI_RemapToGlobal(i)));
  7885. EntityText(text_offset, tempstr, 0,r,g,b);
  7886. text_offset++;
  7887. bHasConditions = true;
  7888. }
  7889. }
  7890. if (!bHasConditions)
  7891. {
  7892. Q_snprintf(tempstr,sizeof(tempstr),"(no conditions)");
  7893. EntityText(text_offset,tempstr,0,r,g,b);
  7894. text_offset++;
  7895. }
  7896. }
  7897. if ( GetFlags() & FL_FLY )
  7898. {
  7899. EntityText(text_offset,"HAS FL_FLY",0);
  7900. text_offset++;
  7901. }
  7902. // --------------
  7903. // Print Interrupte
  7904. // --------------
  7905. if (m_interuptSchedule)
  7906. {
  7907. const char *pName = NULL;
  7908. pName = m_interuptSchedule->GetName();
  7909. if ( !pName )
  7910. {
  7911. pName = "Unknown";
  7912. }
  7913. Q_snprintf(tempstr,sizeof(tempstr),"Intr: %s (%s)\n", pName, m_interruptText );
  7914. EntityText(text_offset,tempstr,0,r,g,b);
  7915. text_offset++;
  7916. }
  7917. // --------------
  7918. // Print Failure
  7919. // --------------
  7920. if (m_failedSchedule)
  7921. {
  7922. const char *pName = NULL;
  7923. pName = m_failedSchedule->GetName();
  7924. if ( !pName )
  7925. {
  7926. pName = "Unknown";
  7927. }
  7928. Q_snprintf(tempstr,sizeof(tempstr),"Fail: %s (%s)\n", pName,m_failText );
  7929. EntityText(text_offset,tempstr,0,r,g,b);
  7930. text_offset++;
  7931. }
  7932. // -------------------------------
  7933. // Print any important condtions
  7934. // -------------------------------
  7935. if (HasCondition(COND_ENEMY_TOO_FAR))
  7936. {
  7937. EntityText(text_offset,"Enemy too far to attack",0,r,g,b);
  7938. text_offset++;
  7939. }
  7940. if ( GetAbsVelocity() != vec3_origin || GetLocalAngularVelocity() != vec3_angle )
  7941. {
  7942. char tmp[512];
  7943. Q_snprintf( tmp, sizeof(tmp), "Vel %.1f %.1f %.1f Ang: %.1f %.1f %.1f\n",
  7944. GetAbsVelocity().x, GetAbsVelocity().y, GetAbsVelocity().z,
  7945. GetLocalAngularVelocity().x, GetLocalAngularVelocity().y, GetLocalAngularVelocity().z );
  7946. EntityText(text_offset,tmp,0,r,g,b);
  7947. text_offset++;
  7948. }
  7949. // -------------------------------
  7950. // Print shot accuracy
  7951. // -------------------------------
  7952. if ( m_LastShootAccuracy != -1 && ai_shot_stats.GetBool() )
  7953. {
  7954. CFmtStr msg;
  7955. EntityText(text_offset,msg.sprintf("Cur Accuracy: %.1f", m_LastShootAccuracy),0);
  7956. text_offset++;
  7957. if ( m_TotalShots )
  7958. {
  7959. EntityText(text_offset,msg.sprintf("Act Accuracy: %.1f", ((float)m_TotalHits/(float)m_TotalShots)*100.0),0,r,g,b);
  7960. text_offset++;
  7961. }
  7962. if ( GetActiveWeapon() && GetEnemy() )
  7963. {
  7964. Vector curSpread = GetAttackSpread(GetActiveWeapon(), GetEnemy());
  7965. float curCone = RAD2DEG(asin(curSpread.x)) * 2;
  7966. float bias = GetSpreadBias( GetActiveWeapon(), GetEnemy());
  7967. EntityText(text_offset,msg.sprintf("Cone %.1f, Bias %.2f", curCone, bias),0,r,g,b);
  7968. text_offset++;
  7969. }
  7970. }
  7971. if ( GetGoalEnt() && GetNavigator()->GetGoalType() == GOALTYPE_PATHCORNER )
  7972. {
  7973. Q_strncpy(tempstr,"Pathcorner/goal ent: ",sizeof(tempstr));
  7974. if (GetGoalEnt()->GetEntityName() != NULL_STRING)
  7975. {
  7976. Q_strncat(tempstr,STRING(GetGoalEnt()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7977. }
  7978. else
  7979. {
  7980. Q_strncat(tempstr,STRING(GetGoalEnt()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7981. }
  7982. EntityText(text_offset, tempstr, 0,r,g,b);
  7983. text_offset++;
  7984. }
  7985. if ( VPhysicsGetObject() )
  7986. {
  7987. vphysics_objectstress_t stressOut;
  7988. CalculateObjectStress( VPhysicsGetObject(), this, &stressOut );
  7989. Q_snprintf(tempstr, sizeof(tempstr),"Stress: %.2f", stressOut.receivedStress );
  7990. EntityText(text_offset, tempstr, 0,r,g,b);
  7991. text_offset++;
  7992. }
  7993. if ( m_pSquad )
  7994. {
  7995. if( m_pSquad->IsLeader(this) )
  7996. {
  7997. Q_snprintf(tempstr, sizeof(tempstr),"**Squad Leader**" );
  7998. EntityText(text_offset, tempstr, 0,r,g,b);
  7999. text_offset++;
  8000. }
  8001. Q_snprintf(tempstr, sizeof(tempstr), "SquadSlot:%s", GetSquadSlotDebugName( GetMyStrategySlot() ) );
  8002. EntityText(text_offset, tempstr, 0,r,g,b);
  8003. text_offset++;
  8004. }
  8005. if ( m_pPrimaryBehavior )
  8006. {
  8007. text_offset = m_pPrimaryBehavior->DrawDebugTextOverlays( text_offset );
  8008. }
  8009. }
  8010. return text_offset;
  8011. }
  8012. //-----------------------------------------------------------------------------
  8013. // Purpose: Displays information in the console about the state of this npc.
  8014. //-----------------------------------------------------------------------------
  8015. void CAI_BaseNPC::ReportAIState( void )
  8016. {
  8017. static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" };
  8018. DevMsg( "%s: ", GetClassname() );
  8019. if ( (int)m_NPCState < ARRAYSIZE(pStateNames) )
  8020. DevMsg( "State: %s, ", pStateNames[m_NPCState] );
  8021. if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID )
  8022. {
  8023. const char *pszActivity = GetActivityName(m_Activity);
  8024. const char *pszIdealActivity = GetActivityName(m_IdealActivity);
  8025. DevMsg( "Activity: %s - Ideal Activity: %s\n", pszActivity, pszIdealActivity );
  8026. }
  8027. if ( GetCurSchedule() )
  8028. {
  8029. const char *pName = NULL;
  8030. pName = GetCurSchedule()->GetName();
  8031. if ( !pName )
  8032. pName = "Unknown";
  8033. DevMsg( "Schedule %s, ", pName );
  8034. const Task_t *pTask = GetTask();
  8035. if ( pTask )
  8036. DevMsg( "Task %d (#%d), ", pTask->iTask, GetScheduleCurTaskIndex() );
  8037. }
  8038. else
  8039. DevMsg( "No Schedule, " );
  8040. if ( GetEnemy() != NULL )
  8041. {
  8042. g_pEffects->Sparks( GetEnemy()->GetAbsOrigin() + Vector( 0, 0, 64 ) );
  8043. DevMsg( "\nEnemy is %s", GetEnemy()->GetClassname() );
  8044. }
  8045. else
  8046. DevMsg( "No enemy " );
  8047. if ( IsMoving() )
  8048. {
  8049. DevMsg( " Moving " );
  8050. if ( m_flMoveWaitFinished > gpGlobals->curtime )
  8051. DevMsg( ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->curtime );
  8052. else if ( m_IdealActivity == GetStoppedActivity() )
  8053. DevMsg( ": In stopped anim. " );
  8054. }
  8055. DevMsg( "Leader." );
  8056. DevMsg( "\n" );
  8057. DevMsg( "Yaw speed:%3.1f,Health: %3d\n", GetMotor()->GetYawSpeed(), m_iHealth.Get() );
  8058. if ( GetGroundEntity() )
  8059. {
  8060. DevMsg( "Groundent:%s\n\n", GetGroundEntity()->GetClassname() );
  8061. }
  8062. else
  8063. {
  8064. DevMsg( "Groundent: NULL\n\n" );
  8065. }
  8066. }
  8067. //-----------------------------------------------------------------------------
  8068. ConVar ai_report_task_timings_on_limit( "ai_report_task_timings_on_limit", "0", FCVAR_ARCHIVE );
  8069. ConVar ai_think_limit_label( "ai_think_limit_label", "0", FCVAR_ARCHIVE );
  8070. void CAI_BaseNPC::ReportOverThinkLimit( float time )
  8071. {
  8072. DevMsg( "%s thinking for %.02fms!!! (%s); r%.2f (c%.2f, pst%.2f, ms%.2f), p-r%.2f, m%.2f\n",
  8073. GetDebugName(), time, GetCurSchedule()->GetName(),
  8074. g_AIRunTimer.GetDuration().GetMillisecondsF(),
  8075. g_AIConditionsTimer.GetDuration().GetMillisecondsF(),
  8076. g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF(),
  8077. g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF(),
  8078. g_AIPostRunTimer.GetDuration().GetMillisecondsF(),
  8079. g_AIMoveTimer.GetDuration().GetMillisecondsF() );
  8080. if (ai_think_limit_label.GetBool())
  8081. {
  8082. Vector tmp;
  8083. CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &tmp );
  8084. tmp.z += 16;
  8085. float max = -1;
  8086. const char *pszMax = "unknown";
  8087. if ( g_AIConditionsTimer.GetDuration().GetMillisecondsF() > max )
  8088. {
  8089. max = g_AIConditionsTimer.GetDuration().GetMillisecondsF();
  8090. pszMax = "Conditions";
  8091. }
  8092. if ( g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF() > max )
  8093. {
  8094. max = g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF();
  8095. pszMax = "Pre-think";
  8096. }
  8097. if ( g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF() > max )
  8098. {
  8099. max = g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF();
  8100. pszMax = "Schedule";
  8101. }
  8102. if ( g_AIPostRunTimer.GetDuration().GetMillisecondsF() > max )
  8103. {
  8104. max = g_AIPostRunTimer.GetDuration().GetMillisecondsF();
  8105. pszMax = "Post-run";
  8106. }
  8107. if ( g_AIMoveTimer.GetDuration().GetMillisecondsF() > max )
  8108. {
  8109. max = g_AIMoveTimer.GetDuration().GetMillisecondsF();
  8110. pszMax = "Move";
  8111. }
  8112. NDebugOverlay::Text( tmp, (char*)(const char *)CFmtStr( "Slow %.1f, %s %.1f ", time, pszMax, max ), false, 1 );
  8113. }
  8114. if ( ai_report_task_timings_on_limit.GetBool() )
  8115. DumpTaskTimings();
  8116. }
  8117. //-----------------------------------------------------------------------------
  8118. // Purpose: Returns whether or not this npc can play the scripted sequence or AI
  8119. // sequence that is trying to possess it. If DisregardState is set, the npc
  8120. // will be sucked into the script no matter what state it is in. ONLY
  8121. // Scripted AI ents should allow this.
  8122. // Input : fDisregardNPCState -
  8123. // interruptLevel -
  8124. // eMode - If the function returns true, eMode will be one of the following values:
  8125. // CAN_PLAY_NOW
  8126. // CAN_PLAY_ENQUEUED
  8127. // Output :
  8128. //-----------------------------------------------------------------------------
  8129. CanPlaySequence_t CAI_BaseNPC::CanPlaySequence( bool fDisregardNPCState, int interruptLevel )
  8130. {
  8131. CanPlaySequence_t eReturn = CAN_PLAY_NOW;
  8132. if ( m_hCine )
  8133. {
  8134. if ( !m_hCine->CanEnqueueAfter() )
  8135. {
  8136. // npc is already running one scripted sequence and has an important script to play next
  8137. return CANNOT_PLAY;
  8138. }
  8139. eReturn = CAN_PLAY_ENQUEUED;
  8140. }
  8141. if ( !IsAlive() )
  8142. {
  8143. // npc is dead!
  8144. return CANNOT_PLAY;
  8145. }
  8146. // An NPC in a vehicle cannot play a scripted sequence
  8147. if ( IsInAVehicle() )
  8148. return CANNOT_PLAY;
  8149. if ( fDisregardNPCState )
  8150. {
  8151. // ok to go, no matter what the npc state. (scripted AI)
  8152. // No clue as to how to proced if they're climbing or jumping
  8153. // Assert( GetNavType() != NAV_JUMP && GetNavType() != NAV_CLIMB );
  8154. return eReturn;
  8155. }
  8156. if ( m_NPCState == NPC_STATE_NONE || m_NPCState == NPC_STATE_IDLE || m_IdealNPCState == NPC_STATE_IDLE )
  8157. {
  8158. // ok to go, but only in these states
  8159. return eReturn;
  8160. }
  8161. if ( m_NPCState == NPC_STATE_ALERT && interruptLevel >= SS_INTERRUPT_BY_NAME )
  8162. {
  8163. return eReturn;
  8164. }
  8165. // unknown situation
  8166. return CANNOT_PLAY;
  8167. }
  8168. //-----------------------------------------------------------------------------
  8169. void CAI_BaseNPC::SetHintGroup( string_t newGroup, bool bHintGroupNavLimiting )
  8170. {
  8171. string_t oldGroup = m_strHintGroup;
  8172. m_strHintGroup = newGroup;
  8173. m_bHintGroupNavLimiting = bHintGroupNavLimiting;
  8174. if ( oldGroup != newGroup )
  8175. OnChangeHintGroup( oldGroup, newGroup );
  8176. }
  8177. //-----------------------------------------------------------------------------
  8178. // Purpose:
  8179. // Input :
  8180. // Output :
  8181. //-----------------------------------------------------------------------------
  8182. Vector CAI_BaseNPC::GetShootEnemyDir( const Vector &shootOrigin, bool bNoisy )
  8183. {
  8184. CBaseEntity *pEnemy = GetEnemy();
  8185. if ( pEnemy )
  8186. {
  8187. Vector vecEnemyLKP = GetEnemyLKP();
  8188. Vector vecEnemyOffset = pEnemy->BodyTarget( shootOrigin, bNoisy ) - pEnemy->GetAbsOrigin();
  8189. #ifdef PORTAL
  8190. // Translate the enemy's position across the portals if it's only seen in the portal view cone
  8191. if ( !FInViewCone( vecEnemyLKP ) || !FVisible( vecEnemyLKP ) )
  8192. {
  8193. CPortal_Base2D *pPortal = FInViewConeThroughPortal( vecEnemyLKP );
  8194. if ( pPortal )
  8195. {
  8196. UTIL_Portal_VectorTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecEnemyOffset, vecEnemyOffset );
  8197. UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecEnemyLKP, vecEnemyLKP );
  8198. }
  8199. }
  8200. #endif
  8201. Vector retval = vecEnemyOffset + vecEnemyLKP - shootOrigin;
  8202. VectorNormalize( retval );
  8203. return retval;
  8204. }
  8205. else
  8206. {
  8207. Vector forward;
  8208. AngleVectors( GetLocalAngles(), &forward );
  8209. return forward;
  8210. }
  8211. }
  8212. //-----------------------------------------------------------------------------
  8213. // Simulates many times and reports statistical accuracy.
  8214. //-----------------------------------------------------------------------------
  8215. void CAI_BaseNPC::CollectShotStats( const Vector &vecShootOrigin, const Vector &vecShootDir )
  8216. {
  8217. #ifdef HL2_DLL
  8218. if( ai_shot_stats.GetBool() != 0 && GetEnemy()->IsPlayer() )
  8219. {
  8220. int iterations = ai_shot_stats_term.GetInt();
  8221. int iHits = 0;
  8222. Vector testDir = vecShootDir;
  8223. CShotManipulator manipulator( testDir );
  8224. for( int i = 0 ; i < iterations ; i++ )
  8225. {
  8226. // Apply appropriate accuracy.
  8227. manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) );
  8228. Vector shotDir = manipulator.GetResult();
  8229. Vector vecEnd = vecShootOrigin + shotDir * 8192;
  8230. trace_t tr;
  8231. AI_TraceLine( vecShootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  8232. if( tr.m_pEnt && tr.m_pEnt == GetEnemy() )
  8233. {
  8234. iHits++;
  8235. }
  8236. Vector vecProjectedPosition = GetActualShootPosition( vecShootOrigin );
  8237. Vector testDir = vecProjectedPosition - vecShootOrigin;
  8238. VectorNormalize( testDir );
  8239. manipulator.SetShootDir( testDir );
  8240. }
  8241. float flHitPercent = ((float)iHits / (float)iterations) * 100.0;
  8242. m_LastShootAccuracy = flHitPercent;
  8243. //DevMsg("Shots:%d Hits:%d Percentage:%.1f\n", iterations, iHits, flHitPercent);
  8244. }
  8245. else
  8246. {
  8247. m_LastShootAccuracy = -1;
  8248. }
  8249. #endif
  8250. }
  8251. //-----------------------------------------------------------------------------
  8252. // Purpose: Return the actual position the NPC wants to fire at when it's trying
  8253. // to hit it's current enemy.
  8254. //-----------------------------------------------------------------------------
  8255. Vector CAI_BaseNPC::GetActualShootPosition( const Vector &shootOrigin )
  8256. {
  8257. // Project the target's location into the future.
  8258. Vector vecEnemyLKP = GetEnemyLKP();
  8259. Vector vecEnemyOffset = GetEnemy()->BodyTarget( shootOrigin ) - GetEnemy()->GetAbsOrigin();
  8260. Vector vecTargetPosition = vecEnemyOffset + vecEnemyLKP;
  8261. #ifdef PORTAL
  8262. // Check if it's also visible through portals
  8263. CPortal_Base2D *pPortal = FInViewConeThroughPortal( vecEnemyLKP );
  8264. // BUG 69142: Only run this if the target is on the other side of a portal
  8265. if ( pPortal && FVisibleThroughPortal( pPortal, GetEnemy() ) )
  8266. {
  8267. // Get the target's position through portals
  8268. Vector vecEnemyOffsetTransformed;
  8269. Vector vecEnemyLKPTransformed;
  8270. UTIL_Portal_VectorTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecEnemyOffset, vecEnemyOffsetTransformed );
  8271. UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecEnemyLKP, vecEnemyLKPTransformed );
  8272. Vector vecTargetPositionTransformed = vecEnemyOffsetTransformed + vecEnemyLKPTransformed;
  8273. // Get the distance to the target with and without portals
  8274. float fDistanceToEnemyThroughPortalSqr = GetAbsOrigin().DistToSqr( vecTargetPositionTransformed );
  8275. float fDistanceToEnemySqr = GetAbsOrigin().DistToSqr( vecTargetPosition );
  8276. if ( fDistanceToEnemyThroughPortalSqr < fDistanceToEnemySqr || !FInViewCone( vecEnemyLKP ) || !FVisible( vecEnemyLKP ) )
  8277. {
  8278. // We're better off shooting through the portals
  8279. vecTargetPosition = vecTargetPositionTransformed;
  8280. }
  8281. }
  8282. #endif
  8283. // lead for some fraction of a second.
  8284. return (vecTargetPosition + ( GetEnemy()->GetSmoothedVelocity() * ai_lead_time.GetFloat() ));
  8285. }
  8286. //-----------------------------------------------------------------------------
  8287. // Purpose:
  8288. //-----------------------------------------------------------------------------
  8289. float CAI_BaseNPC::GetSpreadBias( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
  8290. {
  8291. float bias = BaseClass::GetSpreadBias( pWeapon, pTarget );
  8292. AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget );
  8293. if ( ai_shot_bias.GetFloat() != 1.0 )
  8294. bias = ai_shot_bias.GetFloat();
  8295. if ( pEnemyInfo )
  8296. {
  8297. float timeToFocus = ai_spread_pattern_focus_time.GetFloat();
  8298. if ( timeToFocus > 0.0 )
  8299. {
  8300. float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy;
  8301. if ( timeSinceValidEnemy < 0.0f )
  8302. {
  8303. timeSinceValidEnemy = 0.0f;
  8304. }
  8305. float timeSinceReacquire = gpGlobals->curtime - pEnemyInfo->timeLastReacquired;
  8306. if ( timeSinceValidEnemy < timeToFocus )
  8307. {
  8308. float scale = timeSinceValidEnemy / timeToFocus;
  8309. Assert( scale >= 0.0 && scale <= 1.0 );
  8310. bias *= scale;
  8311. }
  8312. else if ( timeSinceReacquire < timeToFocus ) // handled seperately as might be tuning seperately
  8313. {
  8314. float scale = timeSinceReacquire / timeToFocus;
  8315. Assert( scale >= 0.0 && scale <= 1.0 );
  8316. bias *= scale;
  8317. }
  8318. }
  8319. }
  8320. return bias;
  8321. }
  8322. //-----------------------------------------------------------------------------
  8323. Vector CAI_BaseNPC::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
  8324. {
  8325. Vector baseResult = BaseClass::GetAttackSpread( pWeapon, pTarget );
  8326. AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget );
  8327. if ( pEnemyInfo )
  8328. {
  8329. float timeToFocus = ai_spread_cone_focus_time.GetFloat();
  8330. if ( timeToFocus > 0.0 )
  8331. {
  8332. float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy;
  8333. if ( timeSinceValidEnemy < 0 )
  8334. timeSinceValidEnemy = 0;
  8335. if ( timeSinceValidEnemy < timeToFocus )
  8336. {
  8337. float coneMultiplier = ai_spread_defocused_cone_multiplier.GetFloat();
  8338. if ( coneMultiplier > 1.0 )
  8339. {
  8340. float scale = 1.0 - timeSinceValidEnemy / timeToFocus;
  8341. Assert( scale >= 0.0 && scale <= 1.0 );
  8342. float multiplier = ( (coneMultiplier - 1.0) * scale ) + 1.0;
  8343. baseResult *= multiplier;
  8344. }
  8345. }
  8346. }
  8347. }
  8348. return baseResult;
  8349. }
  8350. //-----------------------------------------------------------------------------
  8351. // Similar to calling GetShootEnemyDir, but returns the exact trajectory to
  8352. // fire the bullet along, after calculating for target speed, location,
  8353. // concealment, etc.
  8354. //
  8355. // Ultimately, this code results in the shooter aiming his weapon somewhere ahead of
  8356. // a moving target. Since bullet traces are instant, aiming ahead of a target
  8357. // will result in more misses than hits. This is designed to provide targets with
  8358. // a bonus when moving perpendicular to the shooter's line of sight.
  8359. //
  8360. // Do not confuse this with leading a target in an effort to more easily hit it.
  8361. //
  8362. // This code PENALIZES a target for moving directly along the shooter's line of sight.
  8363. //
  8364. // This code REWARDS a target for moving perpendicular to the shooter's line of sight.
  8365. //-----------------------------------------------------------------------------
  8366. Vector CAI_BaseNPC::GetActualShootTrajectory( const Vector &shootOrigin )
  8367. {
  8368. if( !GetEnemy() )
  8369. return GetShootEnemyDir( shootOrigin );
  8370. // If we're above water shooting at a player underwater, bias some of the shots forward of
  8371. // the player so that they see the cool bubble trails in the water ahead of them.
  8372. if (GetEnemy()->IsPlayer() && (GetWaterLevel() != WL_Eyes) && (GetEnemy()->GetWaterLevel() == WL_Eyes))
  8373. {
  8374. #if 1
  8375. if (random->RandomInt(0, 4) < 3)
  8376. {
  8377. Vector vecEnemyForward;
  8378. GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL );
  8379. vecEnemyForward.z = 0;
  8380. // Lead up to a second ahead of them unless they are moving backwards.
  8381. Vector vecEnemyVelocity = GetEnemy()->GetSmoothedVelocity();
  8382. VectorNormalize( vecEnemyVelocity );
  8383. float flVelocityScale = vecEnemyForward.Dot( vecEnemyVelocity );
  8384. if ( flVelocityScale < 0.0f )
  8385. {
  8386. flVelocityScale = 0.0f;
  8387. }
  8388. Vector vecAimPos = GetEnemy()->EyePosition() + ( 48.0f * vecEnemyForward ) + (flVelocityScale * GetEnemy()->GetSmoothedVelocity() );
  8389. //NDebugOverlay::Cross3D(vecAimPos, Vector(-16,-16,-16), Vector(16,16,16), 255, 255, 0, true, 1.0f );
  8390. //vecAimPos.z = UTIL_WaterLevel( vecAimPos, vecAimPos.z, vecAimPos.z + 400.0f );
  8391. //NDebugOverlay::Cross3D(vecAimPos, Vector(-32,-32,-32), Vector(32,32,32), 255, 0, 0, true, 1.0f );
  8392. Vector vecShotDir = vecAimPos - shootOrigin;
  8393. VectorNormalize( vecShotDir );
  8394. return vecShotDir;
  8395. }
  8396. #else
  8397. if (random->RandomInt(0, 4) < 3)
  8398. {
  8399. // Aim at a point a few feet in front of the player's eyes
  8400. Vector vecEnemyForward;
  8401. GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL );
  8402. Vector vecAimPos = GetEnemy()->EyePosition() + (120.0f * vecEnemyForward );
  8403. Vector vecShotDir = vecAimPos - shootOrigin;
  8404. VectorNormalize( vecShotDir );
  8405. CShotManipulator manipulator( vecShotDir );
  8406. manipulator.ApplySpread( VECTOR_CONE_10DEGREES, 1 );
  8407. vecShotDir = manipulator.GetResult();
  8408. return vecShotDir;
  8409. }
  8410. #endif
  8411. }
  8412. Vector vecProjectedPosition = GetActualShootPosition( shootOrigin );
  8413. Vector shotDir = vecProjectedPosition - shootOrigin;
  8414. VectorNormalize( shotDir );
  8415. CollectShotStats( shootOrigin, shotDir );
  8416. // NOW we have a shoot direction. Where a 100% accurate bullet should go.
  8417. // Modify it by weapon proficiency.
  8418. // construct a manipulator
  8419. CShotManipulator manipulator( shotDir );
  8420. bool bUsePerfectAccuracy = false;
  8421. #ifdef HL2_DLL
  8422. // Apply appropriate accuracy.
  8423. if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
  8424. {
  8425. CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(GetEnemy());
  8426. if ( pBullseye && pBullseye->UsePerfectAccuracy() )
  8427. {
  8428. bUsePerfectAccuracy = true;
  8429. }
  8430. }
  8431. #endif // HL2_DLL
  8432. if ( !bUsePerfectAccuracy )
  8433. {
  8434. manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) );
  8435. shotDir = manipulator.GetResult();
  8436. }
  8437. // Look for an opportunity to make misses hit interesting things.
  8438. CBaseCombatCharacter *pEnemy;
  8439. pEnemy = GetEnemy()->MyCombatCharacterPointer();
  8440. if( pEnemy && pEnemy->ShouldShootMissTarget( this ) )
  8441. {
  8442. Vector vecEnd = shootOrigin + shotDir * 8192;
  8443. trace_t tr;
  8444. AI_TraceLine(shootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  8445. if( tr.fraction != 1.0 && tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO )
  8446. {
  8447. // Hit something we can harm. Just shoot it.
  8448. return manipulator.GetResult();
  8449. }
  8450. // Find something interesting around the enemy to shoot instead of just missing.
  8451. CBaseEntity *pMissTarget = pEnemy->FindMissTarget();
  8452. if( pMissTarget )
  8453. {
  8454. shotDir = pMissTarget->WorldSpaceCenter() - shootOrigin;
  8455. VectorNormalize( shotDir );
  8456. }
  8457. }
  8458. return shotDir;
  8459. }
  8460. //-----------------------------------------------------------------------------
  8461. Vector CAI_BaseNPC::BodyTarget( const Vector &posSrc, bool bNoisy )
  8462. {
  8463. Vector low = WorldSpaceCenter() - ( WorldSpaceCenter() - GetAbsOrigin() ) * .25;
  8464. Vector high = EyePosition();
  8465. Vector delta = high - low;
  8466. Vector result;
  8467. if ( bNoisy )
  8468. {
  8469. // bell curve
  8470. float rand1 = random->RandomFloat( 0.0, 0.5 );
  8471. float rand2 = random->RandomFloat( 0.0, 0.5 );
  8472. result = low + delta * rand1 + delta * rand2;
  8473. }
  8474. else
  8475. result = low + delta * 0.5;
  8476. return result;
  8477. }
  8478. //-----------------------------------------------------------------------------
  8479. bool CAI_BaseNPC::ShouldMoveAndShoot()
  8480. {
  8481. return ( ( CapabilitiesGet() & bits_CAP_MOVE_SHOOT ) != 0 );
  8482. }
  8483. //=========================================================
  8484. // FacingIdeal - tells us if a npc is facing its ideal
  8485. // yaw. Created this function because many spots in the
  8486. // code were checking the yawdiff against this magic
  8487. // number. Nicer to have it in one place if we're gonna
  8488. // be stuck with it.
  8489. //=========================================================
  8490. bool CAI_BaseNPC::FacingIdeal( float flTolerance )
  8491. {
  8492. flTolerance = MAX( flTolerance, 0.006f ); //!!!BUGBUG - no magic numbers!!!
  8493. if ( fabs( GetMotor()->DeltaIdealYaw() ) <= flTolerance )
  8494. {
  8495. return true;
  8496. }
  8497. return false;
  8498. }
  8499. //---------------------------------
  8500. void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp )
  8501. {
  8502. GetMotor()->AddFacingTarget( pTarget, flImportance, flDuration, flRamp );
  8503. }
  8504. void CAI_BaseNPC::AddFacingTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
  8505. {
  8506. GetMotor()->AddFacingTarget( vecPosition, flImportance, flDuration, flRamp );
  8507. }
  8508. void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
  8509. {
  8510. GetMotor()->AddFacingTarget( pTarget, vecPosition, flImportance, flDuration, flRamp );
  8511. }
  8512. float CAI_BaseNPC::GetFacingDirection( Vector &vecDir )
  8513. {
  8514. return (GetMotor()->GetFacingDirection( vecDir ));
  8515. }
  8516. //---------------------------------
  8517. int CAI_BaseNPC::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener )
  8518. {
  8519. int sentenceIndex = -1;
  8520. if ( pszSentence && IsAlive() )
  8521. {
  8522. if ( pszSentence[0] == '!' )
  8523. {
  8524. sentenceIndex = SENTENCEG_Lookup( pszSentence );
  8525. CPASAttenuationFilter filter( this, soundlevel );
  8526. CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, volume, soundlevel, 0, PITCH_NORM );
  8527. }
  8528. else
  8529. {
  8530. sentenceIndex = SENTENCEG_PlayRndSz( edict(), pszSentence, volume, soundlevel, 0, PITCH_NORM );
  8531. }
  8532. }
  8533. return sentenceIndex;
  8534. }
  8535. int CAI_BaseNPC::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
  8536. {
  8537. return PlaySentence( pszSentence, delay, volume, soundlevel, pListener );
  8538. }
  8539. //-----------------------------------------------------------------------------
  8540. // Purpose:
  8541. // Input :
  8542. // Output :
  8543. //-----------------------------------------------------------------------------
  8544. CBaseEntity *CAI_BaseNPC::FindNamedEntity( const char *name, IEntityFindFilter *pFilter )
  8545. {
  8546. if ( !stricmp( name, "!player" ))
  8547. {
  8548. return ( CBaseEntity * )AI_GetSinglePlayer();
  8549. }
  8550. else if ( !stricmp( name, "!enemy" ) )
  8551. {
  8552. if (GetEnemy() != NULL)
  8553. return GetEnemy();
  8554. }
  8555. else if ( !stricmp( name, "!self" ) || !stricmp( name, "!target1" ) )
  8556. {
  8557. return this;
  8558. }
  8559. else if ( !stricmp( name, "!nearestfriend" ) || !stricmp( name, "!friend" ) )
  8560. {
  8561. // FIXME: look at CBaseEntity *CNPCSimpleTalker::FindNearestFriend(bool fPlayer)
  8562. // punt for now
  8563. return ( CBaseEntity * )AI_GetSinglePlayer();
  8564. }
  8565. else if (!stricmp( name, "self" ))
  8566. {
  8567. static int selfwarningcount = 0;
  8568. // fix the vcd, the reserved names have changed
  8569. if ( ++selfwarningcount < 5 )
  8570. {
  8571. DevMsg( "ERROR: \"self\" is no longer used, use \"!self\" in vcd instead!\n" );
  8572. }
  8573. return this;
  8574. }
  8575. else if ( !stricmp( name, "Player" ))
  8576. {
  8577. static int playerwarningcount = 0;
  8578. if ( ++playerwarningcount < 5 )
  8579. {
  8580. DevMsg( "ERROR: \"player\" is no longer used, use \"!player\" in vcd instead!\n" );
  8581. }
  8582. return ( CBaseEntity * )AI_GetSinglePlayer();
  8583. }
  8584. else
  8585. {
  8586. // search for up to 32 entities with the same name and choose one randomly
  8587. CBaseEntity *entityList[ FINDNAMEDENTITY_MAX_ENTITIES ];
  8588. CBaseEntity *entity;
  8589. int iCount;
  8590. entity = NULL;
  8591. for( iCount = 0; iCount < FINDNAMEDENTITY_MAX_ENTITIES; iCount++ )
  8592. {
  8593. entity = gEntList.FindEntityByName( entity, name, NULL, NULL, NULL, pFilter );
  8594. if ( !entity )
  8595. {
  8596. break;
  8597. }
  8598. entityList[ iCount ] = entity;
  8599. }
  8600. if ( iCount > 0 )
  8601. {
  8602. int index = RandomInt( 0, iCount - 1 );
  8603. entity = entityList[ index ];
  8604. return entity;
  8605. }
  8606. }
  8607. return NULL;
  8608. }
  8609. void CAI_BaseNPC::CorpseFallThink( void )
  8610. {
  8611. if ( GetFlags() & FL_ONGROUND )
  8612. {
  8613. SetThink ( NULL );
  8614. SetSequenceBox( );
  8615. }
  8616. else
  8617. {
  8618. SetNextThink( gpGlobals->curtime + 0.1f );
  8619. }
  8620. }
  8621. // Call after animation/pose is set up
  8622. void CAI_BaseNPC::NPCInitDead( void )
  8623. {
  8624. InitBoneControllers();
  8625. RemoveSolidFlags( FSOLID_NOT_SOLID );
  8626. // so he'll fall to ground
  8627. SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
  8628. SetCycle( 0 );
  8629. ResetSequenceInfo( );
  8630. m_flPlaybackRate = 0;
  8631. // Copy health
  8632. m_iMaxHealth = m_iHealth;
  8633. m_lifeState = LIFE_DEAD;
  8634. UTIL_SetSize(this, vec3_origin, vec3_origin );
  8635. // Setup health counters, etc.
  8636. SetThink( &CAI_BaseNPC::CorpseFallThink );
  8637. SetNextThink( gpGlobals->curtime + 0.5f );
  8638. }
  8639. //=========================================================
  8640. // BBoxIsFlat - check to see if the npc's bounding box
  8641. // is lying flat on a surface (traces from all four corners
  8642. // are same length.)
  8643. //=========================================================
  8644. bool CAI_BaseNPC::BBoxFlat ( void )
  8645. {
  8646. trace_t tr;
  8647. Vector vecPoint;
  8648. float flXSize, flYSize;
  8649. float flLength;
  8650. float flLength2;
  8651. flXSize = WorldAlignSize().x / 2;
  8652. flYSize = WorldAlignSize().y / 2;
  8653. vecPoint.x = GetAbsOrigin().x + flXSize;
  8654. vecPoint.y = GetAbsOrigin().y + flYSize;
  8655. vecPoint.z = GetAbsOrigin().z;
  8656. AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), GetAITraceMask_BrushOnly(), this, COLLISION_GROUP_NONE, &tr );
  8657. flLength = (vecPoint - tr.endpos).Length();
  8658. vecPoint.x = GetAbsOrigin().x - flXSize;
  8659. vecPoint.y = GetAbsOrigin().y - flYSize;
  8660. AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), GetAITraceMask_BrushOnly(), this, COLLISION_GROUP_NONE, &tr );
  8661. flLength2 = (vecPoint - tr.endpos).Length();
  8662. if ( flLength2 > flLength )
  8663. {
  8664. return false;
  8665. }
  8666. flLength = flLength2;
  8667. vecPoint.x = GetAbsOrigin().x - flXSize;
  8668. vecPoint.y = GetAbsOrigin().y + flYSize;
  8669. AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), GetAITraceMask_BrushOnly(), this, COLLISION_GROUP_NONE, &tr );
  8670. flLength2 = (vecPoint - tr.endpos).Length();
  8671. if ( flLength2 > flLength )
  8672. {
  8673. return false;
  8674. }
  8675. flLength = flLength2;
  8676. vecPoint.x = GetAbsOrigin().x + flXSize;
  8677. vecPoint.y = GetAbsOrigin().y - flYSize;
  8678. AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), GetAITraceMask_BrushOnly(), this, COLLISION_GROUP_NONE, &tr );
  8679. flLength2 = (vecPoint - tr.endpos).Length();
  8680. if ( flLength2 > flLength )
  8681. {
  8682. return false;
  8683. }
  8684. flLength = flLength2;
  8685. return true;
  8686. }
  8687. //-----------------------------------------------------------------------------
  8688. // Purpose:
  8689. // Input : *pEnemy -
  8690. // bSetCondNewEnemy -
  8691. //-----------------------------------------------------------------------------
  8692. void CAI_BaseNPC::SetEnemy( CBaseEntity *pEnemy, bool bSetCondNewEnemy )
  8693. {
  8694. if (m_hEnemy != pEnemy)
  8695. {
  8696. ClearAttackConditions( );
  8697. VacateStrategySlot();
  8698. m_GiveUpOnDeadEnemyTimer.Stop();
  8699. // If we've just found a new enemy, set the condition
  8700. if ( pEnemy && bSetCondNewEnemy )
  8701. {
  8702. SetCondition( COND_NEW_ENEMY );
  8703. }
  8704. OnEnemyChanged( m_hEnemy.Get(), pEnemy );
  8705. }
  8706. // Assert( (pEnemy == NULL) || (m_NPCState == NPC_STATE_COMBAT) );
  8707. m_hEnemy = pEnemy;
  8708. m_flTimeEnemyAcquired = gpGlobals->curtime;
  8709. m_LastShootAccuracy = -1;
  8710. m_TotalShots = 0;
  8711. m_TotalHits = 0;
  8712. if ( !pEnemy )
  8713. ClearCondition( COND_NEW_ENEMY );
  8714. }
  8715. const Vector &CAI_BaseNPC::GetEnemyLKP() const
  8716. {
  8717. return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->LastKnownPosition( GetEnemy() );
  8718. }
  8719. float CAI_BaseNPC::GetEnemyLastTimeSeen() const
  8720. {
  8721. return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->LastTimeSeen( GetEnemy() );
  8722. }
  8723. void CAI_BaseNPC::MarkEnemyAsEluded()
  8724. {
  8725. GetEnemies()->MarkAsEluded( GetEnemy() );
  8726. }
  8727. void CAI_BaseNPC::ClearEnemyMemory()
  8728. {
  8729. GetEnemies()->ClearMemory( GetEnemy() );
  8730. }
  8731. bool CAI_BaseNPC::EnemyHasEludedMe() const
  8732. {
  8733. return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->HasEludedMe( GetEnemy() );
  8734. }
  8735. void CAI_BaseNPC::SetTarget( CBaseEntity *pTarget )
  8736. {
  8737. m_hTargetEnt = pTarget;
  8738. }
  8739. //=========================================================
  8740. // Choose Enemy - tries to find the best suitable enemy for the npc.
  8741. //=========================================================
  8742. bool CAI_BaseNPC::ShouldChooseNewEnemy()
  8743. {
  8744. CBaseEntity *pEnemy = GetEnemy();
  8745. if ( pEnemy )
  8746. {
  8747. if ( GetEnemies()->GetSerialNumber() != m_EnemiesSerialNumber )
  8748. {
  8749. return true;
  8750. }
  8751. m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber();
  8752. if ( EnemyHasEludedMe() || (IRelationType( pEnemy ) != D_HT && IRelationType( pEnemy ) != D_FR) || !IsValidEnemy( pEnemy ) )
  8753. {
  8754. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (1)\n" );
  8755. return true;
  8756. }
  8757. if ( HasCondition(COND_SEE_HATE) || HasCondition(COND_SEE_DISLIKE) || HasCondition(COND_SEE_NEMESIS) || HasCondition(COND_SEE_FEAR) )
  8758. {
  8759. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (2)\n" );
  8760. return true;
  8761. }
  8762. if ( !pEnemy->IsAlive() )
  8763. {
  8764. if ( m_GiveUpOnDeadEnemyTimer.IsRunning() )
  8765. {
  8766. if ( m_GiveUpOnDeadEnemyTimer.Expired() )
  8767. {
  8768. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (3)\n" );
  8769. return true;
  8770. }
  8771. }
  8772. else
  8773. m_GiveUpOnDeadEnemyTimer.Start();
  8774. }
  8775. AI_EnemyInfo_t *pInfo = GetEnemies()->Find( pEnemy );
  8776. if ( m_FailChooseEnemyTimer.Expired() )
  8777. {
  8778. m_FailChooseEnemyTimer.Set( 1.5 );
  8779. if ( HasCondition( COND_TASK_FAILED ) ||
  8780. ( pInfo && ( pInfo->timeAtFirstHand == AI_INVALID_TIME || gpGlobals->curtime - pInfo->timeLastSeen > 10 ) ) )
  8781. {
  8782. return true;
  8783. }
  8784. }
  8785. if ( pInfo && pInfo->timeValidEnemy < gpGlobals->curtime )
  8786. {
  8787. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> false\n" );
  8788. return false;
  8789. }
  8790. }
  8791. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (4)\n" );
  8792. m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber();
  8793. return true;
  8794. }
  8795. //-------------------------------------
  8796. bool CAI_BaseNPC::ChooseEnemy( void )
  8797. {
  8798. AI_PROFILE_SCOPE(CAI_Enemies_ChooseEnemy);
  8799. DbgEnemyMsg( this, "ChooseEnemy() {\n" );
  8800. //---------------------------------
  8801. //
  8802. // Gather initial conditions
  8803. //
  8804. CBaseEntity *pInitialEnemy = GetEnemy();
  8805. CBaseEntity *pChosenEnemy = pInitialEnemy;
  8806. // Use memory bits in case enemy pointer altered outside this function, (e.g., ehandle goes NULL)
  8807. bool fHadEnemy = ( HasMemory( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER ) );
  8808. bool fEnemyWasPlayer = HasMemory( bits_MEMORY_HAD_PLAYER );
  8809. bool fEnemyWentNull = ( fHadEnemy && !pInitialEnemy );
  8810. bool fEnemyEluded = ( fEnemyWentNull || ( pInitialEnemy && GetEnemies()->HasEludedMe( pInitialEnemy ) ) );
  8811. //---------------------------------
  8812. //
  8813. // Establish suitability of choosing a new enemy
  8814. //
  8815. bool fHaveCondNewEnemy;
  8816. bool fHaveCondLostEnemy;
  8817. if ( !m_ScheduleState.bScheduleWasInterrupted && GetCurSchedule() && !FScheduleDone() )
  8818. {
  8819. Assert( InterruptFromCondition( COND_NEW_ENEMY ) == COND_NEW_ENEMY && InterruptFromCondition( COND_LOST_ENEMY ) == COND_LOST_ENEMY );
  8820. fHaveCondNewEnemy = GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY );
  8821. fHaveCondLostEnemy = GetCurSchedule()->HasInterrupt( COND_LOST_ENEMY );
  8822. // See if they've been added as a custom interrupt
  8823. if ( !fHaveCondNewEnemy )
  8824. {
  8825. fHaveCondNewEnemy = IsCustomInterruptConditionSet( COND_NEW_ENEMY );
  8826. }
  8827. if ( !fHaveCondLostEnemy )
  8828. {
  8829. fHaveCondLostEnemy = IsCustomInterruptConditionSet( COND_LOST_ENEMY );
  8830. }
  8831. }
  8832. else
  8833. {
  8834. fHaveCondNewEnemy = true; // not having a schedule is the same as being interruptable by any condition
  8835. fHaveCondLostEnemy = true;
  8836. }
  8837. if ( !fEnemyWentNull )
  8838. {
  8839. if ( !fHaveCondNewEnemy && !( fHaveCondLostEnemy && fEnemyEluded ) )
  8840. {
  8841. // DO NOT mess with the npc's enemy pointer unless the schedule the npc is currently
  8842. // running will be interrupted by COND_NEW_ENEMY or COND_LOST_ENEMY. This will
  8843. // eliminate the problem of npcs getting a new enemy while they are in a schedule
  8844. // that doesn't care, and then not realizing it by the time they get to a schedule
  8845. // that does. I don't feel this is a good permanent fix.
  8846. m_bSkippedChooseEnemy = true;
  8847. DbgEnemyMsg( this, "Skipped enemy selection due to schedule restriction\n" );
  8848. DbgEnemyMsg( this, "}\n" );
  8849. return ( pChosenEnemy != NULL );
  8850. }
  8851. }
  8852. else if ( !fHaveCondNewEnemy && !fHaveCondLostEnemy && GetCurSchedule() )
  8853. {
  8854. DevMsg( 2, "WARNING: AI enemy went NULL but schedule (%s) is not interested\n", GetCurSchedule()->GetName() );
  8855. }
  8856. m_bSkippedChooseEnemy = false;
  8857. //---------------------------------
  8858. //
  8859. // Select a target
  8860. //
  8861. if ( ShouldChooseNewEnemy() )
  8862. {
  8863. pChosenEnemy = BestEnemy();
  8864. }
  8865. //---------------------------------
  8866. //
  8867. // React to result of selection
  8868. //
  8869. bool fChangingEnemy = ( pChosenEnemy != pInitialEnemy );
  8870. if ( fChangingEnemy || fEnemyWentNull )
  8871. {
  8872. DbgEnemyMsg( this, "Enemy changed from %s to %s\n", pInitialEnemy->GetDebugName(), pChosenEnemy->GetDebugName() );
  8873. Forget( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER );
  8874. // Did our old enemy snuff it?
  8875. if ( pInitialEnemy && !pInitialEnemy->IsAlive() )
  8876. {
  8877. SetCondition( COND_ENEMY_DEAD );
  8878. }
  8879. SetEnemy( pChosenEnemy );
  8880. if ( fHadEnemy )
  8881. {
  8882. // Vacate any strategy slot on old enemy
  8883. VacateStrategySlot();
  8884. // Force output event for establishing LOS
  8885. Forget( bits_MEMORY_HAD_LOS );
  8886. // m_flLastAttackTime = 0;
  8887. }
  8888. if ( !pChosenEnemy )
  8889. {
  8890. // Don't break on enemies going null if they've been killed
  8891. if ( !HasCondition(COND_ENEMY_DEAD) )
  8892. {
  8893. SetCondition( COND_ENEMY_WENT_NULL );
  8894. }
  8895. if ( fEnemyEluded )
  8896. {
  8897. SetCondition( COND_LOST_ENEMY );
  8898. LostEnemySound();
  8899. }
  8900. if ( fEnemyWasPlayer )
  8901. {
  8902. m_OnLostPlayer.FireOutput( pInitialEnemy, this );
  8903. }
  8904. m_OnLostEnemy.FireOutput( pInitialEnemy, this);
  8905. }
  8906. else
  8907. {
  8908. Remember( ( pChosenEnemy->IsPlayer() ) ? bits_MEMORY_HAD_PLAYER : bits_MEMORY_HAD_ENEMY );
  8909. }
  8910. }
  8911. //---------------------------------
  8912. return ( pChosenEnemy != NULL );
  8913. }
  8914. //=========================================================
  8915. void CAI_BaseNPC::PickupWeapon( CBaseCombatWeapon *pWeapon )
  8916. {
  8917. pWeapon->OnPickedUp( this );
  8918. Weapon_Equip( pWeapon );
  8919. m_iszPendingWeapon = NULL_STRING;
  8920. }
  8921. //=========================================================
  8922. // DropItem - dead npc drops named item
  8923. //=========================================================
  8924. CBaseEntity *CAI_BaseNPC::DropItem ( char *pszItemName, Vector vecPos, QAngle vecAng )
  8925. {
  8926. if ( !pszItemName )
  8927. {
  8928. DevMsg( "DropItem() - No item name!\n" );
  8929. return NULL;
  8930. }
  8931. CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, this );
  8932. if ( pItem )
  8933. {
  8934. if ( g_pGameRules->IsAllowedToSpawn( pItem ) == false )
  8935. {
  8936. UTIL_Remove( pItem );
  8937. return NULL;
  8938. }
  8939. IPhysicsObject *pPhys = pItem->VPhysicsGetObject();
  8940. if ( pPhys )
  8941. {
  8942. // Add an extra push in a random direction
  8943. Vector vel = RandomVector( -64.0f, 64.0f );
  8944. AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f );
  8945. vel[2] = 0.0f;
  8946. pPhys->AddVelocity( &vel, &angImp );
  8947. }
  8948. else
  8949. {
  8950. // do we want this behavior to be default?! (sjb)
  8951. pItem->ApplyAbsVelocityImpulse( GetAbsVelocity() );
  8952. pItem->ApplyLocalAngularVelocityImpulse( AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 ) );
  8953. }
  8954. return pItem;
  8955. }
  8956. else
  8957. {
  8958. DevMsg( "DropItem() - Didn't create!\n" );
  8959. return NULL;
  8960. }
  8961. }
  8962. bool CAI_BaseNPC::ShouldFadeOnDeath( void )
  8963. {
  8964. if ( g_RagdollLVManager.IsLowViolence() )
  8965. {
  8966. return true;
  8967. }
  8968. else
  8969. {
  8970. // if flagged to fade out
  8971. return HasSpawnFlags(SF_NPC_FADE_CORPSE);
  8972. }
  8973. }
  8974. //-----------------------------------------------------------------------------
  8975. // Purpose: Indicates whether or not this npc should play an idle sound now.
  8976. //
  8977. //
  8978. // Output : Returns true if yes, false if no.
  8979. //-----------------------------------------------------------------------------
  8980. bool CAI_BaseNPC::ShouldPlayIdleSound( void )
  8981. {
  8982. if ( ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT ) &&
  8983. random->RandomInt(0,99) == 0 && !HasSpawnFlags(SF_NPC_GAG) )
  8984. {
  8985. return true;
  8986. }
  8987. return false;
  8988. }
  8989. //-----------------------------------------------------------------------------
  8990. // Purpose: Make a sound that other AI's can hear, to broadcast our presence
  8991. // Input : volume (radius) of the sound.
  8992. // Output :
  8993. //-----------------------------------------------------------------------------
  8994. void CAI_BaseNPC::MakeAIFootstepSound( float volume, float duration )
  8995. {
  8996. CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), volume, duration, this, SOUNDENT_CHANNEL_NPC_FOOTSTEP );
  8997. }
  8998. //-----------------------------------------------------------------------------
  8999. // Purpose:
  9000. // Input :
  9001. // Output :
  9002. //-----------------------------------------------------------------------------
  9003. bool CAI_BaseNPC::FOkToMakeSound( int soundPriority )
  9004. {
  9005. // ask the squad to filter sounds if I'm in one
  9006. if ( m_pSquad )
  9007. {
  9008. if ( !m_pSquad->FOkToMakeSound( soundPriority ) )
  9009. return false;
  9010. }
  9011. else
  9012. {
  9013. // otherwise, check my own sound timer
  9014. // Am I making uninterruptable sound?
  9015. if (gpGlobals->curtime <= m_flSoundWaitTime)
  9016. {
  9017. if ( soundPriority <= m_nSoundPriority )
  9018. return false;
  9019. }
  9020. }
  9021. // no talking outside of combat if gagged.
  9022. if ( HasSpawnFlags(SF_NPC_GAG) && ( m_NPCState != NPC_STATE_COMBAT ) )
  9023. return false;
  9024. return true;
  9025. }
  9026. //-----------------------------------------------------------------------------
  9027. // Purpose:
  9028. // Input :
  9029. // Output :
  9030. //-----------------------------------------------------------------------------
  9031. void CAI_BaseNPC::JustMadeSound( int soundPriority, float flSoundLength )
  9032. {
  9033. m_flSoundWaitTime = gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0);
  9034. m_nSoundPriority = soundPriority;
  9035. if (m_pSquad)
  9036. {
  9037. m_pSquad->JustMadeSound( soundPriority, gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0) );
  9038. }
  9039. }
  9040. Activity CAI_BaseNPC::GetStoppedActivity( void )
  9041. {
  9042. if (GetNavigator()->IsGoalActive())
  9043. {
  9044. Activity activity = GetNavigator()->GetArrivalActivity();
  9045. if (activity > ACT_RESET)
  9046. {
  9047. return activity;
  9048. }
  9049. }
  9050. return ACT_IDLE;
  9051. }
  9052. //=========================================================
  9053. //=========================================================
  9054. void CAI_BaseNPC::OnScheduleChange ( void )
  9055. {
  9056. EndTaskOverlay();
  9057. m_pNavigator->OnScheduleChange();
  9058. m_flMoveWaitFinished = 0;
  9059. VacateStrategySlot();
  9060. // If I still have have a route, clear it
  9061. // FIXME: Routes should only be cleared inside of tasks (kenb)
  9062. GetNavigator()->ClearGoal();
  9063. UnlockBestSound();
  9064. // If I locked a hint node clear it
  9065. if ( HasMemory(bits_MEMORY_LOCKED_HINT) && GetHintNode() != NULL)
  9066. {
  9067. float hintDelay = GetHintDelay(GetHintNode()->HintType());
  9068. GetHintNode()->Unlock(hintDelay);
  9069. SetHintNode( NULL );
  9070. }
  9071. }
  9072. CBaseCombatCharacter* CAI_BaseNPC::GetEnemyCombatCharacterPointer()
  9073. {
  9074. if ( GetEnemy() == NULL )
  9075. return NULL;
  9076. return GetEnemy()->MyCombatCharacterPointer();
  9077. }
  9078. // Global Savedata for npc
  9079. //
  9080. // This should be an exact copy of the var's in the header. Fields
  9081. // that aren't save/restored are commented out
  9082. BEGIN_DATADESC( CAI_BaseNPC )
  9083. // m_pSchedule (reacquired on restore)
  9084. DEFINE_EMBEDDED( m_ScheduleState ),
  9085. DEFINE_FIELD( m_IdealSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules
  9086. DEFINE_FIELD( m_failSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules
  9087. DEFINE_FIELD( m_bUsingStandardThinkTime, FIELD_BOOLEAN ),
  9088. DEFINE_FIELD( m_flLastRealThinkTime, FIELD_TIME ),
  9089. // m_iFrameBlocked (not saved)
  9090. // m_bInChoreo (not saved)
  9091. // m_bDoPostRestoreRefindPath (not saved)
  9092. // gm_flTimeLastSpawn (static)
  9093. // gm_nSpawnedThisFrame (static)
  9094. // m_Conditions (custom save)
  9095. // m_CustomInterruptConditions (custom save)
  9096. // m_ConditionsPreIgnore (custom save)
  9097. // m_InverseIgnoreConditions (custom save)
  9098. // m_poseAim_Pitch (not saved; recomputed on restore)
  9099. // m_poseAim_Yaw (not saved; recomputed on restore)
  9100. // m_poseMove_Yaw (not saved; recomputed on restore)
  9101. DEFINE_FIELD( m_flTimePingEffect, FIELD_TIME ),
  9102. DEFINE_FIELD( m_flFrozenMoveBlock, FIELD_FLOAT ),
  9103. DEFINE_FIELD( m_bForceConditionsGather, FIELD_BOOLEAN ),
  9104. DEFINE_FIELD( m_bConditionsGathered, FIELD_BOOLEAN ),
  9105. DEFINE_FIELD( m_bSkippedChooseEnemy, FIELD_BOOLEAN ),
  9106. DEFINE_FIELD( m_NPCState, FIELD_INTEGER ),
  9107. DEFINE_FIELD( m_IdealNPCState, FIELD_INTEGER ),
  9108. DEFINE_FIELD( m_flLastStateChangeTime, FIELD_TIME ),
  9109. DEFINE_FIELD( m_Efficiency, FIELD_INTEGER ),
  9110. DEFINE_FIELD( m_MoveEfficiency, FIELD_INTEGER ),
  9111. DEFINE_FIELD( m_flNextDecisionTime, FIELD_TIME ),
  9112. DEFINE_KEYFIELD( m_SleepState, FIELD_INTEGER, "sleepstate" ),
  9113. DEFINE_FIELD( m_SleepFlags, FIELD_INTEGER ),
  9114. DEFINE_KEYFIELD( m_flWakeRadius, FIELD_FLOAT, "wakeradius" ),
  9115. DEFINE_KEYFIELD( m_bWakeSquad, FIELD_BOOLEAN, "wakesquad" ),
  9116. DEFINE_FIELD( m_nWakeTick, FIELD_TICK ),
  9117. DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ),
  9118. DEFINE_CUSTOM_FIELD( m_translatedActivity, ActivityDataOps() ),
  9119. DEFINE_CUSTOM_FIELD( m_IdealActivity, ActivityDataOps() ),
  9120. DEFINE_CUSTOM_FIELD( m_IdealTranslatedActivity, ActivityDataOps() ),
  9121. DEFINE_CUSTOM_FIELD( m_IdealWeaponActivity, ActivityDataOps() ),
  9122. DEFINE_FIELD( m_nIdealSequence, FIELD_INTEGER ),
  9123. DEFINE_EMBEDDEDBYREF( m_pSenses ),
  9124. DEFINE_EMBEDDEDBYREF( m_pLockedBestSound ),
  9125. DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ),
  9126. DEFINE_FIELD( m_flTimeEnemyAcquired, FIELD_TIME ),
  9127. DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ),
  9128. DEFINE_EMBEDDED( m_GiveUpOnDeadEnemyTimer ),
  9129. DEFINE_EMBEDDED( m_FailChooseEnemyTimer ),
  9130. DEFINE_FIELD( m_EnemiesSerialNumber, FIELD_INTEGER ),
  9131. DEFINE_FIELD( m_flAcceptableTimeSeenEnemy, FIELD_TIME ),
  9132. DEFINE_EMBEDDED( m_UpdateEnemyPosTimer ),
  9133. // m_flTimeAnyUpdateEnemyPos (static)
  9134. DEFINE_FIELD( m_vecCommandGoal, FIELD_VECTOR ),
  9135. DEFINE_EMBEDDED( m_CommandMoveMonitor ),
  9136. DEFINE_FIELD( m_flSoundWaitTime, FIELD_TIME ),
  9137. DEFINE_FIELD( m_nSoundPriority, FIELD_INTEGER ),
  9138. DEFINE_FIELD( m_flIgnoreDangerSoundsUntil, FIELD_TIME ),
  9139. DEFINE_FIELD( m_afCapability, FIELD_INTEGER ),
  9140. DEFINE_FIELD( m_flMoveWaitFinished, FIELD_TIME ),
  9141. DEFINE_FIELD( m_hOpeningDoor, FIELD_EHANDLE ),
  9142. DEFINE_EMBEDDEDBYREF( m_pNavigator ),
  9143. DEFINE_EMBEDDEDBYREF( m_pLocalNavigator ),
  9144. DEFINE_EMBEDDEDBYREF( m_pPathfinder ),
  9145. DEFINE_EMBEDDEDBYREF( m_pMoveProbe ),
  9146. DEFINE_EMBEDDEDBYREF( m_pMotor ),
  9147. DEFINE_UTLVECTOR(m_UnreachableEnts, FIELD_EMBEDDED),
  9148. DEFINE_FIELD( m_hInteractionPartner, FIELD_EHANDLE ),
  9149. DEFINE_FIELD( m_hLastInteractionTestTarget, FIELD_EHANDLE ),
  9150. DEFINE_FIELD( m_hForcedInteractionPartner, FIELD_EHANDLE ),
  9151. DEFINE_FIELD( m_flForcedInteractionTimeout, FIELD_TIME ),
  9152. DEFINE_FIELD( m_vecForcedWorldPosition, FIELD_POSITION_VECTOR ),
  9153. DEFINE_FIELD( m_bCannotDieDuringInteraction, FIELD_BOOLEAN ),
  9154. DEFINE_FIELD( m_iInteractionState, FIELD_INTEGER ),
  9155. DEFINE_FIELD( m_iInteractionPlaying, FIELD_INTEGER ),
  9156. DEFINE_UTLVECTOR(m_ScriptedInteractions,FIELD_EMBEDDED),
  9157. DEFINE_FIELD( m_flInteractionYaw, FIELD_FLOAT ),
  9158. DEFINE_EMBEDDED( m_CheckOnGroundTimer ),
  9159. DEFINE_FIELD( m_vDefaultEyeOffset, FIELD_VECTOR ),
  9160. DEFINE_FIELD( m_flNextEyeLookTime, FIELD_TIME ),
  9161. DEFINE_FIELD( m_flEyeIntegRate, FIELD_FLOAT ),
  9162. DEFINE_FIELD( m_vEyeLookTarget, FIELD_POSITION_VECTOR ),
  9163. DEFINE_FIELD( m_vCurEyeTarget, FIELD_POSITION_VECTOR ),
  9164. DEFINE_FIELD( m_hEyeLookTarget, FIELD_EHANDLE ),
  9165. DEFINE_FIELD( m_flHeadYaw, FIELD_FLOAT ),
  9166. DEFINE_FIELD( m_flHeadPitch, FIELD_FLOAT ),
  9167. DEFINE_FIELD( m_flOriginalYaw, FIELD_FLOAT ),
  9168. DEFINE_FIELD( m_bInAScript, FIELD_BOOLEAN ),
  9169. DEFINE_FIELD( m_scriptState, FIELD_INTEGER ),
  9170. DEFINE_FIELD( m_hCine, FIELD_EHANDLE ),
  9171. DEFINE_CUSTOM_FIELD( m_ScriptArrivalActivity, ActivityDataOps() ),
  9172. DEFINE_FIELD( m_strScriptArrivalSequence, FIELD_STRING ),
  9173. DEFINE_FIELD( m_flSceneTime, FIELD_TIME ),
  9174. DEFINE_FIELD( m_iszSceneCustomMoveSeq, FIELD_STRING ),
  9175. // m_pEnemies Saved specially in ai_saverestore.cpp
  9176. DEFINE_FIELD( m_afMemory, FIELD_INTEGER ),
  9177. DEFINE_FIELD( m_hEnemyOccluder, FIELD_EHANDLE ),
  9178. DEFINE_FIELD( m_flSumDamage, FIELD_FLOAT ),
  9179. DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ),
  9180. DEFINE_FIELD( m_flLastPlayerDamageTime, FIELD_TIME ),
  9181. DEFINE_FIELD( m_flLastSawPlayerTime, FIELD_TIME ),
  9182. DEFINE_FIELD( m_flLastAttackTime, FIELD_TIME ),
  9183. DEFINE_FIELD( m_flLastEnemyTime, FIELD_TIME ),
  9184. DEFINE_FIELD( m_flNextWeaponSearchTime, FIELD_TIME ),
  9185. DEFINE_FIELD( m_iszPendingWeapon, FIELD_STRING ),
  9186. DEFINE_KEYFIELD( m_bIgnoreUnseenEnemies, FIELD_BOOLEAN , "ignoreunseenenemies"),
  9187. DEFINE_EMBEDDED( m_ShotRegulator ),
  9188. DEFINE_FIELD( m_iDesiredWeaponState, FIELD_INTEGER ),
  9189. // m_pSquad Saved specially in ai_saverestore.cpp
  9190. DEFINE_KEYFIELD(m_SquadName, FIELD_STRING, "squadname" ),
  9191. DEFINE_FIELD( m_iMySquadSlot, FIELD_INTEGER ),
  9192. DEFINE_KEYFIELD( m_strHintGroup, FIELD_STRING, "hintgroup" ),
  9193. DEFINE_KEYFIELD( m_bHintGroupNavLimiting, FIELD_BOOLEAN, "hintlimiting" ),
  9194. DEFINE_EMBEDDEDBYREF( m_pTacticalServices ),
  9195. DEFINE_FIELD( m_flWaitFinished, FIELD_TIME ),
  9196. DEFINE_FIELD( m_flNextFlinchTime, FIELD_TIME ),
  9197. DEFINE_FIELD( m_flNextDodgeTime, FIELD_TIME ),
  9198. DEFINE_EMBEDDED( m_MoveAndShootOverlay ),
  9199. DEFINE_FIELD( m_vecLastPosition, FIELD_POSITION_VECTOR ),
  9200. DEFINE_FIELD( m_vSavePosition, FIELD_POSITION_VECTOR ),
  9201. DEFINE_FIELD( m_vInterruptSavePosition, FIELD_POSITION_VECTOR ),
  9202. DEFINE_FIELD( m_pHintNode, FIELD_EHANDLE),
  9203. DEFINE_FIELD( m_cAmmoLoaded, FIELD_INTEGER ),
  9204. DEFINE_FIELD( m_flDistTooFar, FIELD_FLOAT ),
  9205. DEFINE_FIELD( m_hGoalEnt, FIELD_EHANDLE ),
  9206. DEFINE_FIELD( m_flTimeLastMovement, FIELD_TIME ),
  9207. DEFINE_KEYFIELD(m_spawnEquipment, FIELD_STRING, "additionalequipment" ),
  9208. DEFINE_FIELD( m_fNoDamageDecal, FIELD_BOOLEAN ),
  9209. DEFINE_FIELD( m_hStoredPathTarget, FIELD_EHANDLE ),
  9210. DEFINE_FIELD( m_vecStoredPathGoal, FIELD_POSITION_VECTOR ),
  9211. DEFINE_FIELD( m_nStoredPathType, FIELD_INTEGER ),
  9212. DEFINE_FIELD( m_fStoredPathFlags, FIELD_INTEGER ),
  9213. DEFINE_FIELD( m_bDidDeathCleanup, FIELD_BOOLEAN ),
  9214. DEFINE_FIELD( m_bCrouchDesired, FIELD_BOOLEAN ),
  9215. DEFINE_FIELD( m_bForceCrouch, FIELD_BOOLEAN ),
  9216. DEFINE_FIELD( m_bIsCrouching, FIELD_BOOLEAN ),
  9217. DEFINE_FIELD( m_bPerformAvoidance, FIELD_BOOLEAN ),
  9218. DEFINE_FIELD( m_bIsMoving, FIELD_BOOLEAN ),
  9219. DEFINE_FIELD( m_bFadeCorpse, FIELD_BOOLEAN ),
  9220. DEFINE_FIELD( m_iDeathPose, FIELD_INTEGER ),
  9221. DEFINE_FIELD( m_iDeathFrame, FIELD_INTEGER ),
  9222. DEFINE_FIELD( m_bCheckContacts, FIELD_BOOLEAN ),
  9223. DEFINE_FIELD( m_bSpeedModActive, FIELD_BOOLEAN ),
  9224. DEFINE_FIELD( m_iSpeedModRadius, FIELD_INTEGER ),
  9225. DEFINE_FIELD( m_iSpeedModSpeed, FIELD_INTEGER ),
  9226. DEFINE_FIELD( m_hEnemyFilter, FIELD_EHANDLE ),
  9227. DEFINE_KEYFIELD( m_iszEnemyFilterName, FIELD_STRING, "enemyfilter" ),
  9228. DEFINE_FIELD( m_bImportanRagdoll, FIELD_BOOLEAN ),
  9229. DEFINE_FIELD( m_bPlayerAvoidState, FIELD_BOOLEAN ),
  9230. // Satisfy classcheck
  9231. // DEFINE_FIELD( m_ScheduleHistory, CUtlVector < AIScheduleChoice_t > ),
  9232. // m_fIsUsingSmallHull TODO -- This needs more consideration than simple save/load
  9233. // m_failText DEBUG
  9234. // m_interruptText DEBUG
  9235. // m_failedSchedule DEBUG
  9236. // m_interuptSchedule DEBUG
  9237. // m_nDebugCurIndex DEBUG
  9238. // m_LastShootAccuracy DEBUG
  9239. // m_RecentShotAccuracy DEBUG
  9240. // m_TotalShots DEBUG
  9241. // m_TotalHits DEBUG
  9242. // m_bSelected DEBUG
  9243. // m_TimeLastShotMark DEBUG
  9244. // m_bDeferredNavigation
  9245. // Outputs
  9246. DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
  9247. DEFINE_OUTPUT( m_OnDeath, "OnDeath" ),
  9248. DEFINE_OUTPUT( m_OnHalfHealth, "OnHalfHealth" ),
  9249. DEFINE_OUTPUT( m_OnFoundEnemy, "OnFoundEnemy" ),
  9250. DEFINE_OUTPUT( m_OnLostEnemyLOS, "OnLostEnemyLOS" ),
  9251. DEFINE_OUTPUT( m_OnLostEnemy, "OnLostEnemy" ),
  9252. DEFINE_OUTPUT( m_OnFoundPlayer, "OnFoundPlayer" ),
  9253. DEFINE_OUTPUT( m_OnLostPlayerLOS, "OnLostPlayerLOS" ),
  9254. DEFINE_OUTPUT( m_OnLostPlayer, "OnLostPlayer" ),
  9255. DEFINE_OUTPUT( m_OnHearWorld, "OnHearWorld" ),
  9256. DEFINE_OUTPUT( m_OnHearPlayer, "OnHearPlayer" ),
  9257. DEFINE_OUTPUT( m_OnHearCombat, "OnHearCombat" ),
  9258. DEFINE_OUTPUT( m_OnDamagedByPlayer, "OnDamagedByPlayer" ),
  9259. DEFINE_OUTPUT( m_OnDamagedByPlayerSquad, "OnDamagedByPlayerSquad" ),
  9260. DEFINE_OUTPUT( m_OnDenyCommanderUse, "OnDenyCommanderUse" ),
  9261. DEFINE_OUTPUT( m_OnRappelTouchdown, "OnRappelTouchdown" ),
  9262. DEFINE_OUTPUT( m_OnWake, "OnWake" ),
  9263. DEFINE_OUTPUT( m_OnSleep, "OnSleep" ),
  9264. DEFINE_OUTPUT( m_OnForcedInteractionStarted, "OnForcedInteractionStarted" ),
  9265. DEFINE_OUTPUT( m_OnForcedInteractionAborted, "OnForcedInteractionAborted" ),
  9266. DEFINE_OUTPUT( m_OnForcedInteractionFinished, "OnForcedInteractionFinished" ),
  9267. // Inputs
  9268. DEFINE_INPUTFUNC( FIELD_STRING, "SetRelationship", InputSetRelationship ),
  9269. DEFINE_INPUTFUNC( FIELD_STRING, "SetEnemyFilter", InputSetEnemyFilter ),
  9270. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
  9271. DEFINE_INPUTFUNC( FIELD_VOID, "BeginRappel", InputBeginRappel ),
  9272. DEFINE_INPUTFUNC( FIELD_STRING, "SetSquad", InputSetSquad ),
  9273. DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ),
  9274. DEFINE_INPUTFUNC( FIELD_STRING, "ForgetEntity", InputForgetEntity ),
  9275. DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreDangerSounds", InputIgnoreDangerSounds ),
  9276. DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
  9277. DEFINE_INPUTFUNC( FIELD_VOID, "StartScripting", InputStartScripting ),
  9278. DEFINE_INPUTFUNC( FIELD_VOID, "StopScripting", InputStopScripting ),
  9279. DEFINE_INPUTFUNC( FIELD_VOID, "GagEnable", InputGagEnable ),
  9280. DEFINE_INPUTFUNC( FIELD_VOID, "GagDisable", InputGagDisable ),
  9281. DEFINE_INPUTFUNC( FIELD_VOID, "InsideTransition", InputInsideTransition ),
  9282. DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
  9283. DEFINE_INPUTFUNC( FIELD_VOID, "ActivateSpeedModifier", InputActivateSpeedModifier ),
  9284. DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpeedModifier", InputDisableSpeedModifier ),
  9285. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModRadius", InputSetSpeedModifierRadius ),
  9286. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModSpeed", InputSetSpeedModifierSpeed ),
  9287. DEFINE_INPUTFUNC( FIELD_VOID, "HolsterWeapon", InputHolsterWeapon ),
  9288. DEFINE_INPUTFUNC( FIELD_VOID, "HolsterAndDestroyWeapon", InputHolsterAndDestroyWeapon ),
  9289. DEFINE_INPUTFUNC( FIELD_VOID, "UnholsterWeapon", InputUnholsterWeapon ),
  9290. DEFINE_INPUTFUNC( FIELD_STRING, "ForceInteractionWithNPC", InputForceInteractionWithNPC ),
  9291. DEFINE_INPUTFUNC( FIELD_STRING, "UpdateEnemyMemory", InputUpdateEnemyMemory ),
  9292. DEFINE_INPUTFUNC( FIELD_STRING, "CreateAddon", InputCreateAddon ),
  9293. // Function pointers
  9294. DEFINE_USEFUNC( NPCUse ),
  9295. DEFINE_THINKFUNC( CallNPCThink ),
  9296. DEFINE_THINKFUNC( CorpseFallThink ),
  9297. DEFINE_THINKFUNC( NPCInitThink ),
  9298. END_DATADESC()
  9299. BEGIN_SIMPLE_DATADESC( AIScheduleState_t )
  9300. DEFINE_FIELD( iCurTask, FIELD_INTEGER ),
  9301. DEFINE_FIELD( fTaskStatus, FIELD_INTEGER ),
  9302. DEFINE_FIELD( timeStarted, FIELD_TIME ),
  9303. DEFINE_FIELD( timeCurTaskStarted, FIELD_TIME ),
  9304. DEFINE_FIELD( taskFailureCode, FIELD_INTEGER ),
  9305. DEFINE_FIELD( iTaskInterrupt, FIELD_INTEGER ),
  9306. DEFINE_FIELD( bTaskRanAutomovement, FIELD_BOOLEAN ),
  9307. DEFINE_FIELD( bTaskUpdatedYaw, FIELD_BOOLEAN ),
  9308. DEFINE_FIELD( bScheduleWasInterrupted, FIELD_BOOLEAN ),
  9309. END_DATADESC()
  9310. IMPLEMENT_SERVERCLASS_ST( CAI_BaseNPC, DT_AI_BaseNPC )
  9311. SendPropInt( SENDINFO( m_lifeState ), 3, SPROP_UNSIGNED ),
  9312. SendPropBool( SENDINFO( m_bPerformAvoidance ) ),
  9313. SendPropBool( SENDINFO( m_bIsMoving ) ),
  9314. SendPropBool( SENDINFO( m_bFadeCorpse ) ),
  9315. SendPropInt( SENDINFO( m_iDeathPose ), ANIMATION_SEQUENCE_BITS ),
  9316. SendPropInt( SENDINFO( m_iDeathFrame ), 5 ),
  9317. SendPropBool( SENDINFO( m_bSpeedModActive ) ),
  9318. SendPropInt( SENDINFO( m_iSpeedModRadius ) ),
  9319. SendPropInt( SENDINFO( m_iSpeedModSpeed ) ),
  9320. SendPropBool( SENDINFO( m_bImportanRagdoll ) ),
  9321. SendPropFloat( SENDINFO( m_flTimePingEffect ) ),
  9322. END_SEND_TABLE()
  9323. //-------------------------------------
  9324. BEGIN_SIMPLE_DATADESC( UnreachableEnt_t )
  9325. DEFINE_FIELD( hUnreachableEnt, FIELD_EHANDLE ),
  9326. DEFINE_FIELD( fExpireTime, FIELD_TIME ),
  9327. DEFINE_FIELD( vLocationWhenUnreachable, FIELD_POSITION_VECTOR ),
  9328. END_DATADESC()
  9329. //-------------------------------------
  9330. BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_Phases_t )
  9331. DEFINE_FIELD( iszSequence, FIELD_STRING ),
  9332. DEFINE_FIELD( iActivity, FIELD_INTEGER ),
  9333. END_DATADESC()
  9334. //-------------------------------------
  9335. BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t )
  9336. DEFINE_FIELD( iszInteractionName, FIELD_STRING ),
  9337. DEFINE_FIELD( iFlags, FIELD_INTEGER ),
  9338. DEFINE_FIELD( iTriggerMethod, FIELD_INTEGER ),
  9339. DEFINE_FIELD( iLoopBreakTriggerMethod, FIELD_INTEGER ),
  9340. DEFINE_FIELD( vecRelativeOrigin, FIELD_VECTOR ),
  9341. DEFINE_FIELD( angRelativeAngles, FIELD_VECTOR ),
  9342. DEFINE_FIELD( vecRelativeVelocity, FIELD_VECTOR ),
  9343. DEFINE_FIELD( flCameraDistance, FIELD_FLOAT ),
  9344. DEFINE_FIELD( angCameraAngles, FIELD_VECTOR ),
  9345. DEFINE_FIELD( flDelay, FIELD_FLOAT ),
  9346. DEFINE_FIELD( flDistSqr, FIELD_FLOAT ),
  9347. DEFINE_FIELD( iszMyWeapon, FIELD_STRING ),
  9348. DEFINE_FIELD( iszTheirWeapon, FIELD_STRING ),
  9349. DEFINE_EMBEDDED_ARRAY( sPhases, SNPCINT_NUM_PHASES ),
  9350. DEFINE_FIELD( matDesiredLocalToWorld, FIELD_VMATRIX ),
  9351. DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ),
  9352. DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ),
  9353. END_DATADESC()
  9354. //-------------------------------------
  9355. void CAI_BaseNPC::PostConstructor( const char *szClassname )
  9356. {
  9357. BaseClass::PostConstructor( szClassname );
  9358. CreateComponents();
  9359. }
  9360. //-----------------------------------------------------------------------------
  9361. // Purpose:
  9362. //-----------------------------------------------------------------------------
  9363. void CAI_BaseNPC::Activate( void )
  9364. {
  9365. BaseClass::Activate();
  9366. if ( GetModelPtr() )
  9367. {
  9368. ParseScriptedNPCInteractions();
  9369. }
  9370. // Get a handle to my enemy filter entity if there is one.
  9371. if ( m_iszEnemyFilterName != NULL_STRING )
  9372. {
  9373. CBaseEntity *pFilter = gEntList.FindEntityByName( NULL, m_iszEnemyFilterName );
  9374. if ( pFilter != NULL )
  9375. {
  9376. m_hEnemyFilter = dynamic_cast<CBaseFilter*>(pFilter);
  9377. }
  9378. }
  9379. #ifdef AI_MONITOR_FOR_OSCILLATION
  9380. m_ScheduleHistory.RemoveAll();
  9381. #endif//AI_MONITOR_FOR_OSCILLATION
  9382. }
  9383. void CAI_BaseNPC::Precache( void )
  9384. {
  9385. gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls
  9386. if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0") )
  9387. {
  9388. UTIL_PrecacheOther( STRING(m_spawnEquipment) );
  9389. }
  9390. // Make sure schedules are loaded for this NPC type
  9391. if (!LoadedSchedules())
  9392. {
  9393. DevMsg("ERROR: Rejecting spawn of %s as error in NPC's schedules.\n",GetDebugName());
  9394. UTIL_Remove(this);
  9395. return;
  9396. }
  9397. PrecacheScriptSound( "AI_BaseNPC.SwishSound" );
  9398. PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Heavy" );
  9399. PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Light" );
  9400. PrecacheScriptSound( "AI_BaseNPC.SentenceStop" );
  9401. BaseClass::Precache();
  9402. }
  9403. //-----------------------------------------------------------------------------
  9404. const short AI_EXTENDED_SAVE_HEADER_VERSION = 5;
  9405. const short AI_EXTENDED_SAVE_HEADER_RESET_VERSION = 3;
  9406. const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS = 2;
  9407. const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP = 3;
  9408. const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE = 4;
  9409. const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE = 5;
  9410. struct AIExtendedSaveHeader_t
  9411. {
  9412. AIExtendedSaveHeader_t()
  9413. : version(AI_EXTENDED_SAVE_HEADER_VERSION),
  9414. flags(0),
  9415. scheduleCrc(0)
  9416. {
  9417. szSchedule[0] = 0;
  9418. szIdealSchedule[0] = 0;
  9419. szFailSchedule[0] = 0;
  9420. szSequence[0] = 0;
  9421. }
  9422. short version;
  9423. unsigned flags;
  9424. char szSchedule[128];
  9425. CRC32_t scheduleCrc;
  9426. char szIdealSchedule[128];
  9427. char szFailSchedule[128];
  9428. char szSequence[128];
  9429. DECLARE_SIMPLE_DATADESC();
  9430. };
  9431. enum AIExtendedSaveHeaderFlags_t
  9432. {
  9433. AIESH_HAD_ENEMY = 0x01,
  9434. AIESH_HAD_TARGET = 0x02,
  9435. AIESH_HAD_NAVGOAL = 0x04,
  9436. };
  9437. //-------------------------------------
  9438. BEGIN_SIMPLE_DATADESC( AIExtendedSaveHeader_t )
  9439. DEFINE_FIELD( version, FIELD_SHORT ),
  9440. DEFINE_FIELD( flags, FIELD_INTEGER ),
  9441. DEFINE_AUTO_ARRAY( szSchedule, FIELD_CHARACTER ),
  9442. DEFINE_FIELD( scheduleCrc, FIELD_INTEGER ),
  9443. DEFINE_AUTO_ARRAY( szIdealSchedule, FIELD_CHARACTER ),
  9444. DEFINE_AUTO_ARRAY( szFailSchedule, FIELD_CHARACTER ),
  9445. DEFINE_AUTO_ARRAY( szSequence, FIELD_CHARACTER ),
  9446. END_DATADESC()
  9447. //-------------------------------------
  9448. int CAI_BaseNPC::Save( ISave &save )
  9449. {
  9450. AIExtendedSaveHeader_t saveHeader;
  9451. if ( GetEnemy() )
  9452. saveHeader.flags |= AIESH_HAD_ENEMY;
  9453. if ( GetTarget() )
  9454. saveHeader.flags |= AIESH_HAD_TARGET;
  9455. if ( GetNavigator()->IsGoalActive() )
  9456. saveHeader.flags |= AIESH_HAD_NAVGOAL;
  9457. if ( m_pSchedule )
  9458. {
  9459. const char *pszSchedule = m_pSchedule->GetName();
  9460. Assert( Q_strlen( pszSchedule ) < sizeof( saveHeader.szSchedule ) - 1 );
  9461. Q_strncpy( saveHeader.szSchedule, pszSchedule, sizeof( saveHeader.szSchedule ) );
  9462. CRC32_Init( &saveHeader.scheduleCrc );
  9463. CRC32_ProcessBuffer( &saveHeader.scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) );
  9464. CRC32_Final( &saveHeader.scheduleCrc );
  9465. }
  9466. else
  9467. {
  9468. saveHeader.szSchedule[0] = 0;
  9469. saveHeader.scheduleCrc = 0;
  9470. }
  9471. int idealSchedule = GetGlobalScheduleId( m_IdealSchedule );
  9472. if ( idealSchedule != -1 && idealSchedule != AI_RemapToGlobal( SCHED_NONE ) && idealSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) )
  9473. {
  9474. CAI_Schedule *pIdealSchedule = GetSchedule( m_IdealSchedule );
  9475. if ( pIdealSchedule )
  9476. {
  9477. const char *pszIdealSchedule = pIdealSchedule->GetName();
  9478. Assert( Q_strlen( pszIdealSchedule ) < sizeof( saveHeader.szIdealSchedule ) - 1 );
  9479. Q_strncpy( saveHeader.szIdealSchedule, pszIdealSchedule, sizeof( saveHeader.szIdealSchedule ) );
  9480. }
  9481. }
  9482. int failSchedule = GetGlobalScheduleId( m_failSchedule );
  9483. if ( failSchedule != -1 && failSchedule != AI_RemapToGlobal( SCHED_NONE ) && failSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) )
  9484. {
  9485. CAI_Schedule *pFailSchedule = GetSchedule( m_failSchedule );
  9486. if ( pFailSchedule )
  9487. {
  9488. const char *pszFailSchedule = pFailSchedule->GetName();
  9489. Assert( Q_strlen( pszFailSchedule ) < sizeof( saveHeader.szFailSchedule ) - 1 );
  9490. Q_strncpy( saveHeader.szFailSchedule, pszFailSchedule, sizeof( saveHeader.szFailSchedule ) );
  9491. }
  9492. }
  9493. if ( GetSequence() != ACT_INVALID && GetModelPtr() )
  9494. {
  9495. const char *pszSequenceName = GetSequenceName( GetSequence() );
  9496. if ( pszSequenceName && *pszSequenceName )
  9497. {
  9498. Assert( Q_strlen( pszSequenceName ) < sizeof( saveHeader.szSequence ) - 1 );
  9499. Q_strncpy( saveHeader.szSequence, pszSequenceName, sizeof(saveHeader.szSequence) );
  9500. }
  9501. }
  9502. save.WriteAll( &saveHeader );
  9503. save.StartBlock();
  9504. SaveConditions( save, m_Conditions );
  9505. SaveConditions( save, m_CustomInterruptConditions );
  9506. SaveConditions( save, m_ConditionsPreIgnore );
  9507. CAI_ScheduleBits ignoreConditions;
  9508. m_InverseIgnoreConditions.Not( &ignoreConditions );
  9509. SaveConditions( save, ignoreConditions );
  9510. save.EndBlock();
  9511. save.StartBlock();
  9512. GetNavigator()->Save( save );
  9513. save.EndBlock();
  9514. return BaseClass::Save(save);
  9515. }
  9516. //-------------------------------------
  9517. void CAI_BaseNPC::DiscardScheduleState()
  9518. {
  9519. // We don't save/restore routes yet
  9520. GetNavigator()->ClearGoal();
  9521. // We don't save/restore schedules yet
  9522. ClearSchedule( "Restoring NPC" );
  9523. // Reset animation
  9524. m_Activity = ACT_RESET;
  9525. // If we don't have an enemy, clear conditions like see enemy, etc.
  9526. if ( GetEnemy() == NULL )
  9527. {
  9528. m_Conditions.ClearAll();
  9529. }
  9530. // went across a transition and lost my m_hCine
  9531. bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL );
  9532. if ( bLostScript )
  9533. {
  9534. // UNDONE: Do something better here?
  9535. // for now, just go back to idle and let the AI figure out what to do.
  9536. SetState( NPC_STATE_IDLE );
  9537. SetIdealState( NPC_STATE_IDLE );
  9538. DevMsg(1, "Scripted Sequence stripped on level transition for %s\n", GetDebugName() );
  9539. }
  9540. }
  9541. //-------------------------------------
  9542. void CAI_BaseNPC::OnRestore()
  9543. {
  9544. gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls
  9545. if ( m_bDoPostRestoreRefindPath && CAI_NetworkManager::NetworksLoaded() )
  9546. {
  9547. CAI_DynamicLink::InitDynamicLinks();
  9548. if ( !GetNavigator()->RefindPathToGoal( false ) )
  9549. DiscardScheduleState();
  9550. }
  9551. else
  9552. {
  9553. GetNavigator()->ClearGoal();
  9554. }
  9555. BaseClass::OnRestore();
  9556. m_bCheckContacts = true;
  9557. }
  9558. //-------------------------------------
  9559. int CAI_BaseNPC::Restore( IRestore &restore )
  9560. {
  9561. AIExtendedSaveHeader_t saveHeader;
  9562. restore.ReadAll( &saveHeader );
  9563. if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS )
  9564. {
  9565. restore.StartBlock();
  9566. RestoreConditions( restore, &m_Conditions );
  9567. RestoreConditions( restore, &m_CustomInterruptConditions );
  9568. RestoreConditions( restore, &m_ConditionsPreIgnore );
  9569. CAI_ScheduleBits ignoreConditions;
  9570. RestoreConditions( restore, &ignoreConditions );
  9571. ignoreConditions.Not( &m_InverseIgnoreConditions );
  9572. restore.EndBlock();
  9573. }
  9574. if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE )
  9575. {
  9576. restore.StartBlock();
  9577. GetNavigator()->Restore( restore );
  9578. restore.EndBlock();
  9579. }
  9580. // do a normal restore
  9581. int status = BaseClass::Restore(restore);
  9582. if ( !status )
  9583. return 0;
  9584. // Do schedule fix-up
  9585. if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP )
  9586. {
  9587. if ( saveHeader.szIdealSchedule[0] )
  9588. {
  9589. CAI_Schedule *pIdealSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szIdealSchedule );
  9590. m_IdealSchedule = ( pIdealSchedule ) ? pIdealSchedule->GetId() : SCHED_NONE;
  9591. }
  9592. if ( saveHeader.szFailSchedule[0] )
  9593. {
  9594. CAI_Schedule *pFailSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szFailSchedule );
  9595. m_failSchedule = ( pFailSchedule ) ? pFailSchedule->GetId() : SCHED_NONE;
  9596. }
  9597. }
  9598. bool bLostSequence = false;
  9599. if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE && saveHeader.szSequence[0] && GetModelPtr() )
  9600. {
  9601. SetSequence( LookupSequence( saveHeader.szSequence ) );
  9602. if ( GetSequence() == ACT_INVALID )
  9603. {
  9604. DevMsg( this, AIMF_IGNORE_SELECTED, "Discarding missing sequence %s on load.\n", saveHeader.szSequence );
  9605. SetSequence( 0 );
  9606. bLostSequence = true;
  9607. }
  9608. Assert( IsValidSequence( GetSequence() ) );
  9609. }
  9610. bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL );
  9611. bool bDiscardScheduleState = ( bLostScript ||
  9612. bLostSequence ||
  9613. saveHeader.szSchedule[0] == 0 ||
  9614. saveHeader.version < AI_EXTENDED_SAVE_HEADER_RESET_VERSION ||
  9615. ( (saveHeader.flags & AIESH_HAD_ENEMY) && !GetEnemy() ) ||
  9616. ( (saveHeader.flags & AIESH_HAD_TARGET) && !GetTarget() ) );
  9617. if ( m_ScheduleState.taskFailureCode >= NUM_FAIL_CODES )
  9618. m_ScheduleState.taskFailureCode = FAIL_NO_TARGET; // must have been a string, gotta punt
  9619. if ( !bDiscardScheduleState )
  9620. {
  9621. m_pSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szSchedule );
  9622. if ( m_pSchedule )
  9623. {
  9624. CRC32_t scheduleCrc;
  9625. CRC32_Init( &scheduleCrc );
  9626. CRC32_ProcessBuffer( &scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) );
  9627. CRC32_Final( &scheduleCrc );
  9628. if ( scheduleCrc != saveHeader.scheduleCrc )
  9629. {
  9630. m_pSchedule = NULL;
  9631. }
  9632. }
  9633. }
  9634. if ( !m_pSchedule )
  9635. bDiscardScheduleState = true;
  9636. if ( !bDiscardScheduleState )
  9637. m_bDoPostRestoreRefindPath = ( ( saveHeader.flags & AIESH_HAD_NAVGOAL) != 0 );
  9638. else
  9639. {
  9640. m_bDoPostRestoreRefindPath = false;
  9641. DiscardScheduleState();
  9642. }
  9643. return status;
  9644. }
  9645. //-------------------------------------
  9646. void CAI_BaseNPC::SaveConditions( ISave &save, const CAI_ScheduleBits &conditions )
  9647. {
  9648. for (int i = 0; i < MAX_CONDITIONS; i++)
  9649. {
  9650. if (conditions.IsBitSet(i))
  9651. {
  9652. const char *pszConditionName = ConditionName(AI_RemapToGlobal(i));
  9653. if ( !pszConditionName )
  9654. break;
  9655. save.WriteString( pszConditionName );
  9656. }
  9657. }
  9658. save.WriteString( "" );
  9659. }
  9660. //-------------------------------------
  9661. void CAI_BaseNPC::RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions )
  9662. {
  9663. pConditions->ClearAll();
  9664. char szCondition[256];
  9665. for (;;)
  9666. {
  9667. restore.ReadString( szCondition, sizeof(szCondition), 0 );
  9668. if ( !szCondition[0] )
  9669. break;
  9670. int iCondition = GetSchedulingSymbols()->ConditionSymbolToId( szCondition );
  9671. if ( iCondition != -1 )
  9672. pConditions->Set( AI_RemapFromGlobal( iCondition ) );
  9673. }
  9674. }
  9675. //-----------------------------------------------------------------------------
  9676. //-----------------------------------------------------------------------------
  9677. bool CAI_BaseNPC::KeyValue( const char *szKeyName, const char *szValue )
  9678. {
  9679. bool bResult = BaseClass::KeyValue( szKeyName, szValue );
  9680. if( !bResult )
  9681. {
  9682. // Defer unhandled Keys to behaviors
  9683. CAI_BehaviorBase **ppBehaviors = AccessBehaviors();
  9684. for ( int i = 0; i < NumBehaviors(); i++ )
  9685. {
  9686. if( ppBehaviors[ i ]->KeyValue( szKeyName, szValue ) )
  9687. {
  9688. return true;
  9689. }
  9690. }
  9691. }
  9692. return bResult;
  9693. }
  9694. //-----------------------------------------------------------------------------
  9695. // Debug function to make this NPC freeze in place (or unfreeze).
  9696. //-----------------------------------------------------------------------------
  9697. void CAI_BaseNPC::ToggleFreeze()
  9698. {
  9699. if ( GetMoveType() != MOVETYPE_NONE )
  9700. {
  9701. Freeze();
  9702. }
  9703. else
  9704. {
  9705. Unfreeze();
  9706. }
  9707. }
  9708. //-----------------------------------------------------------------------------
  9709. // Freezes this NPC in place for a period of time.
  9710. //-----------------------------------------------------------------------------
  9711. void CAI_BaseNPC::Freeze( float flFreezeAmount, CBaseEntity *pFreezer, Ray_t *pFreezeRay )
  9712. {
  9713. BaseClass::Freeze( flFreezeAmount, pFreezer, pFreezeRay );
  9714. if ( flFreezeAmount < 0.0f )
  9715. {
  9716. SetCondition(COND_NPC_FREEZE);
  9717. SetMoveType(MOVETYPE_NONE);
  9718. SetGravity(0);
  9719. SetLocalAngularVelocity(vec3_angle);
  9720. SetAbsVelocity( vec3_origin );
  9721. }
  9722. else
  9723. {
  9724. m_flFrozenThawRate = 0.1f;
  9725. if ( ShouldBecomeStatue() )
  9726. {
  9727. // Dude is frozen, so lets use a stiff server side statue
  9728. if ( IsAlive() )
  9729. {
  9730. CreateServerStatue( this, COLLISION_GROUP_NONE );
  9731. Event_Killed( CTakeDamageInfo( pFreezer, pFreezer, 1000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE ) );
  9732. RemoveDeferred();
  9733. }
  9734. }
  9735. }
  9736. }
  9737. bool CAI_BaseNPC::ShouldBecomeStatue()
  9738. {
  9739. return ( m_flFrozen >= 1.0f );
  9740. }
  9741. //-----------------------------------------------------------------------------
  9742. //-----------------------------------------------------------------------------
  9743. void CAI_BaseNPC::Unfreeze()
  9744. {
  9745. BaseClass::Unfreeze();
  9746. // Unfreeze them.
  9747. SetCondition(COND_NPC_UNFREEZE);
  9748. m_Activity = ACT_RESET;
  9749. SetMoveType( MOVETYPE_STEP ); // BUGBUG: this might not be the correct movetype!
  9750. SetGravity(1); // Doesn't restore gravity to the original value, but who cares?
  9751. }
  9752. //-----------------------------------------------------------------------------
  9753. // Purpose: Written by subclasses macro to load schedules
  9754. // Input :
  9755. // Output :
  9756. //-----------------------------------------------------------------------------
  9757. bool CAI_BaseNPC::LoadSchedules(void)
  9758. {
  9759. return true;
  9760. }
  9761. //-----------------------------------------------------------------------------
  9762. bool CAI_BaseNPC::LoadedSchedules(void)
  9763. {
  9764. return true;
  9765. }
  9766. //-----------------------------------------------------------------------------
  9767. // Purpose: Constructor
  9768. // Input :
  9769. // Output :
  9770. //-----------------------------------------------------------------------------
  9771. CAI_BaseNPC::CAI_BaseNPC(void)
  9772. : m_UnreachableEnts( 0, 4 ),
  9773. m_bDeferredNavigation( false ),
  9774. m_pPrimaryBehavior(NULL)
  9775. {
  9776. m_pMotor = NULL;
  9777. m_pMoveProbe = NULL;
  9778. m_pNavigator = NULL;
  9779. m_pSenses = NULL;
  9780. m_pPathfinder = NULL;
  9781. m_pLocalNavigator = NULL;
  9782. m_pSchedule = NULL;
  9783. m_IdealSchedule = SCHED_NONE;
  9784. #ifdef _DEBUG
  9785. // necessary since in debug, we initialize vectors to NAN for debugging
  9786. m_vecLastPosition.Init();
  9787. m_vSavePosition.Init();
  9788. m_vEyeLookTarget.Init();
  9789. m_vCurEyeTarget.Init();
  9790. m_vDefaultEyeOffset.Init();
  9791. #endif
  9792. m_bDidDeathCleanup = false;
  9793. m_afCapability = 0; // Make sure this is cleared in the base class
  9794. SetHullType(HULL_HUMAN); // Give human hull by default, subclasses should override
  9795. m_nAITraceMask = MASK_NPCSOLID;
  9796. m_iMySquadSlot = SQUAD_SLOT_NONE;
  9797. m_flSumDamage = 0;
  9798. m_flLastDamageTime = 0;
  9799. m_flLastAttackTime = 0;
  9800. m_flSoundWaitTime = 0;
  9801. m_flNextEyeLookTime = 0;
  9802. m_flHeadYaw = 0;
  9803. m_flHeadPitch = 0;
  9804. m_spawnEquipment = NULL_STRING;
  9805. m_pEnemies = new CAI_Enemies;
  9806. m_bIgnoreUnseenEnemies = false;
  9807. m_flEyeIntegRate = 0.95;
  9808. m_flFaceEnemyTolerance = 0.006f;
  9809. SetTarget( NULL );
  9810. m_pSquad = NULL;
  9811. m_flMoveWaitFinished = 0;
  9812. m_fIsUsingSmallHull = true;
  9813. m_bHintGroupNavLimiting = false;
  9814. m_fNoDamageDecal = false;
  9815. SetInAScript( false );
  9816. m_pLockedBestSound = new CSound;
  9817. m_pLockedBestSound->m_iType = SOUND_NONE;
  9818. // ----------------------------
  9819. // Debugging fields
  9820. // ----------------------------
  9821. m_interruptText = NULL;
  9822. m_failText = NULL;
  9823. m_failedSchedule = NULL;
  9824. m_interuptSchedule = NULL;
  9825. m_nDebugPauseIndex = 0;
  9826. g_AI_Manager.AddAI( this );
  9827. if ( g_AI_Manager.NumAIs() == 1 )
  9828. {
  9829. m_AnyUpdateEnemyPosTimer.Force();
  9830. gm_flTimeLastSpawn = -1;
  9831. gm_nSpawnedThisFrame = 0;
  9832. gm_iNextThinkRebalanceTick = 0;
  9833. }
  9834. m_iFrameBlocked = -1;
  9835. m_bInChoreo = true; // assume so until call to UpdateEfficiency()
  9836. m_pScheduleEvent = NULL;
  9837. SetCollisionGroup( COLLISION_GROUP_NPC );
  9838. }
  9839. //-----------------------------------------------------------------------------
  9840. // Purpose: Destructor
  9841. // Input :
  9842. // Output :
  9843. //-----------------------------------------------------------------------------
  9844. CAI_BaseNPC::~CAI_BaseNPC(void)
  9845. {
  9846. g_AI_Manager.RemoveAI( this );
  9847. delete m_pLockedBestSound;
  9848. RemoveMemory();
  9849. delete m_pPathfinder;
  9850. delete m_pNavigator;
  9851. delete m_pMotor;
  9852. delete m_pLocalNavigator;
  9853. delete m_pMoveProbe;
  9854. delete m_pSenses;
  9855. delete m_pTacticalServices;
  9856. }
  9857. //-----------------------------------------------------------------------------
  9858. // Purpose:
  9859. //-----------------------------------------------------------------------------
  9860. void CAI_BaseNPC::UpdateOnRemove(void)
  9861. {
  9862. if ( !m_bDidDeathCleanup )
  9863. {
  9864. if ( m_NPCState == NPC_STATE_DEAD )
  9865. DevMsg( "May not have cleaned up on NPC death\n");
  9866. CleanupOnDeath( NULL, false );
  9867. }
  9868. // Chain at end to mimic destructor unwind order
  9869. BaseClass::UpdateOnRemove();
  9870. }
  9871. //-----------------------------------------------------------------------------
  9872. //-----------------------------------------------------------------------------
  9873. int CAI_BaseNPC::UpdateTransmitState()
  9874. {
  9875. if( gpGlobals->curtime < m_flTimePingEffect )
  9876. {
  9877. return SetTransmitState( FL_EDICT_ALWAYS );
  9878. }
  9879. return BaseClass::UpdateTransmitState();
  9880. }
  9881. //-----------------------------------------------------------------------------
  9882. bool CAI_BaseNPC::CreateComponents()
  9883. {
  9884. m_pSenses = CreateSenses();
  9885. if ( !m_pSenses )
  9886. return false;
  9887. m_pMotor = CreateMotor();
  9888. if ( !m_pMotor )
  9889. return false;
  9890. m_pLocalNavigator = CreateLocalNavigator();
  9891. if ( !m_pLocalNavigator )
  9892. return false;
  9893. m_pMoveProbe = CreateMoveProbe();
  9894. if ( !m_pMoveProbe )
  9895. return false;
  9896. m_pNavigator = CreateNavigator();
  9897. if ( !m_pNavigator )
  9898. return false;
  9899. m_pPathfinder = CreatePathfinder();
  9900. if ( !m_pPathfinder )
  9901. return false;
  9902. m_pTacticalServices = CreateTacticalServices();
  9903. if ( !m_pTacticalServices )
  9904. return false;
  9905. m_MoveAndShootOverlay.SetOuter( this );
  9906. m_pMotor->Init( m_pLocalNavigator );
  9907. m_pLocalNavigator->Init( m_pNavigator );
  9908. m_pNavigator->Init( g_pBigAINet );
  9909. m_pPathfinder->Init( g_pBigAINet );
  9910. m_pTacticalServices->Init( g_pBigAINet );
  9911. return CreateBehaviors();
  9912. }
  9913. //-----------------------------------------------------------------------------
  9914. CAI_Senses *CAI_BaseNPC::CreateSenses()
  9915. {
  9916. CAI_Senses *pSenses = new CAI_Senses;
  9917. pSenses->SetOuter( this );
  9918. return pSenses;
  9919. }
  9920. //-----------------------------------------------------------------------------
  9921. CAI_Motor *CAI_BaseNPC::CreateMotor()
  9922. {
  9923. return new CAI_Motor( this );
  9924. }
  9925. //-----------------------------------------------------------------------------
  9926. CAI_MoveProbe *CAI_BaseNPC::CreateMoveProbe()
  9927. {
  9928. return new CAI_MoveProbe( this );
  9929. }
  9930. //-----------------------------------------------------------------------------
  9931. CAI_LocalNavigator *CAI_BaseNPC::CreateLocalNavigator()
  9932. {
  9933. return new CAI_LocalNavigator( this );
  9934. }
  9935. //-----------------------------------------------------------------------------
  9936. CAI_TacticalServices *CAI_BaseNPC::CreateTacticalServices()
  9937. {
  9938. return new CAI_TacticalServices( this );
  9939. }
  9940. //-----------------------------------------------------------------------------
  9941. CAI_Navigator *CAI_BaseNPC::CreateNavigator()
  9942. {
  9943. return new CAI_Navigator( this );
  9944. }
  9945. //-----------------------------------------------------------------------------
  9946. CAI_Pathfinder *CAI_BaseNPC::CreatePathfinder()
  9947. {
  9948. return new CAI_Pathfinder( this );
  9949. }
  9950. //-----------------------------------------------------------------------------
  9951. // Purpose:
  9952. //-----------------------------------------------------------------------------
  9953. void CAI_BaseNPC::InputSetRelationship( inputdata_t &inputdata )
  9954. {
  9955. AddRelationship( inputdata.value.String(), inputdata.pActivator );
  9956. }
  9957. //-----------------------------------------------------------------------------
  9958. // Won't affect the current enemy, only future enemy acquisitions.
  9959. //-----------------------------------------------------------------------------
  9960. void CAI_BaseNPC::InputSetEnemyFilter( inputdata_t &inputdata )
  9961. {
  9962. // Get a handle to my enemy filter entity if there is one.
  9963. CBaseEntity *pFilter = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
  9964. m_hEnemyFilter = dynamic_cast<CBaseFilter*>(pFilter);
  9965. }
  9966. //-----------------------------------------------------------------------------
  9967. // Purpose:
  9968. //-----------------------------------------------------------------------------
  9969. void CAI_BaseNPC::InputSetHealth( inputdata_t &inputdata )
  9970. {
  9971. int iNewHealth = inputdata.value.Int();
  9972. int iDelta = abs(GetHealth() - iNewHealth);
  9973. if ( iNewHealth > GetHealth() )
  9974. {
  9975. TakeHealth( iDelta, DMG_GENERIC );
  9976. }
  9977. else if ( iNewHealth < GetHealth() )
  9978. {
  9979. TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) );
  9980. }
  9981. }
  9982. //-----------------------------------------------------------------------------
  9983. // Purpose:
  9984. //-----------------------------------------------------------------------------
  9985. void CAI_BaseNPC::InputBeginRappel( inputdata_t &inputdata )
  9986. {
  9987. BeginRappel();
  9988. }
  9989. //-----------------------------------------------------------------------------
  9990. // Purpose:
  9991. //-----------------------------------------------------------------------------
  9992. void CAI_BaseNPC::InputSetSquad( inputdata_t &inputdata )
  9993. {
  9994. if ( !( CapabilitiesGet() & bits_CAP_SQUAD ) )
  9995. {
  9996. Warning("SetSquad Input received for NPC %s, but that NPC can't use squads.\n", GetDebugName() );
  9997. return;
  9998. }
  9999. m_SquadName = inputdata.value.StringID();
  10000. // Removing from squad?
  10001. if ( m_SquadName == NULL_STRING )
  10002. {
  10003. if ( m_pSquad )
  10004. {
  10005. m_pSquad->RemoveFromSquad(this, true);
  10006. m_pSquad = NULL;
  10007. }
  10008. }
  10009. else
  10010. {
  10011. m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName);
  10012. }
  10013. }
  10014. //-----------------------------------------------------------------------------
  10015. // Purpose:
  10016. //-----------------------------------------------------------------------------
  10017. void CAI_BaseNPC::InputWake( inputdata_t &inputdata )
  10018. {
  10019. Wake();
  10020. // Check if we have a path to follow. This is normally done in StartNPC,
  10021. // but putting the NPC to sleep will cancel it, so we have to do it again.
  10022. if ( m_target != NULL_STRING )// this npc has a target
  10023. {
  10024. // Find the npc's initial target entity, stash it
  10025. SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) );
  10026. if ( !GetGoalEnt() )
  10027. {
  10028. Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target));
  10029. }
  10030. else
  10031. {
  10032. StartTargetHandling( GetGoalEnt() );
  10033. }
  10034. }
  10035. }
  10036. //-----------------------------------------------------------------------------
  10037. // Purpose:
  10038. //-----------------------------------------------------------------------------
  10039. void CAI_BaseNPC::InputForgetEntity( inputdata_t &inputdata )
  10040. {
  10041. const char *pszEntityToForget = inputdata.value.String();
  10042. if ( g_pDeveloper->GetInt() && pszEntityToForget[strlen( pszEntityToForget ) - 1] == '*' )
  10043. DevMsg( "InputForgetEntity does not support wildcards\n" );
  10044. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, pszEntityToForget );
  10045. if ( pEntity )
  10046. {
  10047. if ( GetEnemy() == pEntity )
  10048. {
  10049. SetEnemy( NULL );
  10050. SetIdealState( NPC_STATE_ALERT );
  10051. }
  10052. GetEnemies()->ClearMemory( pEntity );
  10053. }
  10054. }
  10055. //-----------------------------------------------------------------------------
  10056. //-----------------------------------------------------------------------------
  10057. void CAI_BaseNPC::InputIgnoreDangerSounds( inputdata_t &inputdata )
  10058. {
  10059. // Default is 10 seconds.
  10060. float flDelay = 10.0f;
  10061. if( inputdata.value.Float() > 0.0f )
  10062. {
  10063. flDelay = inputdata.value.Float();
  10064. }
  10065. m_flIgnoreDangerSoundsUntil = gpGlobals->curtime + flDelay;
  10066. }
  10067. //-----------------------------------------------------------------------------
  10068. //-----------------------------------------------------------------------------
  10069. void CAI_BaseNPC::InputUpdateEnemyMemory( inputdata_t &inputdata )
  10070. {
  10071. const char *pszEnemy = inputdata.value.String();
  10072. CBaseEntity *pEnemy = gEntList.FindEntityByName( NULL, pszEnemy );
  10073. if( pEnemy )
  10074. {
  10075. UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), this );
  10076. }
  10077. }
  10078. //-----------------------------------------------------------------------------
  10079. // create an addon and attach to npc
  10080. //-----------------------------------------------------------------------------
  10081. void CAI_BaseNPC::InputCreateAddon( inputdata_t &inputdata )
  10082. {
  10083. Vector vecSpawnOrigin = GetLocalOrigin();
  10084. const char *pszAddonName = inputdata.value.String();
  10085. // Spawn the addon
  10086. CBaseEntity *pItem = (CBaseEntity *)CreateEntityByName( pszAddonName );
  10087. if ( pItem )
  10088. {
  10089. pItem->SetAbsOrigin( vecSpawnOrigin );
  10090. DispatchSpawn( pItem );
  10091. // install the addon
  10092. assert_cast< CAI_AddOn *>( pItem )->Install( this );
  10093. }
  10094. }
  10095. //-----------------------------------------------------------------------------
  10096. // Purpose:
  10097. // Input : &inputdata -
  10098. //-----------------------------------------------------------------------------
  10099. void CAI_BaseNPC::InputOutsideTransition( inputdata_t &inputdata )
  10100. {
  10101. }
  10102. //-----------------------------------------------------------------------------
  10103. // Purpose: Called when this NPC transitions to another level with the player
  10104. // Input : &inputdata -
  10105. //-----------------------------------------------------------------------------
  10106. void CAI_BaseNPC::InputInsideTransition( inputdata_t &inputdata )
  10107. {
  10108. CleanupScriptsOnTeleport( true );
  10109. // If we're inside a vcd, tell it to stop
  10110. if ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) )
  10111. {
  10112. RemoveActorFromScriptedScenes( this, false );
  10113. }
  10114. }
  10115. //-----------------------------------------------------------------------------
  10116. // Purpose:
  10117. //-----------------------------------------------------------------------------
  10118. void CAI_BaseNPC::CleanupScriptsOnTeleport( bool bEnrouteAsWell )
  10119. {
  10120. // If I'm running a scripted sequence, I need to clean up
  10121. if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
  10122. {
  10123. if ( !bEnrouteAsWell )
  10124. {
  10125. //
  10126. // Don't cancel scripts when they're teleporting an NPC
  10127. // to the script for the purposes of movement.
  10128. //
  10129. if ( ( m_scriptState == CAI_BaseNPC::SCRIPT_WALK_TO_MARK ) ||
  10130. ( m_scriptState == CAI_BaseNPC::SCRIPT_RUN_TO_MARK ) ||
  10131. ( m_scriptState == CAI_BaseNPC::SCRIPT_CUSTOM_MOVE_TO_MARK ) ||
  10132. m_hCine->IsTeleportingDueToMoveTo() )
  10133. {
  10134. return;
  10135. }
  10136. }
  10137. m_hCine->ScriptEntityCancel( m_hCine, true );
  10138. }
  10139. }
  10140. //-----------------------------------------------------------------------------
  10141. //-----------------------------------------------------------------------------
  10142. bool CAI_BaseNPC::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
  10143. {
  10144. #if defined( HL2_DLL )
  10145. if ( interactionType == g_interactionBarnacleVictimGrab )
  10146. {
  10147. // Make the victim stop thinking so they're as good as dead without
  10148. // shocking the system by destroying the entity.
  10149. StopLoopingSounds();
  10150. BarnacleDeathSound();
  10151. SetThink( NULL );
  10152. // Gag the NPC so they won't talk anymore
  10153. AddSpawnFlags( SF_NPC_GAG );
  10154. // Drop any weapon they're holding
  10155. if ( GetActiveWeapon() )
  10156. {
  10157. Weapon_Drop( GetActiveWeapon() );
  10158. }
  10159. return true;
  10160. }
  10161. #endif // HL2_DLL
  10162. return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
  10163. }
  10164. CAI_BaseNPC *CAI_BaseNPC::GetInteractionPartner( void )
  10165. {
  10166. if ( m_hInteractionPartner == NULL )
  10167. return NULL;
  10168. return m_hInteractionPartner->MyNPCPointer();
  10169. }
  10170. //-----------------------------------------------------------------------------
  10171. // Purpose: Called when exiting a scripted sequence.
  10172. // Output : Returns true if alive, false if dead.
  10173. //-----------------------------------------------------------------------------
  10174. bool CAI_BaseNPC::ExitScriptedSequence( )
  10175. {
  10176. if ( m_lifeState == LIFE_DYING )
  10177. {
  10178. // is this legal?
  10179. // BUGBUG -- This doesn't call Killed()
  10180. SetIdealState( NPC_STATE_DEAD );
  10181. return false;
  10182. }
  10183. if (m_hCine)
  10184. {
  10185. m_hCine->CancelScript( );
  10186. }
  10187. return true;
  10188. }
  10189. ConVar sv_test_scripted_sequences( "sv_test_scripted_sequences", "0", FCVAR_NONE, "Tests for scripted sequences that are embedded in the world. Run through your map with this set to check for NPCs falling through the world." );
  10190. bool CAI_BaseNPC::CineCleanup()
  10191. {
  10192. CAI_ScriptedSequence *pOldCine = m_hCine;
  10193. int nSavedFlags = ( m_hCine ? m_hCine->m_savedFlags : GetFlags() );
  10194. bool bDestroyCine = false;
  10195. if ( IsRunningDynamicInteraction() )
  10196. {
  10197. bDestroyCine = true;
  10198. // Re-enable physics collisions between me & the other NPC
  10199. if ( m_hInteractionPartner )
  10200. {
  10201. PhysEnableEntityCollisions( this, m_hInteractionPartner );
  10202. //Msg("%s(%s) enabled collisions with %s(%s) at %0.2f\n", GetClassname(), GetDebugName(), m_hInteractionPartner->GetClassName(), m_hInteractionPartner->GetDebugName(), gpGlobals->curtime );
  10203. }
  10204. if ( m_hForcedInteractionPartner )
  10205. {
  10206. // We've finished a forced interaction. Let the mapmaker know.
  10207. m_OnForcedInteractionFinished.FireOutput( this, this );
  10208. }
  10209. // Clear interaction partner, because we're not running a scripted sequence anymore
  10210. m_hInteractionPartner = NULL;
  10211. CleanupForcedInteraction();
  10212. }
  10213. // am I linked to a cinematic?
  10214. if (m_hCine)
  10215. {
  10216. // okay, reset me to what it thought I was before
  10217. m_hCine->SetTarget( NULL );
  10218. // NOTE that this will have had EF_NODRAW removed in script.dll when it's cached off
  10219. SetEffects( m_hCine->m_saved_effects );
  10220. SetCollisionGroup( m_hCine->m_savedCollisionGroup );
  10221. }
  10222. else
  10223. {
  10224. // arg, punt
  10225. AddSolidFlags( FSOLID_NOT_STANDABLE );
  10226. }
  10227. m_hCine = NULL;
  10228. SetTarget( NULL );
  10229. SetGoalEnt( NULL );
  10230. if (m_lifeState == LIFE_DYING)
  10231. {
  10232. // last frame of death animation?
  10233. if ( m_iHealth > 0 )
  10234. {
  10235. m_iHealth = 0;
  10236. }
  10237. AddSolidFlags( FSOLID_NOT_SOLID );
  10238. SetState( NPC_STATE_DEAD );
  10239. m_lifeState = LIFE_DEAD;
  10240. UTIL_SetSize( this, WorldAlignMins(), Vector(WorldAlignMaxs().x, WorldAlignMaxs().y, WorldAlignMins().z + 2) );
  10241. if ( pOldCine && pOldCine->HasSpawnFlags( SF_SCRIPT_LEAVECORPSE ) )
  10242. {
  10243. SetUse( NULL ); // BUGBUG -- This doesn't call Killed()
  10244. SetThink( NULL ); // This will probably break some stuff
  10245. SetTouch( NULL );
  10246. }
  10247. else
  10248. SUB_StartFadeOut(); // SetThink( SUB_DoNothing );
  10249. //Not becoming a ragdoll, so set the NOINTERP flag on.
  10250. if ( CanBecomeRagdoll() == false )
  10251. {
  10252. StopAnimation();
  10253. AddEffects( EF_NOINTERP ); // Don't interpolate either, assume the corpse is positioned in its final resting place
  10254. }
  10255. SetMoveType( MOVETYPE_NONE );
  10256. return false;
  10257. }
  10258. // If we actually played a sequence
  10259. if ( pOldCine && pOldCine->m_iszPlay != NULL_STRING && pOldCine->PlayedSequence() )
  10260. {
  10261. if ( !pOldCine->HasSpawnFlags(SF_SCRIPT_DONT_TELEPORT_AT_END) )
  10262. {
  10263. // reset position
  10264. Vector new_origin;
  10265. QAngle new_angle;
  10266. GetBonePosition( 0, new_origin, new_angle );
  10267. // Figure out how far they have moved
  10268. // We can't really solve this problem because we can't query the movement of the origin relative
  10269. // to the sequence. We can get the root bone's position as we do here, but there are
  10270. // cases where the root bone is in a different relative position to the entity's origin
  10271. // before/after the sequence plays. So we are stuck doing this:
  10272. // !!!HACKHACK: Float the origin up and drop to floor because some sequences have
  10273. // irregular motion that can't be properly accounted for.
  10274. // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE.
  10275. Vector oldOrigin = GetLocalOrigin();
  10276. // UNDONE: ugly hack. Don't move NPC if they don't "seem" to move
  10277. // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly
  10278. // being set, so animations that really do move won't be caught.
  10279. if ((oldOrigin - new_origin).Length2D() < 8.0)
  10280. new_origin = oldOrigin;
  10281. Vector origin = GetLocalOrigin();
  10282. origin.x = new_origin.x;
  10283. origin.y = new_origin.y;
  10284. origin.z += 1;
  10285. if ( nSavedFlags & FL_FLY )
  10286. {
  10287. origin.z = new_origin.z;
  10288. SetLocalOrigin( origin );
  10289. }
  10290. else
  10291. {
  10292. SetLocalOrigin( origin );
  10293. int drop = UTIL_DropToFloor( this, GetAITraceMask(), UTIL_GetLocalPlayer() );
  10294. // Origin in solid? Set to org at the end of the sequence
  10295. if ( ( drop < 0 ) || sv_test_scripted_sequences.GetBool() )
  10296. {
  10297. SetLocalOrigin( oldOrigin );
  10298. }
  10299. else if ( drop == 0 ) // Hanging in air?
  10300. {
  10301. Vector origin = GetLocalOrigin();
  10302. origin.z = new_origin.z;
  10303. SetLocalOrigin( origin );
  10304. SetGroundEntity( NULL );
  10305. }
  10306. }
  10307. origin = GetLocalOrigin();
  10308. // teleport if it's a non-trivial distance
  10309. if ((oldOrigin - origin).Length() > 8.0)
  10310. {
  10311. // Call teleport to notify
  10312. Teleport( &origin, NULL, NULL );
  10313. SetLocalOrigin( origin );
  10314. AddEffects( EF_NOINTERP );
  10315. }
  10316. if ( m_iHealth <= 0 )
  10317. {
  10318. // Dropping out because he got killed
  10319. SetIdealState( NPC_STATE_DEAD );
  10320. SetCondition( COND_LIGHT_DAMAGE );
  10321. m_lifeState = LIFE_DYING;
  10322. }
  10323. }
  10324. // We should have some animation to put these guys in, but for now it's idle.
  10325. // Due to NOINTERP above, there won't be any blending between this anim & the sequence
  10326. m_Activity = ACT_RESET;
  10327. }
  10328. // set them back into a normal state
  10329. if ( m_iHealth > 0 )
  10330. {
  10331. SetIdealState( NPC_STATE_IDLE );
  10332. }
  10333. else
  10334. {
  10335. // Dropping out because he got killed
  10336. SetIdealState( NPC_STATE_DEAD );
  10337. SetCondition( COND_LIGHT_DAMAGE );
  10338. }
  10339. // SetAnimation( m_NPCState );
  10340. CLEARBITS(m_spawnflags, SF_NPC_WAIT_FOR_SCRIPT );
  10341. if ( bDestroyCine )
  10342. {
  10343. UTIL_Remove( pOldCine );
  10344. }
  10345. return true;
  10346. }
  10347. //-----------------------------------------------------------------------------
  10348. void CAI_BaseNPC::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity, bool bUseSlowHighAccuracyContacts )
  10349. {
  10350. CleanupScriptsOnTeleport( false );
  10351. BaseClass::Teleport( newPosition, newAngles, newVelocity, bUseSlowHighAccuracyContacts );
  10352. CheckPVSCondition();
  10353. }
  10354. //-----------------------------------------------------------------------------
  10355. bool CAI_BaseNPC::FindSpotForNPCInRadius( Vector *pResult, const Vector &vStartPos, CAI_BaseNPC *pNPC, float radius, bool bOutOfPlayerViewcone )
  10356. {
  10357. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  10358. QAngle fan;
  10359. fan.x = 0;
  10360. fan.z = 0;
  10361. for( fan.y = 0 ; fan.y < 360 ; fan.y += 18.0 )
  10362. {
  10363. Vector vecTest;
  10364. Vector vecDir;
  10365. AngleVectors( fan, &vecDir );
  10366. vecTest = vStartPos + vecDir * radius;
  10367. if ( bOutOfPlayerViewcone && pPlayer && !pPlayer->FInViewCone( vecTest ) )
  10368. continue;
  10369. trace_t tr;
  10370. UTIL_TraceLine( vecTest, vecTest - Vector( 0, 0, 8192 ), MASK_SHOT, pNPC, COLLISION_GROUP_NONE, &tr );
  10371. if( tr.fraction == 1.0 )
  10372. {
  10373. continue;
  10374. }
  10375. UTIL_TraceHull( tr.endpos,
  10376. tr.endpos + Vector( 0, 0, 10 ),
  10377. pNPC->GetHullMins(),
  10378. pNPC->GetHullMaxs(),
  10379. pNPC->GetAITraceMask(),
  10380. pNPC,
  10381. COLLISION_GROUP_NONE,
  10382. &tr );
  10383. if( tr.fraction == 1.0 && pNPC->GetMoveProbe()->CheckStandPosition( tr.endpos, pNPC->GetAITraceMask() ) )
  10384. {
  10385. *pResult = tr.endpos;
  10386. return true;
  10387. }
  10388. }
  10389. return false;
  10390. }
  10391. //-----------------------------------------------------------------------------
  10392. bool CAI_BaseNPC::IsNavigationUrgent()
  10393. {
  10394. // return true if the navigation is for something that can't react well to failure
  10395. if ( IsCurSchedule( SCHED_SCRIPTED_WALK, false ) ||
  10396. IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
  10397. IsCurSchedule( SCHED_SCRIPTED_CUSTOM_MOVE, false ) ||
  10398. ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) && IsInLockedScene() ) )
  10399. {
  10400. return true;
  10401. }
  10402. return false;
  10403. }
  10404. //-----------------------------------------------------------------------------
  10405. bool CAI_BaseNPC::ShouldFailNav( bool bMovementFailed )
  10406. {
  10407. #ifdef HL2_EPISODIC
  10408. if ( ai_vehicle_avoidance.GetBool() )
  10409. {
  10410. // Never be blocked this way by a vehicle (creates too many headaches around the levels)
  10411. CBaseEntity *pEntity = GetNavigator()->GetBlockingEntity();
  10412. if ( pEntity && pEntity->GetServerVehicle() )
  10413. {
  10414. // Vital allies never get stuck, and urgent moves cannot be blocked by a vehicle
  10415. if ( Classify() == CLASS_PLAYER_ALLY_VITAL || IsNavigationUrgent() )
  10416. return false;
  10417. }
  10418. }
  10419. #endif // HL2_EPISODIC
  10420. // It's up to the schedule that requested movement to deal with failed movement. Currently, only a handfull of
  10421. // schedules are considered Urgent, and they need to deal with what to do when there's no route, which by inspection
  10422. // they'd don't.
  10423. if ( IsNavigationUrgent())
  10424. {
  10425. return false;
  10426. }
  10427. return true;
  10428. }
  10429. Navigation_t CAI_BaseNPC::GetNavType() const
  10430. {
  10431. return m_pNavigator->GetNavType();
  10432. }
  10433. void CAI_BaseNPC::SetNavType( Navigation_t navType )
  10434. {
  10435. m_pNavigator->SetNavType( navType );
  10436. }
  10437. //-----------------------------------------------------------------------------
  10438. // NPCs can override this to tweak with how costly particular movements are
  10439. //-----------------------------------------------------------------------------
  10440. bool CAI_BaseNPC::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
  10441. {
  10442. // We have nothing to say on the matter, but derived classes might
  10443. return false;
  10444. }
  10445. bool CAI_BaseNPC::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
  10446. {
  10447. return false;
  10448. }
  10449. bool CAI_BaseNPC::OverrideMove( float flInterval )
  10450. {
  10451. return false;
  10452. }
  10453. //=========================================================
  10454. // VecToYaw - turns a directional vector into a yaw value
  10455. // that points down that vector.
  10456. //=========================================================
  10457. float CAI_BaseNPC::VecToYaw( const Vector &vecDir )
  10458. {
  10459. if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0)
  10460. return GetLocalAngles().y;
  10461. return UTIL_VecToYaw( vecDir );
  10462. }
  10463. //-----------------------------------------------------------------------------
  10464. // Inherited from IAI_MotorMovementServices
  10465. //-----------------------------------------------------------------------------
  10466. float CAI_BaseNPC::CalcYawSpeed( void )
  10467. {
  10468. // Negative values are invalud
  10469. return -1.0f;
  10470. }
  10471. bool CAI_BaseNPC::OnCalcBaseMove( AILocalMoveGoal_t *pMoveGoal,
  10472. float distClear,
  10473. AIMoveResult_t *pResult )
  10474. {
  10475. if ( pMoveGoal->directTrace.pObstruction )
  10476. {
  10477. CBasePropDoor *pPropDoor = dynamic_cast<CBasePropDoor *>( pMoveGoal->directTrace.pObstruction );
  10478. if ( pPropDoor && OnUpcomingPropDoor( pMoveGoal, pPropDoor, distClear, pResult ) )
  10479. {
  10480. return true;
  10481. }
  10482. }
  10483. return false;
  10484. }
  10485. bool CAI_BaseNPC::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal,
  10486. float distClear,
  10487. AIMoveResult_t *pResult )
  10488. {
  10489. if ( pMoveGoal->directTrace.pObstruction )
  10490. {
  10491. CBaseDoor *pDoor = dynamic_cast<CBaseDoor *>( pMoveGoal->directTrace.pObstruction );
  10492. if ( pDoor && OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) )
  10493. {
  10494. return true;
  10495. }
  10496. }
  10497. return false;
  10498. }
  10499. bool CAI_BaseNPC::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal,
  10500. CBaseDoor *pDoor,
  10501. float distClear,
  10502. AIMoveResult_t *pResult )
  10503. {
  10504. if ( pMoveGoal->maxDist < distClear )
  10505. return false;
  10506. // By default, NPCs don't know how to open doors
  10507. if ( pDoor->m_toggle_state == TS_AT_BOTTOM || pDoor->m_toggle_state == TS_GOING_DOWN )
  10508. {
  10509. if ( distClear < 0.1 )
  10510. {
  10511. *pResult = AIMR_BLOCKED_ENTITY;
  10512. }
  10513. else
  10514. {
  10515. pMoveGoal->maxDist = distClear;
  10516. *pResult = AIMR_OK;
  10517. }
  10518. return true;
  10519. }
  10520. return false;
  10521. }
  10522. //-----------------------------------------------------------------------------
  10523. // Purpose:
  10524. // Input : pMoveGoal -
  10525. // pDoor -
  10526. // distClear -
  10527. // default -
  10528. // spawn -
  10529. // oldorg -
  10530. // pfPosition -
  10531. // neworg -
  10532. // Output : Returns true if movement is solved, false otherwise.
  10533. //-----------------------------------------------------------------------------
  10534. bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal,
  10535. CBasePropDoor *pDoor,
  10536. float distClear,
  10537. AIMoveResult_t *pResult )
  10538. {
  10539. int bits;
  10540. bits = CapabilitiesGet() & bits_CAP_DOORS_GROUP;
  10541. if ( (pMoveGoal->flags & AILMG_TARGET_IS_GOAL) && pMoveGoal->maxDist < distClear )
  10542. return false;
  10543. if ( pMoveGoal->maxDist + GetHullWidth() * .25 < distClear )
  10544. return false;
  10545. if (pDoor == m_hOpeningDoor)
  10546. {
  10547. if ( pDoor->IsNPCOpening( this ) )
  10548. {
  10549. // We're in the process of opening the door, don't be blocked by it.
  10550. pMoveGoal->maxDist = distClear;
  10551. *pResult = AIMR_OK;
  10552. return true;
  10553. }
  10554. m_hOpeningDoor = NULL;
  10555. }
  10556. if ((CapabilitiesGet() & bits_CAP_DOORS_GROUP) && !pDoor->IsDoorLocked() && (pDoor->IsDoorClosed() || pDoor->IsDoorClosing()))
  10557. {
  10558. AI_Waypoint_t *pOpenDoorRoute = NULL;
  10559. opendata_t opendata;
  10560. pDoor->GetNPCOpenData(this, opendata);
  10561. // dvs: FIXME: local route might not be sufficient
  10562. pOpenDoorRoute = GetPathfinder()->BuildLocalRoute(
  10563. GetLocalOrigin(),
  10564. opendata.vecStandPos,
  10565. NULL,
  10566. bits_WP_TO_DOOR | bits_WP_DONT_SIMPLIFY,
  10567. NO_NODE,
  10568. bits_BUILD_GROUND | bits_BUILD_IGNORE_NPCS,
  10569. 0.0);
  10570. if ( pOpenDoorRoute )
  10571. {
  10572. if ( AIIsDebuggingDoors(this) )
  10573. {
  10574. NDebugOverlay::Cross3D(opendata.vecStandPos + Vector(0,0,1), 32, 255, 255, 255, false, 1.0 );
  10575. Msg( "Opening door!\n" );
  10576. }
  10577. // Attach the door to the waypoint so we open it when we get there.
  10578. // dvs: FIXME: this is kind of bullshit, I need to find the exact waypoint to open the door
  10579. // should I just walk the path until I find it?
  10580. pOpenDoorRoute->m_hData = pDoor;
  10581. GetNavigator()->GetPath()->PrependWaypoints( pOpenDoorRoute );
  10582. m_hOpeningDoor = pDoor;
  10583. pMoveGoal->maxDist = distClear;
  10584. *pResult = AIMR_CHANGE_TYPE;
  10585. return true;
  10586. }
  10587. else
  10588. AIDoorDebugMsg( this, "Failed create door route!\n" );
  10589. }
  10590. return false;
  10591. }
  10592. //-----------------------------------------------------------------------------
  10593. // Purpose: Called by the navigator to initiate the opening of a prop_door
  10594. // that is in our way.
  10595. //-----------------------------------------------------------------------------
  10596. void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor )
  10597. {
  10598. // dvs: not quite working, disabled for now.
  10599. //opendata_t opendata;
  10600. //pDoor->GetNPCOpenData(this, opendata);
  10601. //
  10602. //if (HaveSequenceForActivity(opendata.eActivity))
  10603. //{
  10604. // SetIdealActivity(opendata.eActivity);
  10605. //}
  10606. //else
  10607. {
  10608. // We don't have an appropriate sequence, just open the door magically.
  10609. OpenPropDoorNow( pDoor );
  10610. }
  10611. }
  10612. //-----------------------------------------------------------------------------
  10613. // Purpose: Called when we are trying to open a prop_door and it's time to start
  10614. // the door moving. This is called either in response to an anim event
  10615. // or as a fallback when we don't have an appropriate open activity.
  10616. //-----------------------------------------------------------------------------
  10617. void CAI_BaseNPC::OpenPropDoorNow( CBasePropDoor *pDoor )
  10618. {
  10619. // Start the door moving.
  10620. pDoor->NPCOpenDoor(this);
  10621. // Wait for the door to finish opening before trying to move through the doorway.
  10622. m_flMoveWaitFinished = gpGlobals->curtime + pDoor->GetOpenInterval();
  10623. }
  10624. //-----------------------------------------------------------------------------
  10625. // Purpose: Called when the door we were trying to open becomes fully open.
  10626. // Input : pDoor -
  10627. //-----------------------------------------------------------------------------
  10628. void CAI_BaseNPC::OnDoorFullyOpen(CBasePropDoor *pDoor)
  10629. {
  10630. // We're done with the door.
  10631. m_hOpeningDoor = NULL;
  10632. }
  10633. //-----------------------------------------------------------------------------
  10634. // Purpose: Called when the door we were trying to open becomes blocked before opening.
  10635. // Input : pDoor -
  10636. //-----------------------------------------------------------------------------
  10637. void CAI_BaseNPC::OnDoorBlocked(CBasePropDoor *pDoor)
  10638. {
  10639. // dvs: FIXME: do something so that we don't loop forever trying to open this door
  10640. // not clearing out the door handle will cause the NPC to invalidate the connection
  10641. // We're done with the door.
  10642. //m_hOpeningDoor = NULL;
  10643. }
  10644. //-----------------------------------------------------------------------------
  10645. // Purpose: Template NPCs are marked as templates by the level designer. They
  10646. // do not spawn, but their keyvalues are saved for use by a template
  10647. // spawner.
  10648. //-----------------------------------------------------------------------------
  10649. bool CAI_BaseNPC::IsTemplate( void )
  10650. {
  10651. return HasSpawnFlags( SF_NPC_TEMPLATE );
  10652. }
  10653. //-----------------------------------------------------------------------------
  10654. //
  10655. // Movement code for walking + flying
  10656. //
  10657. //-----------------------------------------------------------------------------
  10658. int CAI_BaseNPC::FlyMove( const Vector& pfPosition, unsigned int mask )
  10659. {
  10660. Vector oldorg, neworg;
  10661. trace_t trace;
  10662. // try the move
  10663. VectorCopy( GetAbsOrigin(), oldorg );
  10664. VectorAdd( oldorg, pfPosition, neworg );
  10665. UTIL_TraceEntity( this, oldorg, neworg, mask, &trace );
  10666. if (trace.fraction == 1)
  10667. {
  10668. if ( (GetFlags() & FL_SWIM) && enginetrace->GetPointContents(trace.endpos) == CONTENTS_EMPTY )
  10669. return false; // swim monster left water
  10670. SetAbsOrigin( trace.endpos );
  10671. PhysicsTouchTriggers();
  10672. return true;
  10673. }
  10674. return false;
  10675. }
  10676. //-----------------------------------------------------------------------------
  10677. // Purpose:
  10678. // Input : ent -
  10679. // Dir - Normalized direction vector for movement.
  10680. // dist - Distance along 'Dir' to move.
  10681. // iMode -
  10682. // Output : Returns nonzero on success, zero on failure.
  10683. //-----------------------------------------------------------------------------
  10684. int CAI_BaseNPC::WalkMove( const Vector& vecPosition, unsigned int mask )
  10685. {
  10686. if ( GetFlags() & (FL_FLY | FL_SWIM) )
  10687. {
  10688. return FlyMove( vecPosition, mask );
  10689. }
  10690. if ( (GetFlags() & FL_ONGROUND) == 0 )
  10691. {
  10692. return 0;
  10693. }
  10694. trace_t trace;
  10695. Vector oldorg, neworg, end;
  10696. Vector move( vecPosition[0], vecPosition[1], 0.0f );
  10697. VectorCopy( GetAbsOrigin(), oldorg );
  10698. VectorAdd( oldorg, move, neworg );
  10699. // push down from a step height above the wished position
  10700. float flStepSize = sv_stepsize.GetFloat();
  10701. neworg[2] += flStepSize;
  10702. VectorCopy(neworg, end);
  10703. end[2] -= flStepSize*2;
  10704. UTIL_TraceEntity( this, neworg, end, mask, &trace );
  10705. if ( trace.allsolid )
  10706. return false;
  10707. if (trace.startsolid)
  10708. {
  10709. neworg[2] -= flStepSize;
  10710. UTIL_TraceEntity( this, neworg, end, mask, &trace );
  10711. if ( trace.allsolid || trace.startsolid )
  10712. return false;
  10713. }
  10714. if (trace.fraction == 1)
  10715. {
  10716. // if monster had the ground pulled out, go ahead and fall
  10717. if ( GetFlags() & FL_PARTIALGROUND )
  10718. {
  10719. SetAbsOrigin( oldorg + move );
  10720. PhysicsTouchTriggers();
  10721. SetGroundEntity( NULL );
  10722. return true;
  10723. }
  10724. return false; // walked off an edge
  10725. }
  10726. // check point traces down for dangling corners
  10727. SetAbsOrigin( trace.endpos );
  10728. if (UTIL_CheckBottom( this, NULL, flStepSize ) == 0)
  10729. {
  10730. if ( GetFlags() & FL_PARTIALGROUND )
  10731. {
  10732. // entity had floor mostly pulled out from underneath it
  10733. // and is trying to correct
  10734. PhysicsTouchTriggers();
  10735. return true;
  10736. }
  10737. // Reset to original position
  10738. SetAbsOrigin( oldorg );
  10739. return false;
  10740. }
  10741. if ( GetFlags() & FL_PARTIALGROUND )
  10742. {
  10743. // Con_Printf ("back on ground\n");
  10744. RemoveFlag( FL_PARTIALGROUND );
  10745. }
  10746. // the move is ok
  10747. SetGroundEntity( trace.m_pEnt );
  10748. PhysicsTouchTriggers();
  10749. return true;
  10750. }
  10751. //-----------------------------------------------------------------------------
  10752. #ifndef DBGFLAG_STRINGS_STRIP
  10753. static void AIMsgGuts( CAI_BaseNPC *pAI, unsigned flags, const char *pszMsg )
  10754. {
  10755. int len = strlen( pszMsg );
  10756. const char *pszFmt2 = NULL;
  10757. if ( len && pszMsg[len-1] == '\n' )
  10758. {
  10759. (const_cast<char *>(pszMsg))[len-1] = 0;
  10760. pszFmt2 = "%s (%s: %d/%s) [%d]\n";
  10761. }
  10762. else
  10763. pszFmt2 = "%s (%s: %d/%s) [%d]";
  10764. DevMsg( pszFmt2,
  10765. pszMsg,
  10766. pAI->GetClassname(),
  10767. pAI->entindex(),
  10768. ( pAI->GetEntityName() == NULL_STRING ) ? "<unnamed>" : STRING(pAI->GetEntityName()),
  10769. gpGlobals->tickcount );
  10770. }
  10771. void DevMsg( CAI_BaseNPC *pAI, unsigned flags, const char *pszFormat, ... )
  10772. {
  10773. if ( (flags & AIMF_IGNORE_SELECTED) || (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
  10774. {
  10775. AIMsgGuts( pAI, flags, CFmtStr( &pszFormat ) );
  10776. }
  10777. }
  10778. //-----------------------------------------------------------------------------
  10779. void DevMsg( CAI_BaseNPC *pAI, const char *pszFormat, ... )
  10780. {
  10781. if ( (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
  10782. {
  10783. AIMsgGuts( pAI, 0, CFmtStr( &pszFormat ) );
  10784. }
  10785. }
  10786. #endif
  10787. //-----------------------------------------------------------------------------
  10788. bool CAI_BaseNPC::IsPlayerAlly( CBasePlayer *pPlayer )
  10789. {
  10790. if ( pPlayer == NULL )
  10791. {
  10792. // in multiplayer mode we need a valid pPlayer
  10793. // or override this virtual function
  10794. if ( !AI_IsSinglePlayer() )
  10795. return false;
  10796. // NULL means single player mode
  10797. pPlayer = UTIL_GetLocalPlayer();
  10798. }
  10799. return ( !pPlayer || IRelationType( pPlayer ) == D_LI );
  10800. }
  10801. //-----------------------------------------------------------------------------
  10802. void CAI_BaseNPC::SetCommandGoal( const Vector &vecGoal )
  10803. {
  10804. m_vecCommandGoal = vecGoal;
  10805. m_CommandMoveMonitor.ClearMark();
  10806. }
  10807. //-----------------------------------------------------------------------------
  10808. void CAI_BaseNPC::ClearCommandGoal()
  10809. {
  10810. m_vecCommandGoal = vec3_invalid;
  10811. m_CommandMoveMonitor.ClearMark();
  10812. }
  10813. //-----------------------------------------------------------------------------
  10814. bool CAI_BaseNPC::IsInPlayerSquad() const
  10815. {
  10816. return ( m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() && !CAI_Squad::IsSilentMember(this) );
  10817. }
  10818. //-----------------------------------------------------------------------------
  10819. bool CAI_BaseNPC::CanBeUsedAsAFriend( void )
  10820. {
  10821. if ( IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN) )
  10822. return false;
  10823. return true;
  10824. }
  10825. //-----------------------------------------------------------------------------
  10826. Vector CAI_BaseNPC::GetSmoothedVelocity( void )
  10827. {
  10828. if( GetNavType() == NAV_GROUND || GetNavType() == NAV_FLY )
  10829. {
  10830. return m_pMotor->GetCurVel();
  10831. }
  10832. return BaseClass::GetSmoothedVelocity();
  10833. }
  10834. //-----------------------------------------------------------------------------
  10835. bool CAI_BaseNPC::IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition )
  10836. {
  10837. trace_t tr;
  10838. // By default, we ignore the viewer (me) when determining cover positions
  10839. CTraceFilterLOS filter( NULL, COLLISION_GROUP_NONE, this );
  10840. // If I'm trying to find cover from the player, and the player is in a vehicle,
  10841. // ignore the vehicle for the purpose of determining line of sight.
  10842. CBaseEntity *pEnemy = GetEnemy();
  10843. if ( pEnemy )
  10844. {
  10845. // Hack to see if our threat position is our enemy
  10846. bool bThreatPosIsEnemy = ( (vecThreat - GetEnemy()->EyePosition()).LengthSqr() < 0.1f );
  10847. if ( bThreatPosIsEnemy )
  10848. {
  10849. CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
  10850. if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
  10851. {
  10852. // Ignore the vehicle
  10853. filter.SetPassEntity( pCCEnemy->GetVehicleEntity() );
  10854. }
  10855. if ( !filter.GetPassEntity() )
  10856. {
  10857. filter.SetPassEntity( pEnemy );
  10858. }
  10859. }
  10860. }
  10861. AI_TraceLOS( vecThreat, vecPosition, this, &tr, &filter );
  10862. if( tr.fraction != 1.0 && hl2_episodic.GetBool() )
  10863. {
  10864. if( tr.m_pEnt->m_iClassname == m_iClassname )
  10865. {
  10866. // Don't hide behind buddies!
  10867. return false;
  10868. }
  10869. }
  10870. return (tr.fraction != 1.0);
  10871. }
  10872. //-----------------------------------------------------------------------------
  10873. float CAI_BaseNPC::SetWait( float minWait, float maxWait )
  10874. {
  10875. int minThinks = Ceil2Int( minWait * 10 );
  10876. if ( maxWait == 0.0 )
  10877. {
  10878. m_flWaitFinished = gpGlobals->curtime + ( 0.1 * minThinks );
  10879. }
  10880. else
  10881. {
  10882. if ( minThinks == 0 ) // random 0..n is almost certain to not return 0
  10883. minThinks = 1;
  10884. int maxThinks = Ceil2Int( maxWait * 10 );
  10885. m_flWaitFinished = gpGlobals->curtime + ( 0.1 * random->RandomInt( minThinks, maxThinks ) );
  10886. }
  10887. return m_flWaitFinished;
  10888. }
  10889. //-----------------------------------------------------------------------------
  10890. void CAI_BaseNPC::ClearWait()
  10891. {
  10892. m_flWaitFinished = FLT_MAX;
  10893. }
  10894. //-----------------------------------------------------------------------------
  10895. bool CAI_BaseNPC::IsWaitFinished()
  10896. {
  10897. return ( gpGlobals->curtime >= m_flWaitFinished );
  10898. }
  10899. //-----------------------------------------------------------------------------
  10900. bool CAI_BaseNPC::IsWaitSet()
  10901. {
  10902. return ( m_flWaitFinished != FLT_MAX );
  10903. }
  10904. void CAI_BaseNPC::TestPlayerPushing( CBaseEntity *pEntity )
  10905. {
  10906. if ( HasSpawnFlags( SF_NPC_NO_PLAYER_PUSHAWAY ) )
  10907. return;
  10908. // Heuristic for determining if the player is pushing me away
  10909. CBasePlayer *pPlayer = ToBasePlayer( pEntity );
  10910. if ( pPlayer && !( pPlayer->GetFlags() & FL_NOTARGET ) )
  10911. {
  10912. if ( (pPlayer->m_nButtons & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT)) ||
  10913. pPlayer->GetAbsVelocity().AsVector2D().LengthSqr() > 50*50 )
  10914. {
  10915. SetCondition( COND_PLAYER_PUSHING );
  10916. Vector vecPush = GetAbsOrigin() - pPlayer->GetAbsOrigin();
  10917. VectorNormalize( vecPush );
  10918. CascadePlayerPush( vecPush, pPlayer->WorldSpaceCenter() );
  10919. }
  10920. }
  10921. }
  10922. void CAI_BaseNPC::CascadePlayerPush( const Vector &push, const Vector &pushOrigin )
  10923. {
  10924. //
  10925. // Try to push any friends that are in the way.
  10926. //
  10927. float hullWidth = GetHullWidth();
  10928. const Vector & origin = GetAbsOrigin();
  10929. const Vector2D &origin2D = origin.AsVector2D();
  10930. const float MIN_Z_TO_TRANSMIT = GetHullHeight() * 0.5 + 0.1;
  10931. const float DIST_REQD_TO_TRANSMIT_PUSH_SQ = Square( hullWidth * 5 + 0.1 );
  10932. const float DIST_FROM_PUSH_VECTOR_REQD_SQ = Square( hullWidth + 0.1 );
  10933. Vector2D pushTestPoint = vec2_invalid;
  10934. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  10935. {
  10936. CAI_BaseNPC *pOther = g_AI_Manager.AccessAIs()[i];
  10937. if ( pOther != this && pOther->IRelationType(this) == D_LI && !pOther->HasCondition( COND_PLAYER_PUSHING ) )
  10938. {
  10939. const Vector &friendOrigin = pOther->GetAbsOrigin();
  10940. if ( fabsf( friendOrigin.z - origin.z ) < MIN_Z_TO_TRANSMIT &&
  10941. ( friendOrigin.AsVector2D() - origin.AsVector2D() ).LengthSqr() < DIST_REQD_TO_TRANSMIT_PUSH_SQ )
  10942. {
  10943. if ( pushTestPoint == vec2_invalid )
  10944. {
  10945. pushTestPoint = origin2D - pushOrigin.AsVector2D();
  10946. // No normalize, since it wants to just be a big number and we can't be less that a hull away
  10947. pushTestPoint *= 2000;
  10948. pushTestPoint += origin2D;
  10949. }
  10950. float t;
  10951. float distSq = CalcDistanceSqrToLine2D( friendOrigin.AsVector2D(), origin2D, pushTestPoint, &t );
  10952. if ( t > 0 && distSq < DIST_FROM_PUSH_VECTOR_REQD_SQ )
  10953. {
  10954. pOther->SetCondition( COND_PLAYER_PUSHING );
  10955. }
  10956. }
  10957. }
  10958. }
  10959. }
  10960. //-----------------------------------------------------------------------------
  10961. // Break into pieces!
  10962. //-----------------------------------------------------------------------------
  10963. void CAI_BaseNPC::Break( CBaseEntity *pBreaker )
  10964. {
  10965. m_takedamage = DAMAGE_NO;
  10966. Vector velocity;
  10967. AngularImpulse angVelocity;
  10968. IPhysicsObject *pPhysics = VPhysicsGetObject();
  10969. Vector origin;
  10970. QAngle angles;
  10971. AddSolidFlags( FSOLID_NOT_SOLID );
  10972. if ( pPhysics )
  10973. {
  10974. pPhysics->GetVelocity( &velocity, &angVelocity );
  10975. pPhysics->GetPosition( &origin, &angles );
  10976. pPhysics->RecheckCollisionFilter();
  10977. }
  10978. else
  10979. {
  10980. velocity = GetAbsVelocity();
  10981. QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity );
  10982. origin = GetAbsOrigin();
  10983. angles = GetAbsAngles();
  10984. }
  10985. breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity );
  10986. params.impactEnergyScale = m_impactEnergyScale;
  10987. params.defCollisionGroup = GetCollisionGroup();
  10988. if ( params.defCollisionGroup == COLLISION_GROUP_NONE )
  10989. {
  10990. // don't automatically make anything COLLISION_GROUP_NONE or it will
  10991. // collide with debris being ejected by breaking
  10992. params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE;
  10993. }
  10994. // no damage/damage force? set a burst of 100 for some movement
  10995. params.defBurstScale = 100;//pDamageInfo ? 0 : 100;
  10996. PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, false );
  10997. UTIL_Remove(this);
  10998. }
  10999. //-----------------------------------------------------------------------------
  11000. // Purpose: Input handler for breaking the breakable immediately.
  11001. //-----------------------------------------------------------------------------
  11002. void CAI_BaseNPC::InputBreak( inputdata_t &inputdata )
  11003. {
  11004. Break( inputdata.pActivator );
  11005. }
  11006. //-----------------------------------------------------------------------------
  11007. bool CAI_BaseNPC::FindNearestValidGoalPos( const Vector &vTestPoint, Vector *pResult )
  11008. {
  11009. AIMoveTrace_t moveTrace;
  11010. Vector vCandidate = vec3_invalid;
  11011. if ( GetNavigator()->CanFitAtPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) )
  11012. {
  11013. if ( GetMoveProbe()->CheckStandPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) )
  11014. {
  11015. vCandidate = vTestPoint;
  11016. }
  11017. }
  11018. if ( vCandidate == vec3_invalid )
  11019. {
  11020. int iNearestNode = GetNavigator()->GetNetwork()->NearestNodeToPoint( vTestPoint, false );
  11021. if ( iNearestNode != NO_NODE )
  11022. {
  11023. GetMoveProbe()->MoveLimit( NAV_GROUND,
  11024. g_pBigAINet->GetNodePosition(GetHullType(), iNearestNode ),
  11025. vTestPoint,
  11026. MASK_SOLID_BRUSHONLY,
  11027. NULL,
  11028. 0,
  11029. &moveTrace );
  11030. if ( ( moveTrace.vEndPosition - vTestPoint ).Length2DSqr() < Square( GetHullWidth() * 3.0 ) &&
  11031. GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY ) )
  11032. {
  11033. vCandidate = moveTrace.vEndPosition;
  11034. }
  11035. }
  11036. }
  11037. if ( vCandidate != vec3_invalid )
  11038. {
  11039. AI_Waypoint_t *pPathToPoint = GetPathfinder()->BuildRoute( GetAbsOrigin(), vCandidate, AI_GetSinglePlayer(), 5*12, NAV_NONE, bits_BUILD_GET_CLOSE );
  11040. if ( pPathToPoint )
  11041. {
  11042. GetPathfinder()->UnlockRouteNodes( pPathToPoint );
  11043. CAI_Path tempPath;
  11044. tempPath.SetWaypoints( pPathToPoint ); // path object will delete waypoints
  11045. }
  11046. else
  11047. vCandidate = vec3_invalid;
  11048. }
  11049. if ( vCandidate == vec3_invalid )
  11050. {
  11051. GetMoveProbe()->MoveLimit( NAV_GROUND,
  11052. GetAbsOrigin(),
  11053. vTestPoint,
  11054. MASK_SOLID_BRUSHONLY,
  11055. NULL,
  11056. 0,
  11057. &moveTrace );
  11058. vCandidate = moveTrace.vEndPosition;
  11059. }
  11060. if ( vCandidate == vec3_invalid )
  11061. return false;
  11062. if ( pResult != NULL )
  11063. {
  11064. *pResult = vCandidate;
  11065. }
  11066. return true;
  11067. }
  11068. //---------------------------------------------------------
  11069. // Pass a direction to get how far an NPC would see if facing
  11070. // that direction. Pass nothing to get the length of the NPC's
  11071. // current line of sight.
  11072. //---------------------------------------------------------
  11073. float CAI_BaseNPC::LineOfSightDist( const Vector &vecDir, float zEye )
  11074. {
  11075. Vector testDir;
  11076. if( vecDir == vec3_invalid )
  11077. {
  11078. testDir = EyeDirection3D();
  11079. }
  11080. else
  11081. {
  11082. testDir = vecDir;
  11083. }
  11084. if ( zEye == FLT_MAX )
  11085. zEye = EyePosition().z;
  11086. trace_t tr;
  11087. // Need to center trace so don't get erratic results based on orientation
  11088. Vector testPos( GetAbsOrigin().x, GetAbsOrigin().y, zEye );
  11089. AI_TraceLOS( testPos, testPos + testDir * MAX_COORD_RANGE, this, &tr );
  11090. return (tr.startpos - tr.endpos ).Length();
  11091. }
  11092. ConVar ai_LOS_mode( "ai_LOS_mode", "0", FCVAR_REPLICATED );
  11093. //-----------------------------------------------------------------------------
  11094. // Purpose: Use this to perform AI tracelines that are trying to determine LOS between points.
  11095. // LOS checks between entities should use FVisible.
  11096. //-----------------------------------------------------------------------------
  11097. void AI_TraceLOS( const Vector& vecAbsStart, const Vector& vecAbsEnd, CBaseEntity *pLooker, trace_t *ptr, ITraceFilter *pFilter )
  11098. {
  11099. AI_PROFILE_SCOPE( AI_TraceLOS );
  11100. if ( ai_LOS_mode.GetBool() )
  11101. {
  11102. // Don't use LOS tracefilter
  11103. UTIL_TraceLine( vecAbsStart, vecAbsEnd, MASK_BLOCKLOS, pLooker, COLLISION_GROUP_NONE, ptr );
  11104. return;
  11105. }
  11106. // Use the custom LOS trace filter
  11107. CTraceFilterLOS traceFilter( pLooker, COLLISION_GROUP_NONE );
  11108. if ( !pFilter )
  11109. pFilter = &traceFilter;
  11110. AI_TraceLine( vecAbsStart, vecAbsEnd, MASK_BLOCKLOS_AND_NPCS, pFilter, ptr );
  11111. }
  11112. void CAI_BaseNPC::InputSetSpeedModifierRadius( inputdata_t &inputdata )
  11113. {
  11114. m_iSpeedModRadius = inputdata.value.Int();
  11115. m_iSpeedModRadius *= m_iSpeedModRadius;
  11116. }
  11117. void CAI_BaseNPC::InputSetSpeedModifierSpeed( inputdata_t &inputdata )
  11118. {
  11119. m_iSpeedModSpeed = inputdata.value.Int();
  11120. }
  11121. //-----------------------------------------------------------------------------
  11122. // Purpose:
  11123. //-----------------------------------------------------------------------------
  11124. bool CAI_BaseNPC::IsAllowedToDodge( void )
  11125. {
  11126. // Can't do it if I'm not available
  11127. if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT )
  11128. return false;
  11129. return ( m_flNextDodgeTime <= gpGlobals->curtime );
  11130. }
  11131. //-----------------------------------------------------------------------------
  11132. // Purpose:
  11133. //-----------------------------------------------------------------------------
  11134. void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
  11135. {
  11136. // Already parsed them?
  11137. if ( m_ScriptedInteractions.Count() )
  11138. return;
  11139. // Parse the model's key values and find any dynamic interactions
  11140. KeyValues *modelKeyValues = new KeyValues("");
  11141. CUtlBuffer buf( 1024, 0, CUtlBuffer::TEXT_BUFFER );
  11142. KeyValues::AutoDelete autodelete_key( modelKeyValues );
  11143. if (! modelinfo->GetModelKeyValue( GetModel(), buf ))
  11144. return;
  11145. if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), buf ) )
  11146. {
  11147. // Do we have a dynamic interactions section?
  11148. KeyValues *pkvInteractions = modelKeyValues->FindKey("dynamic_interactions");
  11149. if ( pkvInteractions )
  11150. {
  11151. KeyValues *pkvNode = pkvInteractions->GetFirstSubKey();
  11152. while ( pkvNode )
  11153. {
  11154. ScriptedNPCInteraction_t sInteraction;
  11155. sInteraction.iszInteractionName = AllocPooledString( pkvNode->GetName() );
  11156. // Trigger method
  11157. const char *pszTrigger = pkvNode->GetString( "trigger", NULL );
  11158. if ( pszTrigger )
  11159. {
  11160. if ( StringHasPrefixCaseSensitive( pszTrigger, "auto_in_combat" ) )
  11161. {
  11162. sInteraction.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT;
  11163. }
  11164. }
  11165. // Loop Break trigger method
  11166. pszTrigger = pkvNode->GetString( "loop_break_trigger", NULL );
  11167. if ( pszTrigger )
  11168. {
  11169. char szTrigger[256];
  11170. Q_strncpy( szTrigger, pszTrigger, sizeof(szTrigger) );
  11171. char *pszParam = strtok( szTrigger, " " );
  11172. while (pszParam)
  11173. {
  11174. if ( StringHasPrefixCaseSensitive( pszParam, "on_damage" ) )
  11175. {
  11176. sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE;
  11177. }
  11178. if ( StringHasPrefixCaseSensitive( pszParam, "on_flashlight_illum" ) )
  11179. {
  11180. sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM;
  11181. }
  11182. pszParam = strtok(NULL," ");
  11183. }
  11184. }
  11185. // Origin
  11186. const char *pszOrigin = pkvNode->GetString( "origin_relative", "0 0 0" );
  11187. UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), pszOrigin );
  11188. // Angles
  11189. const char *pszAngles = pkvNode->GetString( "angles_relative", NULL );
  11190. if ( pszAngles )
  11191. {
  11192. sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES;
  11193. UTIL_StringToVector( sInteraction.angRelativeAngles.Base(), pszAngles );
  11194. }
  11195. // Velocity
  11196. const char *pszVelocity = pkvNode->GetString( "velocity_relative", NULL );
  11197. if ( pszVelocity )
  11198. {
  11199. sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY;
  11200. UTIL_StringToVector( sInteraction.vecRelativeVelocity.Base(), pszVelocity );
  11201. }
  11202. // Entry Sequence
  11203. const char *pszSequence = pkvNode->GetString( "entry_sequence", NULL );
  11204. if ( pszSequence )
  11205. {
  11206. sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString( pszSequence );
  11207. }
  11208. // Entry Activity
  11209. const char *pszActivity = pkvNode->GetString( "entry_activity", NULL );
  11210. if ( pszActivity )
  11211. {
  11212. sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID( pszActivity );
  11213. }
  11214. // Sequence
  11215. pszSequence = pkvNode->GetString( "sequence", NULL );
  11216. if ( pszSequence )
  11217. {
  11218. sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( pszSequence );
  11219. }
  11220. // Activity
  11221. pszActivity = pkvNode->GetString( "activity", NULL );
  11222. if ( pszActivity )
  11223. {
  11224. sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID( pszActivity );
  11225. }
  11226. // Exit Sequence
  11227. pszSequence = pkvNode->GetString( "exit_sequence", NULL );
  11228. if ( pszSequence )
  11229. {
  11230. sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString( pszSequence );
  11231. }
  11232. // Exit Activity
  11233. pszActivity = pkvNode->GetString( "exit_activity", NULL );
  11234. if ( pszActivity )
  11235. {
  11236. sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID( pszActivity );
  11237. }
  11238. // Delay
  11239. sInteraction.flDelay = pkvNode->GetFloat( "delay", 10.0 );
  11240. // Delta
  11241. sInteraction.flDistSqr = pkvNode->GetFloat( "origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST) );
  11242. // Loop?
  11243. if ( pkvNode->GetFloat( "loop_in_action", 0 ) )
  11244. {
  11245. sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION;
  11246. }
  11247. // Fixup position?
  11248. const char *pszDontFixup = pkvNode->GetString( "dont_teleport_at_end", NULL );
  11249. if ( pszDontFixup )
  11250. {
  11251. if ( !Q_stricmp( pszDontFixup, "me" ) || !Q_stricmp( pszDontFixup, "both" ) )
  11252. {
  11253. sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME;
  11254. }
  11255. else if ( !Q_stricmp( pszDontFixup, "them" ) || !Q_stricmp( pszDontFixup, "both" ) )
  11256. {
  11257. sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM;
  11258. }
  11259. }
  11260. // Needs a weapon?
  11261. const char *pszNeedsWeapon = pkvNode->GetString( "needs_weapon", NULL );
  11262. if ( pszNeedsWeapon )
  11263. {
  11264. if ( StringHasPrefixCaseSensitive( pszNeedsWeapon, "ME" ) )
  11265. {
  11266. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
  11267. }
  11268. else if ( StringHasPrefixCaseSensitive( pszNeedsWeapon, "THEM" ) )
  11269. {
  11270. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
  11271. }
  11272. else if ( StringHasPrefixCaseSensitive( pszNeedsWeapon, "BOTH" ) )
  11273. {
  11274. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
  11275. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
  11276. }
  11277. }
  11278. // Specific weapon types
  11279. const char *pszWeaponName = pkvNode->GetString( "weapon_mine", NULL );
  11280. if ( pszWeaponName )
  11281. {
  11282. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
  11283. sInteraction.iszMyWeapon = AllocPooledString( pszWeaponName );
  11284. }
  11285. pszWeaponName = pkvNode->GetString( "weapon_theirs", NULL );
  11286. if ( pszWeaponName )
  11287. {
  11288. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
  11289. sInteraction.iszTheirWeapon = AllocPooledString( pszWeaponName );
  11290. }
  11291. // Add it to the list
  11292. AddScriptedNPCInteraction( &sInteraction );
  11293. // Move to next interaction
  11294. pkvNode = pkvNode->GetNextKey();
  11295. }
  11296. }
  11297. }
  11298. }
  11299. //-----------------------------------------------------------------------------
  11300. // Purpose:
  11301. //-----------------------------------------------------------------------------
  11302. void CAI_BaseNPC::AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction )
  11303. {
  11304. int nNewIndex = m_ScriptedInteractions.AddToTail();
  11305. if ( ai_debug_dyninteractions.GetBool() )
  11306. {
  11307. Msg("%s(%s): Added dynamic interaction: %s\n", GetClassname(), GetDebugName(), STRING(pInteraction->iszInteractionName) );
  11308. }
  11309. // Copy the interaction over
  11310. ScriptedNPCInteraction_t *pNewInt = &(m_ScriptedInteractions[nNewIndex]);
  11311. memcpy( pNewInt, pInteraction, sizeof(ScriptedNPCInteraction_t) );
  11312. // Calculate the local to world matrix
  11313. m_ScriptedInteractions[nNewIndex].matDesiredLocalToWorld.SetupMatrixOrgAngles( pInteraction->vecRelativeOrigin, pInteraction->angRelativeAngles );
  11314. }
  11315. //-----------------------------------------------------------------------------
  11316. // Purpose:
  11317. //-----------------------------------------------------------------------------
  11318. const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase )
  11319. {
  11320. if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID )
  11321. {
  11322. int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity );
  11323. return GetSequenceName( iSequence );
  11324. }
  11325. if ( pInteraction->sPhases[iPhase].iszSequence != NULL_STRING )
  11326. return STRING(pInteraction->sPhases[iPhase].iszSequence);
  11327. return NULL;
  11328. }
  11329. //-----------------------------------------------------------------------------
  11330. // Purpose:
  11331. //-----------------------------------------------------------------------------
  11332. void CAI_BaseNPC::StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive )
  11333. {
  11334. m_hInteractionPartner = pOtherNPC;
  11335. if ( bActive )
  11336. {
  11337. m_iInteractionState = NPCINT_RUNNING_ACTIVE;
  11338. }
  11339. else
  11340. {
  11341. m_iInteractionState = NPCINT_RUNNING_PARTNER;
  11342. }
  11343. m_bCannotDieDuringInteraction = true;
  11344. // Force the NPC into an idle schedule so they don't move.
  11345. // NOTE: We must set SCHED_IDLE_STAND directly, to prevent derived NPC
  11346. // classes from translating the idle stand schedule away to do something bad.
  11347. SetSchedule( GetSchedule(SCHED_IDLE_STAND) );
  11348. // Prepare the NPC for the script. Setting this allows the scripted sequences
  11349. // that we're about to create to immediately grab & use this NPC right away.
  11350. // This prevents the NPC from being able to make any schedule decisions
  11351. // before the DSS gets underway.
  11352. m_scriptState = SCRIPT_PLAYING;
  11353. }
  11354. //-----------------------------------------------------------------------------
  11355. // Purpose:
  11356. //-----------------------------------------------------------------------------
  11357. void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles )
  11358. {
  11359. variant_t emptyVariant;
  11360. StartRunningInteraction( pOtherNPC, true );
  11361. if ( pOtherNPC )
  11362. {
  11363. pOtherNPC->StartRunningInteraction( this, false );
  11364. //Msg("%s(%s) disabled collisions with %s(%s) at %0.2f\n", GetClassname(), GetDebugName(), pOtherNPC->GetClassName(), pOtherNPC->GetDebugName(), gpGlobals->curtime );
  11365. PhysDisableEntityCollisions( this, pOtherNPC );
  11366. }
  11367. // Determine which sequences we're going to use
  11368. const char *pszEntrySequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_ENTRY );
  11369. const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE );
  11370. const char *pszExitSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_EXIT );
  11371. // Debug
  11372. if ( ai_debug_dyninteractions.GetBool() )
  11373. {
  11374. if ( pOtherNPC )
  11375. {
  11376. Msg("%s(%s) starting dynamic interaction \"%s\" with %s(%s).\n", GetClassname(), GetDebugName(), STRING(pInteraction->iszInteractionName), pOtherNPC->GetClassname(), pOtherNPC->GetDebugName() );
  11377. if ( pszEntrySequence )
  11378. {
  11379. Msg( " - Entry sequence: %s\n", pszEntrySequence );
  11380. }
  11381. Msg( " - Core sequence: %s\n", pszSequence );
  11382. if ( pszExitSequence )
  11383. {
  11384. Msg( " - Exit sequence: %s\n", pszExitSequence );
  11385. }
  11386. }
  11387. }
  11388. // Create a scripted sequence name that's guaranteed to be unique
  11389. char szSSName[256];
  11390. if ( pOtherNPC )
  11391. {
  11392. Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d%s%d", GetDebugName(), entindex(), pOtherNPC->GetDebugName(), pOtherNPC->entindex() );
  11393. }
  11394. else
  11395. {
  11396. Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d", GetDebugName(), entindex() );
  11397. }
  11398. string_t iszSSName = AllocPooledString(szSSName);
  11399. // Setup next attempt
  11400. pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2);
  11401. // Spawn a scripted sequence for this NPC to play the interaction anim
  11402. CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
  11403. pMySequence->KeyValue( "m_iszEntry", pszEntrySequence );
  11404. pMySequence->KeyValue( "m_iszPlay", pszSequence );
  11405. pMySequence->KeyValue( "m_iszPostIdle", pszExitSequence );
  11406. pMySequence->KeyValue( "m_fMoveTo", "5" );
  11407. pMySequence->SetAbsOrigin( GetAbsOrigin() );
  11408. QAngle angDesired = GetAbsAngles();
  11409. angDesired[YAW] = m_flInteractionYaw;
  11410. pMySequence->SetAbsAngles( angDesired );
  11411. pMySequence->ForceSetTargetEntity( this, true );
  11412. pMySequence->SetName( iszSSName );
  11413. pMySequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
  11414. if ((pInteraction->iFlags & SCNPC_FLAG_DONT_TELEPORT_AT_END_ME) != 0)
  11415. {
  11416. pMySequence->AddSpawnFlags( SF_SCRIPT_DONT_TELEPORT_AT_END );
  11417. }
  11418. pMySequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 );
  11419. pMySequence->SetSynchPostIdles( true );
  11420. if ( ai_debug_dyninteractions.GetBool() )
  11421. {
  11422. pMySequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT;
  11423. }
  11424. // Spawn the matching scripted sequence for the other NPC
  11425. CAI_ScriptedSequence *pTheirSequence = NULL;
  11426. if ( pOtherNPC )
  11427. {
  11428. pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
  11429. pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence );
  11430. pTheirSequence->KeyValue( "m_iszPlay", pszSequence );
  11431. pTheirSequence->KeyValue( "m_iszPostIdle", pszExitSequence );
  11432. pTheirSequence->KeyValue( "m_fMoveTo", "5" );
  11433. pTheirSequence->SetAbsOrigin( vecOtherOrigin );
  11434. pTheirSequence->SetAbsAngles( angOtherAngles );
  11435. pTheirSequence->ForceSetTargetEntity( pOtherNPC, true );
  11436. pTheirSequence->SetName( iszSSName );
  11437. pTheirSequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
  11438. if ((pInteraction->iFlags & SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM) != 0)
  11439. {
  11440. pTheirSequence->AddSpawnFlags( SF_SCRIPT_DONT_TELEPORT_AT_END );
  11441. }
  11442. pTheirSequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 );
  11443. pTheirSequence->SetSynchPostIdles( true );
  11444. if ( ai_debug_dyninteractions.GetBool() )
  11445. {
  11446. pTheirSequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT;
  11447. }
  11448. // Tell their sequence to keep their position relative to me
  11449. pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld );
  11450. }
  11451. // Spawn both sequences at once
  11452. pMySequence->Spawn();
  11453. if ( pTheirSequence )
  11454. {
  11455. pTheirSequence->Spawn();
  11456. }
  11457. // Call activate on both sequences at once
  11458. pMySequence->Activate();
  11459. if ( pTheirSequence )
  11460. {
  11461. pTheirSequence->Activate();
  11462. }
  11463. // Setup the outputs for both sequences. The first kills them both when it finishes
  11464. pMySequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11465. if ( pszExitSequence )
  11466. {
  11467. pMySequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11468. if ( pTheirSequence )
  11469. {
  11470. pTheirSequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11471. }
  11472. }
  11473. else
  11474. {
  11475. pMySequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11476. if ( pTheirSequence )
  11477. {
  11478. pTheirSequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11479. }
  11480. }
  11481. if ( pTheirSequence )
  11482. {
  11483. pTheirSequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11484. }
  11485. // Tell both sequences to start
  11486. pMySequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );
  11487. if ( pTheirSequence )
  11488. {
  11489. pTheirSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );
  11490. }
  11491. }
  11492. //-----------------------------------------------------------------------------
  11493. // Purpose:
  11494. //-----------------------------------------------------------------------------
  11495. bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced )
  11496. {
  11497. if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT )
  11498. return false;
  11499. if ( !IsAlive() )
  11500. return false;
  11501. if ( IsOnFire() )
  11502. return false;
  11503. if ( IsCrouching() )
  11504. return false;
  11505. // Not while running scripted sequences
  11506. if ( m_hCine )
  11507. return false;
  11508. if ( bForced )
  11509. {
  11510. if ( !m_hForcedInteractionPartner )
  11511. return false;
  11512. }
  11513. else
  11514. {
  11515. if ( m_hForcedInteractionPartner || m_hInteractionPartner )
  11516. return false;
  11517. if ( IsInAScript() || !HasCondition(COND_IN_PVS) )
  11518. return false;
  11519. if ( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_MOVE_AWAY) )
  11520. return false;
  11521. // Default AI prevents interactions while melee attacking, but not ranged attacking
  11522. if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) )
  11523. return false;
  11524. }
  11525. return true;
  11526. }
  11527. //-----------------------------------------------------------------------------
  11528. // Purpose:
  11529. //-----------------------------------------------------------------------------
  11530. void CAI_BaseNPC::CheckForScriptedNPCInteractions( void )
  11531. {
  11532. // Are we being forced to interact with another NPC? If so, do that
  11533. if ( m_hForcedInteractionPartner )
  11534. {
  11535. CheckForcedNPCInteractions();
  11536. return;
  11537. }
  11538. // Otherwise, see if we can interaction with our enemy
  11539. if ( !m_ScriptedInteractions.Count() || !GetEnemy() )
  11540. return;
  11541. CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer();
  11542. if( !pNPC )
  11543. return;
  11544. // Recalculate interaction capability whenever we switch enemies
  11545. if ( m_hLastInteractionTestTarget != GetEnemy() )
  11546. {
  11547. m_hLastInteractionTestTarget = GetEnemy();
  11548. CalculateValidEnemyInteractions();
  11549. }
  11550. // First, make sure I'm in a state where I can do this
  11551. if ( !CanRunAScriptedNPCInteraction() )
  11552. return;
  11553. if ( pNPC && !pNPC->CanRunAScriptedNPCInteraction() )
  11554. return;
  11555. for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
  11556. {
  11557. ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i];
  11558. if ( !pInteraction->bValidOnCurrentEnemy )
  11559. continue;
  11560. if ( pInteraction->flNextAttemptTime > gpGlobals->curtime )
  11561. continue;
  11562. Vector vecOrigin;
  11563. QAngle angAngles;
  11564. if ( InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) )
  11565. {
  11566. m_iInteractionPlaying = i;
  11567. StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles );
  11568. break;
  11569. }
  11570. }
  11571. }
  11572. //-----------------------------------------------------------------------------
  11573. // Purpose: Calculate all the valid dynamic interactions we can perform with our current enemy
  11574. //-----------------------------------------------------------------------------
  11575. void CAI_BaseNPC::CalculateValidEnemyInteractions( void )
  11576. {
  11577. CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer();
  11578. if ( !pNPC )
  11579. return;
  11580. bool bDebug = (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT && ai_debug_dyninteractions.GetBool());
  11581. if ( bDebug )
  11582. {
  11583. Msg("%s(%s): Computing valid interactions with %s(%s)\n", GetClassname(), GetDebugName(), pNPC->GetClassname(), pNPC->GetDebugName() );
  11584. }
  11585. bool bFound = false;
  11586. for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
  11587. {
  11588. ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i];
  11589. pInteraction->bValidOnCurrentEnemy = false;
  11590. // If the trigger method of the interaction isn't the one we're after, we're done
  11591. if ( pInteraction->iTriggerMethod != SNPCINT_AUTOMATIC_IN_COMBAT )
  11592. continue;
  11593. if ( !pNPC->GetModelPtr() )
  11594. continue;
  11595. // If we have a damage filter that prevents us hurting the enemy,
  11596. // don't interact with him, since most interactions kill the enemy.
  11597. // Create a fake damage info to test it with.
  11598. CTakeDamageInfo tempinfo( this, this, vec3_origin, vec3_origin, 1.0, DMG_BULLET );
  11599. if ( !pNPC->PassesDamageFilter( tempinfo ) )
  11600. continue;
  11601. // Check the weapon requirements for the interaction
  11602. if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_ME )
  11603. {
  11604. if ( !GetActiveWeapon())
  11605. continue;
  11606. // Check the specific weapon type
  11607. if ( pInteraction->iszMyWeapon != NULL_STRING && GetActiveWeapon()->m_iClassname != pInteraction->iszMyWeapon )
  11608. continue;
  11609. }
  11610. if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_THEM )
  11611. {
  11612. if ( !pNPC->GetActiveWeapon() )
  11613. continue;
  11614. // Check the specific weapon type
  11615. if ( pInteraction->iszTheirWeapon != NULL_STRING && pNPC->GetActiveWeapon()->m_iClassname != pInteraction->iszTheirWeapon )
  11616. continue;
  11617. }
  11618. // Script needs the other NPC, so make sure they're not dead
  11619. if ( !pNPC->IsAlive() )
  11620. continue;
  11621. // Use sequence? or activity?
  11622. if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID )
  11623. {
  11624. // Resolve the activity to a sequence, and make sure our enemy has it
  11625. const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE );
  11626. if ( !pszSequence )
  11627. continue;
  11628. if ( pNPC->LookupSequence( pszSequence ) == -1 )
  11629. continue;
  11630. }
  11631. else
  11632. {
  11633. if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 )
  11634. continue;
  11635. }
  11636. pInteraction->bValidOnCurrentEnemy = true;
  11637. bFound = true;
  11638. if ( bDebug )
  11639. {
  11640. Msg(" Found: %s\n", STRING(pInteraction->iszInteractionName) );
  11641. }
  11642. }
  11643. if ( bDebug && !bFound )
  11644. {
  11645. Msg(" No valid interactions found.\n");
  11646. }
  11647. }
  11648. //-----------------------------------------------------------------------------
  11649. // Purpose:
  11650. //-----------------------------------------------------------------------------
  11651. void CAI_BaseNPC::CheckForcedNPCInteractions( void )
  11652. {
  11653. // If we don't have an interaction, we're waiting for our partner to start it. Do nothing.
  11654. if ( m_iInteractionPlaying == NPCINT_NONE )
  11655. return;
  11656. CAI_BaseNPC *pNPC = m_hForcedInteractionPartner->MyNPCPointer();
  11657. bool bAbort = false;
  11658. // First, make sure both NPCs are able to do this
  11659. if ( !CanRunAScriptedNPCInteraction( true ) || !pNPC->CanRunAScriptedNPCInteraction( true ) )
  11660. {
  11661. // If we were still moving to our target, abort.
  11662. if ( m_iInteractionState == NPCINT_MOVING_TO_MARK )
  11663. {
  11664. bAbort = true;
  11665. }
  11666. else
  11667. {
  11668. return;
  11669. }
  11670. }
  11671. // Check to see if we can start our interaction. If we can, dance.
  11672. Vector vecOrigin;
  11673. QAngle angAngles;
  11674. ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[m_iInteractionPlaying];
  11675. if ( !bAbort )
  11676. {
  11677. if ( !InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) )
  11678. {
  11679. if ( ( gpGlobals->curtime > m_flForcedInteractionTimeout ) && ( m_iInteractionState == NPCINT_MOVING_TO_MARK ) )
  11680. {
  11681. bAbort = true;
  11682. }
  11683. else
  11684. {
  11685. return;
  11686. }
  11687. }
  11688. }
  11689. if ( bAbort )
  11690. {
  11691. if ( m_hForcedInteractionPartner )
  11692. {
  11693. // We've aborted a forced interaction. Let the mapmaker know.
  11694. m_OnForcedInteractionAborted.FireOutput( this, this );
  11695. }
  11696. CleanupForcedInteraction();
  11697. pNPC->CleanupForcedInteraction();
  11698. return;
  11699. }
  11700. StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles );
  11701. m_OnForcedInteractionStarted.FireOutput( this, this );
  11702. }
  11703. //-----------------------------------------------------------------------------
  11704. // Returns whether two NPCs can fit at each other's origin.
  11705. // Kinda like that movie with Eddie Murphy and Dan Akroyd.
  11706. //-----------------------------------------------------------------------------
  11707. bool CanNPCsTradePlaces( CAI_BaseNPC *pNPC1, CAI_BaseNPC *pNPC2, bool bDebug )
  11708. {
  11709. bool bTest1At2 = true;
  11710. bool bTest2At1 = true;
  11711. if ( ( pNPC1->GetHullMins().x <= pNPC2->GetHullMins().x ) &&
  11712. ( pNPC1->GetHullMins().y <= pNPC2->GetHullMins().y ) &&
  11713. ( pNPC1->GetHullMins().z <= pNPC2->GetHullMins().z ) &&
  11714. ( pNPC1->GetHullMaxs().x >= pNPC2->GetHullMaxs().x ) &&
  11715. ( pNPC1->GetHullMaxs().y >= pNPC2->GetHullMaxs().y ) &&
  11716. ( pNPC1->GetHullMaxs().z >= pNPC2->GetHullMaxs().z ) )
  11717. {
  11718. // 1 bigger than 2 in all axes, skip 2 in 1 test
  11719. bTest2At1 = false;
  11720. }
  11721. else if ( ( pNPC2->GetHullMins().x <= pNPC1->GetHullMins().x ) &&
  11722. ( pNPC2->GetHullMins().y <= pNPC1->GetHullMins().y ) &&
  11723. ( pNPC2->GetHullMins().z <= pNPC1->GetHullMins().z ) &&
  11724. ( pNPC2->GetHullMaxs().x >= pNPC1->GetHullMaxs().x ) &&
  11725. ( pNPC2->GetHullMaxs().y >= pNPC1->GetHullMaxs().y ) &&
  11726. ( pNPC2->GetHullMaxs().z >= pNPC1->GetHullMaxs().z ) )
  11727. {
  11728. // 2 bigger than 1 in all axes, skip 1 in 2 test
  11729. bTest1At2 = false;
  11730. }
  11731. trace_t tr;
  11732. CTraceFilterSkipTwoEntities traceFilter( pNPC1, pNPC2, COLLISION_GROUP_NONE );
  11733. if ( bTest1At2 )
  11734. {
  11735. AI_TraceHull( pNPC2->GetAbsOrigin(), pNPC2->GetAbsOrigin(), pNPC1->GetHullMins(), pNPC1->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr );
  11736. if ( tr.startsolid )
  11737. {
  11738. if ( bDebug )
  11739. {
  11740. NDebugOverlay::Box( pNPC2->GetAbsOrigin(), pNPC1->GetHullMins(), pNPC1->GetHullMaxs(), 255,0,0, true, 1.0 );
  11741. }
  11742. return false;
  11743. }
  11744. }
  11745. if ( bTest2At1 )
  11746. {
  11747. AI_TraceHull( pNPC1->GetAbsOrigin(), pNPC1->GetAbsOrigin(), pNPC2->GetHullMins(), pNPC2->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr );
  11748. if ( tr.startsolid )
  11749. {
  11750. if ( bDebug )
  11751. {
  11752. NDebugOverlay::Box( pNPC1->GetAbsOrigin(), pNPC2->GetHullMins(), pNPC2->GetHullMaxs(), 255,0,0, true, 1.0 );
  11753. }
  11754. return false;
  11755. }
  11756. }
  11757. return true;
  11758. }
  11759. //-----------------------------------------------------------------------------
  11760. // Purpose:
  11761. //-----------------------------------------------------------------------------
  11762. bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles )
  11763. {
  11764. // Get a matrix that'll convert from my local interaction space to world space
  11765. VMatrix matMeToWorld, matLocalToWorld;
  11766. QAngle angMyCurrent = GetAbsAngles();
  11767. angMyCurrent[YAW] = m_flInteractionYaw;
  11768. matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angMyCurrent );
  11769. MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld );
  11770. // Get the desired NPC position in worldspace
  11771. vecOrigin = matLocalToWorld.GetTranslation();
  11772. MatrixToAngles( matLocalToWorld, angAngles );
  11773. bool bDebug = ai_debug_dyninteractions.GetBool();
  11774. if ( bDebug )
  11775. {
  11776. NDebugOverlay::Axis( vecOrigin, angAngles, 20, true, 0.1 );
  11777. }
  11778. // Determine whether or not the enemy is on the target
  11779. float flDistSqr = (vecOrigin - pOtherNPC->GetAbsOrigin()).LengthSqr();
  11780. if ( flDistSqr > pInteraction->flDistSqr )
  11781. {
  11782. if ( bDebug )
  11783. {
  11784. if ( m_debugOverlays & OVERLAY_NPC_SELECTED_BIT || pOtherNPC->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  11785. {
  11786. if ( ai_debug_dyninteractions.GetFloat() == 2 )
  11787. {
  11788. Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: <%0.2f (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
  11789. pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, pInteraction->flDistSqr, vecOrigin.x, vecOrigin.y, vecOrigin.z );
  11790. }
  11791. }
  11792. }
  11793. return false;
  11794. }
  11795. if ( bDebug )
  11796. {
  11797. Msg("DYNINT: (%s) testing interaction \"%s\"\n", GetDebugName(), STRING(pInteraction->iszInteractionName) );
  11798. Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  11799. Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
  11800. pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z );
  11801. if ( pOtherNPC )
  11802. {
  11803. float flOtherSpeed = pOtherNPC->GetSequenceGroundSpeed( pOtherNPC->GetSequence() );
  11804. Msg(" %s Speed: %.2f\n", pOtherNPC->GetSequenceName( pOtherNPC->GetSequence() ), flOtherSpeed);
  11805. }
  11806. }
  11807. // Angle check, if we're supposed to
  11808. if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES )
  11809. {
  11810. QAngle angEnemyAngles = pOtherNPC->GetAbsAngles();
  11811. bool bMatches = true;
  11812. for ( int ang = 0; ang < 3; ang++ )
  11813. {
  11814. float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] );
  11815. if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF )
  11816. {
  11817. bMatches = false;
  11818. break;
  11819. }
  11820. }
  11821. if ( !bMatches )
  11822. return false;
  11823. if ( bDebug )
  11824. {
  11825. Msg(" %s angle matched: (%0.2f %0.2f %0.2f), desired (%0.2f, %0.2f, %0.2f)\n", GetDebugName(),
  11826. anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) );
  11827. }
  11828. }
  11829. // TODO: Velocity check, if we're supposed to
  11830. if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY )
  11831. {
  11832. }
  11833. // Valid so far. Now check to make sure there's nothing in the way.
  11834. // This isn't a very good method of checking, but it's cheap and rules out the problems we're seeing so far.
  11835. // If we start getting interactions that start a fair distance apart, we're going to need to do more work here.
  11836. trace_t tr;
  11837. AI_TraceLine( EyePosition(), pOtherNPC->EyePosition(), GetAITraceMask(), this, COLLISION_GROUP_NONE, &tr);
  11838. if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC )
  11839. {
  11840. if ( bDebug )
  11841. {
  11842. Msg( " %s Interaction was blocked.\n", GetDebugName() );
  11843. NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
  11844. NDebugOverlay::Line( pOtherNPC->EyePosition(), tr.endpos, 255,0,0, true, 1.0 );
  11845. }
  11846. return false;
  11847. }
  11848. if ( bDebug )
  11849. {
  11850. NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
  11851. }
  11852. // Do a knee-level trace to find low physics objects
  11853. Vector vecMyKnee, vecOtherKnee;
  11854. CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecMyKnee );
  11855. pOtherNPC->CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecOtherKnee );
  11856. AI_TraceLine( vecMyKnee, vecOtherKnee, GetAITraceMask(), this, COLLISION_GROUP_NONE, &tr);
  11857. if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC )
  11858. {
  11859. if ( bDebug )
  11860. {
  11861. Msg( " %s Interaction was blocked.\n", GetDebugName() );
  11862. NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
  11863. NDebugOverlay::Line( vecOtherKnee, tr.endpos, 255,0,0, true, 1.0 );
  11864. }
  11865. return false;
  11866. }
  11867. if ( bDebug )
  11868. {
  11869. NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
  11870. }
  11871. // Finally, make sure the NPC can actually fit at the interaction position
  11872. // This solves problems with NPCs who are a few units or so above the
  11873. // interaction point, and would sink into the ground when playing the anim.
  11874. CTraceFilterSkipTwoEntities traceFilter( pOtherNPC, this, COLLISION_GROUP_NONE );
  11875. AI_TraceHull( vecOrigin, vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr );
  11876. if ( tr.startsolid )
  11877. {
  11878. if ( bDebug )
  11879. {
  11880. NDebugOverlay::Box( vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), 255,0,0, true, 1.0 );
  11881. }
  11882. return false;
  11883. }
  11884. // If the NPCs are swapping places during this interaction, make sure they can fit at each
  11885. // others' origins before allowing the interaction.
  11886. if ( !CanNPCsTradePlaces( this, pOtherNPC, bDebug ) )
  11887. {
  11888. return false;
  11889. }
  11890. return true;
  11891. }
  11892. //-----------------------------------------------------------------------------
  11893. // Purpose: Return true if this NPC cannot die because it's in an interaction
  11894. // and the flag has been set by the animation.
  11895. //-----------------------------------------------------------------------------
  11896. bool CAI_BaseNPC::HasInteractionCantDie( void )
  11897. {
  11898. return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() );
  11899. }
  11900. //-----------------------------------------------------------------------------
  11901. // Purpose:
  11902. // Input : &inputdata -
  11903. //-----------------------------------------------------------------------------
  11904. void CAI_BaseNPC::InputForceInteractionWithNPC( inputdata_t &inputdata )
  11905. {
  11906. // Get the interaction name & target
  11907. char parseString[255];
  11908. Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString));
  11909. // First, the target's name
  11910. char *pszParam = strtok(parseString," ");
  11911. if ( !pszParam || !pszParam[0] )
  11912. {
  11913. Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC <target NPC> <interaction name>\n", GetClassname(), GetDebugName(), inputdata.value.String() );
  11914. return;
  11915. }
  11916. // Find the target
  11917. CBaseEntity *pTarget = FindNamedEntity( pszParam );
  11918. if ( !pTarget )
  11919. {
  11920. Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find entity named: %s\n", GetClassname(), GetDebugName(), pszParam );
  11921. return;
  11922. }
  11923. CAI_BaseNPC *pNPC = pTarget->MyNPCPointer();
  11924. if ( !pNPC || !pNPC->GetModelPtr() )
  11925. {
  11926. Warning("%s(%s) received ForceInteractionWithNPC input, but entity named %s cannot run dynamic interactions.\n", GetClassname(), GetDebugName(), pszParam );
  11927. return;
  11928. }
  11929. // Second, the interaction name
  11930. pszParam = strtok(NULL," ");
  11931. if ( !pszParam || !pszParam[0] )
  11932. {
  11933. Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC <target NPC> <interaction name>\n", GetClassname(), GetDebugName(), inputdata.value.String() );
  11934. return;
  11935. }
  11936. // Find the interaction from the name, and ensure it's one that the target NPC can play
  11937. int iInteraction = -1;
  11938. for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
  11939. {
  11940. if ( !StringHasPrefixCaseSensitive( STRING(m_ScriptedInteractions[i].iszInteractionName), pszParam ) )
  11941. continue;
  11942. // Use sequence? or activity?
  11943. if ( m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID )
  11944. {
  11945. if ( !pNPC->HaveSequenceForActivity( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity ) )
  11946. {
  11947. // Other NPC may have all the matching sequences, but just without the activity specified.
  11948. // Lets find a single sequence for us, and ensure they have a matching one.
  11949. int iMySeq = SelectWeightedSequence( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity );
  11950. if ( pNPC->LookupSequence( GetSequenceName(iMySeq) ) == -1 )
  11951. continue;
  11952. }
  11953. }
  11954. else
  11955. {
  11956. if ( pNPC->LookupSequence( STRING(m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 )
  11957. continue;
  11958. }
  11959. iInteraction = i;
  11960. break;
  11961. }
  11962. if ( iInteraction == -1 )
  11963. {
  11964. Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find an interaction named %s that entity named %s could run.\n", GetClassname(), GetDebugName(), pszParam, pNPC->GetDebugName() );
  11965. return;
  11966. }
  11967. // Found both pieces of data, lets dance.
  11968. StartForcedInteraction( pNPC, iInteraction );
  11969. pNPC->StartForcedInteraction( this, NPCINT_NONE );
  11970. }
  11971. //-----------------------------------------------------------------------------
  11972. // Purpose:
  11973. //-----------------------------------------------------------------------------
  11974. void CAI_BaseNPC::StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction )
  11975. {
  11976. m_hForcedInteractionPartner = pNPC;
  11977. ClearSchedule( "Starting a forced interaction" );
  11978. m_flForcedInteractionTimeout = gpGlobals->curtime + 8.0f;
  11979. m_iInteractionPlaying = iInteraction;
  11980. m_iInteractionState = NPCINT_MOVING_TO_MARK;
  11981. }
  11982. //-----------------------------------------------------------------------------
  11983. // Purpose:
  11984. //-----------------------------------------------------------------------------
  11985. void CAI_BaseNPC::CleanupForcedInteraction( void )
  11986. {
  11987. m_hForcedInteractionPartner = NULL;
  11988. m_iInteractionPlaying = NPCINT_NONE;
  11989. m_iInteractionState = NPCINT_NOT_RUNNING;
  11990. m_flForcedInteractionTimeout = 0;
  11991. }
  11992. //-----------------------------------------------------------------------------
  11993. // Purpose: Calculate a position to move to so that I can interact with my
  11994. // target NPC.
  11995. //
  11996. // FIXME: THIS ONLY WORKS FOR INTERACTIONS THAT REQUIRE THE TARGET
  11997. // NPC TO BE SOME DISTANCE DIRECTLY IN FRONT OF ME.
  11998. //-----------------------------------------------------------------------------
  11999. void CAI_BaseNPC::CalculateForcedInteractionPosition( void )
  12000. {
  12001. if ( m_iInteractionPlaying == NPCINT_NONE )
  12002. return;
  12003. ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
  12004. // Pretend I was facing the target, and extrapolate from that the position I should be at
  12005. Vector vecToTarget = m_hForcedInteractionPartner->GetAbsOrigin() - GetAbsOrigin();
  12006. VectorNormalize( vecToTarget );
  12007. QAngle angToTarget;
  12008. VectorAngles( vecToTarget, angToTarget );
  12009. // Get the desired position in worldspace, relative to the target
  12010. VMatrix matMeToWorld, matLocalToWorld;
  12011. matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angToTarget );
  12012. MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld );
  12013. Vector vecOrigin = GetAbsOrigin() - matLocalToWorld.GetTranslation();
  12014. m_vecForcedWorldPosition = m_hForcedInteractionPartner->GetAbsOrigin() + vecOrigin;
  12015. //NDebugOverlay::Axis( m_vecForcedWorldPosition, angToTarget, 20, true, 3.0 );
  12016. }
  12017. //-----------------------------------------------------------------------------
  12018. // Purpose:
  12019. // Input : *pPlayer -
  12020. //-----------------------------------------------------------------------------
  12021. void CAI_BaseNPC::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
  12022. {
  12023. #ifdef HL2_EPISODIC
  12024. if ( IsActiveDynamicInteraction() )
  12025. {
  12026. ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
  12027. if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM )
  12028. {
  12029. // Only do this in alyx darkness mode
  12030. if ( HL2GameRules()->IsAlyxInDarknessMode() )
  12031. {
  12032. // Can only break when we're in the action anim
  12033. if ( m_hCine->IsPlayingAction() )
  12034. {
  12035. m_hCine->StopActionLoop( true );
  12036. }
  12037. }
  12038. }
  12039. }
  12040. #endif
  12041. }
  12042. //-----------------------------------------------------------------------------
  12043. // Purpose:
  12044. //-----------------------------------------------------------------------------
  12045. void CAI_BaseNPC::ModifyOrAppendCriteria( AI_CriteriaSet& set )
  12046. {
  12047. BaseClass::ModifyOrAppendCriteria( set );
  12048. if ( m_pPrimaryBehavior )
  12049. {
  12050. // Append active behavior name
  12051. set.AppendCriteria( "active_behavior", GetPrimaryBehavior()->GetName() );
  12052. }
  12053. // Append time since seen player
  12054. if ( m_flLastSawPlayerTime )
  12055. {
  12056. set.AppendCriteria( "timesinceseenplayer", UTIL_VarArgs( "%f", gpGlobals->curtime - m_flLastSawPlayerTime ) );
  12057. }
  12058. else
  12059. {
  12060. set.AppendCriteria( "timesinceseenplayer", "-1" );
  12061. }
  12062. // Append distance to my enemy
  12063. if ( GetEnemy() )
  12064. {
  12065. set.AppendCriteria( "distancetoenemy", UTIL_VarArgs( "%f", EnemyDistance(GetEnemy()) ) );
  12066. }
  12067. else
  12068. {
  12069. set.AppendCriteria( "distancetoenemy", "-1" );
  12070. }
  12071. }
  12072. //-----------------------------------------------------------------------------
  12073. // If I were crouching at my current location, could I shoot this target?
  12074. //-----------------------------------------------------------------------------
  12075. bool CAI_BaseNPC::CouldShootIfCrouching( CBaseEntity *pTarget )
  12076. {
  12077. bool bWasStanding = !IsCrouching();
  12078. Crouch();
  12079. Vector vecTarget;
  12080. if (GetActiveWeapon())
  12081. {
  12082. vecTarget = pTarget->BodyTarget( GetActiveWeapon()->GetLocalOrigin() );
  12083. }
  12084. else
  12085. {
  12086. vecTarget = pTarget->BodyTarget( GetLocalOrigin() );
  12087. }
  12088. bool bResult = WeaponLOSCondition( GetLocalOrigin(), vecTarget, false );
  12089. if ( bWasStanding )
  12090. {
  12091. Stand();
  12092. }
  12093. return bResult;
  12094. }
  12095. //-----------------------------------------------------------------------------
  12096. // Purpose:
  12097. //-----------------------------------------------------------------------------
  12098. bool CAI_BaseNPC::IsCrouchedActivity( Activity activity )
  12099. {
  12100. Activity realActivity = TranslateActivity(activity);
  12101. switch ( realActivity )
  12102. {
  12103. case ACT_RELOAD_LOW:
  12104. case ACT_COVER_LOW:
  12105. case ACT_COVER_PISTOL_LOW:
  12106. case ACT_COVER_SMG1_LOW:
  12107. case ACT_RELOAD_SMG1_LOW:
  12108. return true;
  12109. }
  12110. return false;
  12111. }
  12112. //-----------------------------------------------------------------------------
  12113. // Purpose: Get shoot position of BCC at an arbitrary position
  12114. //-----------------------------------------------------------------------------
  12115. Vector CAI_BaseNPC::Weapon_ShootPosition( void )
  12116. {
  12117. Vector right;
  12118. GetVectors( NULL, &right, NULL );
  12119. bool bStanding = !IsCrouching();
  12120. if ( bStanding && (CapabilitiesGet() & bits_CAP_DUCK) )
  12121. {
  12122. if ( IsCrouchedActivity( GetActivity() ) )
  12123. {
  12124. bStanding = false;
  12125. }
  12126. }
  12127. if ( !bStanding )
  12128. return (GetAbsOrigin() + GetCrouchGunOffset() + right * 8);
  12129. return BaseClass::Weapon_ShootPosition();
  12130. }
  12131. //-----------------------------------------------------------------------------
  12132. // Purpose:
  12133. //-----------------------------------------------------------------------------
  12134. bool CAI_BaseNPC::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
  12135. {
  12136. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  12137. {
  12138. if ( ai_test_moveprobe_ignoresmall.GetBool() && IsNavigationUrgent() )
  12139. {
  12140. IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
  12141. if ( pPhysics->IsMoveable() && pPhysics->GetMass() < 40.0 )
  12142. return false;
  12143. }
  12144. }
  12145. return true;
  12146. }
  12147. //-----------------------------------------------------------------------------
  12148. // Purpose:
  12149. //-----------------------------------------------------------------------------
  12150. bool CAI_BaseNPC::Crouch( void )
  12151. {
  12152. m_bIsCrouching = true;
  12153. return true;
  12154. }
  12155. //-----------------------------------------------------------------------------
  12156. // Purpose:
  12157. //-----------------------------------------------------------------------------
  12158. bool CAI_BaseNPC::IsCrouching( void )
  12159. {
  12160. return ( (CapabilitiesGet() & bits_CAP_DUCK) && m_bIsCrouching );
  12161. }
  12162. //-----------------------------------------------------------------------------
  12163. // Purpose:
  12164. //-----------------------------------------------------------------------------
  12165. bool CAI_BaseNPC::Stand( void )
  12166. {
  12167. if ( m_bForceCrouch )
  12168. return false;
  12169. m_bIsCrouching = false;
  12170. DesireStand();
  12171. return true;
  12172. }
  12173. //-----------------------------------------------------------------------------
  12174. // Purpose:
  12175. //-----------------------------------------------------------------------------
  12176. void CAI_BaseNPC::DesireCrouch( void )
  12177. {
  12178. m_bCrouchDesired = true;
  12179. }
  12180. bool CAI_BaseNPC::IsInChoreo() const
  12181. {
  12182. return m_bInChoreo;
  12183. }