Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

14142 lines
404 KiB

  1. //========= Copyright 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. #include "vphysics/friction.h"
  80. #include "physics_npc_solver.h"
  81. #include "tier0/vcrmode.h"
  82. #include "death_pose.h"
  83. #include "datacache/imdlcache.h"
  84. #include "vstdlib/jobthread.h"
  85. #ifdef HL2_EPISODIC
  86. #include "npc_alyx_episodic.h"
  87. #endif
  88. #ifdef PORTAL
  89. #include "prop_portal_shared.h"
  90. #endif
  91. #include "env_debughistory.h"
  92. #include "collisionutils.h"
  93. extern ConVar sk_healthkit;
  94. // dvs: for opening doors -- these should probably not be here
  95. #include "ai_route.h"
  96. #include "ai_waypoint.h"
  97. #include "utlbuffer.h"
  98. #include "gamestats.h"
  99. // memdbgon must be the last include file in a .cpp file!!!
  100. #include "tier0/memdbgon.h"
  101. #ifdef __clang__
  102. // These clang 3.1 warnings don't seem very useful, and cannot easily be
  103. // avoided in this file.
  104. #pragma GCC diagnostic ignored "-Wdangling-else" // warning: add explicit braces to avoid dangling else [-Wdangling-else]
  105. #endif
  106. //#define DEBUG_LOOK
  107. bool RagdollManager_SaveImportant( CAI_BaseNPC *pNPC );
  108. #define MIN_PHYSICS_FLINCH_DAMAGE 5.0f
  109. #define NPC_GRENADE_FEAR_DIST 200
  110. #define MAX_GLASS_PENETRATION_DEPTH 16.0f
  111. #define FINDNAMEDENTITY_MAX_ENTITIES 32 // max number of entities to be considered for random entity selection in FindNamedEntity
  112. extern bool g_fDrawLines;
  113. extern short g_sModelIndexLaser; // holds the index for the laser beam
  114. extern short g_sModelIndexLaserDot; // holds the index for the laser beam dot
  115. // Debugging tools
  116. ConVar ai_no_select_box( "ai_no_select_box", "0" );
  117. ConVar ai_show_think_tolerance( "ai_show_think_tolerance", "0" );
  118. ConVar ai_debug_think_ticks( "ai_debug_think_ticks", "0" );
  119. ConVar ai_debug_doors( "ai_debug_doors", "0" );
  120. ConVar ai_debug_enemies( "ai_debug_enemies", "0" );
  121. ConVar ai_rebalance_thinks( "ai_rebalance_thinks", "1" );
  122. ConVar ai_use_efficiency( "ai_use_efficiency", "1" );
  123. ConVar ai_use_frame_think_limits( "ai_use_frame_think_limits", "1" );
  124. ConVar ai_default_efficient( "ai_default_efficient", ( IsX360() ) ? "1" : "0" );
  125. ConVar ai_efficiency_override( "ai_efficiency_override", "0" );
  126. ConVar ai_debug_efficiency( "ai_debug_efficiency", "0" );
  127. ConVar ai_debug_dyninteractions( "ai_debug_dyninteractions", "0", FCVAR_NONE, "Debug the NPC dynamic interaction system." );
  128. ConVar ai_frametime_limit( "ai_frametime_limit", "50", FCVAR_NONE, "frametime limit for min efficiency AIE_NORMAL (in sec's)." );
  129. ConVar ai_use_think_optimizations( "ai_use_think_optimizations", "1" );
  130. ConVar ai_test_moveprobe_ignoresmall( "ai_test_moveprobe_ignoresmall", "0" );
  131. #ifdef HL2_EPISODIC
  132. extern ConVar ai_vehicle_avoidance;
  133. #endif // HL2_EPISODIC
  134. #ifndef _RETAIL
  135. #define ShouldUseEfficiency() ( ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool() )
  136. #define ShouldUseFrameThinkLimits() ( ai_use_think_optimizations.GetBool() && ai_use_frame_think_limits.GetBool() )
  137. #define ShouldRebalanceThinks() ( ai_use_think_optimizations.GetBool() && ai_rebalance_thinks.GetBool() )
  138. #define ShouldDefaultEfficient() ( ai_use_think_optimizations.GetBool() && ai_default_efficient.GetBool() )
  139. #else
  140. #define ShouldUseEfficiency() ( true )
  141. #define ShouldUseFrameThinkLimits() ( true )
  142. #define ShouldRebalanceThinks() ( true )
  143. #define ShouldDefaultEfficient() ( true )
  144. #endif
  145. #ifndef _RETAIL
  146. #define DbgEnemyMsg if ( !ai_debug_enemies.GetBool() ) ; else DevMsg
  147. #else
  148. #define DbgEnemyMsg if ( 0 ) ; else DevMsg
  149. #endif
  150. #ifdef DEBUG_AI_FRAME_THINK_LIMITS
  151. #define DbgFrameLimitMsg DevMsg
  152. #else
  153. #define DbgFrameLimitMsg (void)
  154. #endif
  155. // NPC damage adjusters
  156. ConVar sk_npc_head( "sk_npc_head","2" );
  157. ConVar sk_npc_chest( "sk_npc_chest","1" );
  158. ConVar sk_npc_stomach( "sk_npc_stomach","1" );
  159. ConVar sk_npc_arm( "sk_npc_arm","1" );
  160. ConVar sk_npc_leg( "sk_npc_leg","1" );
  161. ConVar showhitlocation( "showhitlocation", "0" );
  162. // Squad debugging
  163. ConVar ai_debug_squads( "ai_debug_squads", "0" );
  164. ConVar ai_debug_loners( "ai_debug_loners", "0" );
  165. // Shoot trajectory
  166. ConVar ai_lead_time( "ai_lead_time","0.0" );
  167. ConVar ai_shot_stats( "ai_shot_stats", "0" );
  168. ConVar ai_shot_stats_term( "ai_shot_stats_term", "1000" );
  169. ConVar ai_shot_bias( "ai_shot_bias", "1.0" );
  170. ConVar ai_spread_defocused_cone_multiplier( "ai_spread_defocused_cone_multiplier","3.0" );
  171. ConVar ai_spread_cone_focus_time( "ai_spread_cone_focus_time","0.6" );
  172. ConVar ai_spread_pattern_focus_time( "ai_spread_pattern_focus_time","0.8" );
  173. ConVar ai_reaction_delay_idle( "ai_reaction_delay_idle","0.3" );
  174. ConVar ai_reaction_delay_alert( "ai_reaction_delay_alert", "0.1" );
  175. ConVar ai_strong_optimizations( "ai_strong_optimizations", ( IsX360() ) ? "1" : "0" );
  176. bool AIStrongOpt( void )
  177. {
  178. return ai_strong_optimizations.GetBool();
  179. }
  180. //-----------------------------------------------------------------------------
  181. //
  182. // Crude frame timings
  183. //
  184. CFastTimer g_AIRunTimer;
  185. CFastTimer g_AIPostRunTimer;
  186. CFastTimer g_AIMoveTimer;
  187. CFastTimer g_AIConditionsTimer;
  188. CFastTimer g_AIPrescheduleThinkTimer;
  189. CFastTimer g_AIMaintainScheduleTimer;
  190. //-----------------------------------------------------------------------------
  191. //-----------------------------------------------------------------------------
  192. CAI_Manager g_AI_Manager;
  193. //-------------------------------------
  194. CAI_Manager::CAI_Manager()
  195. {
  196. m_AIs.EnsureCapacity( MAX_AIS );
  197. }
  198. //-------------------------------------
  199. CAI_BaseNPC **CAI_Manager::AccessAIs()
  200. {
  201. if (m_AIs.Count())
  202. return &m_AIs[0];
  203. return NULL;
  204. }
  205. //-------------------------------------
  206. int CAI_Manager::NumAIs()
  207. {
  208. return m_AIs.Count();
  209. }
  210. //-------------------------------------
  211. void CAI_Manager::AddAI( CAI_BaseNPC *pAI )
  212. {
  213. m_AIs.AddToTail( pAI );
  214. }
  215. //-------------------------------------
  216. void CAI_Manager::RemoveAI( CAI_BaseNPC *pAI )
  217. {
  218. int i = m_AIs.Find( pAI );
  219. if ( i != -1 )
  220. m_AIs.FastRemove( i );
  221. }
  222. //-----------------------------------------------------------------------------
  223. // ================================================================
  224. // Init static data
  225. // ================================================================
  226. int CAI_BaseNPC::m_nDebugBits = 0;
  227. CAI_BaseNPC* CAI_BaseNPC::m_pDebugNPC = NULL;
  228. int CAI_BaseNPC::m_nDebugPauseIndex = -1;
  229. CAI_ClassScheduleIdSpace CAI_BaseNPC::gm_ClassScheduleIdSpace( true );
  230. CAI_GlobalScheduleNamespace CAI_BaseNPC::gm_SchedulingSymbols;
  231. CAI_LocalIdSpace CAI_BaseNPC::gm_SquadSlotIdSpace( true );
  232. string_t CAI_BaseNPC::gm_iszPlayerSquad;
  233. int CAI_BaseNPC::gm_iNextThinkRebalanceTick;
  234. float CAI_BaseNPC::gm_flTimeLastSpawn;
  235. int CAI_BaseNPC::gm_nSpawnedThisFrame;
  236. CSimpleSimTimer CAI_BaseNPC::m_AnyUpdateEnemyPosTimer;
  237. //
  238. // Deferred Navigation calls go here
  239. //
  240. CPostFrameNavigationHook g_PostFrameNavigationHook;
  241. CPostFrameNavigationHook *PostFrameNavigationSystem( void )
  242. {
  243. return &g_PostFrameNavigationHook;
  244. }
  245. //-----------------------------------------------------------------------------
  246. // Purpose:
  247. //-----------------------------------------------------------------------------
  248. bool CPostFrameNavigationHook::Init( void )
  249. {
  250. m_Functors.Purge();
  251. m_bGameFrameRunning = false;
  252. return true;
  253. }
  254. // Main query job
  255. CJob *g_pQueuedNavigationQueryJob = NULL;
  256. static void ProcessNavigationQueries( CFunctor **pData, unsigned int nCount )
  257. {
  258. // Run all queued navigation on a separate thread
  259. for ( int i = 0; i < nCount; i++ )
  260. {
  261. (*pData[i])();
  262. }
  263. }
  264. //-----------------------------------------------------------------------------
  265. // Purpose:
  266. //-----------------------------------------------------------------------------
  267. void CPostFrameNavigationHook::FrameUpdatePreEntityThink( void )
  268. {
  269. // If the thread is executing, then wait for it to finish
  270. if ( g_pQueuedNavigationQueryJob )
  271. {
  272. g_pQueuedNavigationQueryJob->WaitForFinishAndRelease();
  273. g_pQueuedNavigationQueryJob = NULL;
  274. m_Functors.Purge();
  275. }
  276. if ( ai_post_frame_navigation.GetBool() == false )
  277. return;
  278. SetGrameFrameRunning( true );
  279. }
  280. //-----------------------------------------------------------------------------
  281. // Purpose: Now that the game frame has collected all the navigation queries, service them
  282. //-----------------------------------------------------------------------------
  283. void CPostFrameNavigationHook::FrameUpdatePostEntityThink( void )
  284. {
  285. if ( ai_post_frame_navigation.GetBool() == false )
  286. return;
  287. // The guts of the NPC will check against this to decide whether or not to queue its navigation calls
  288. SetGrameFrameRunning( false );
  289. // Throw this off to a thread job
  290. g_pQueuedNavigationQueryJob = ThreadExecute( &ProcessNavigationQueries, m_Functors.Base(), m_Functors.Count() );
  291. }
  292. //-----------------------------------------------------------------------------
  293. // Purpose: Queue up our navigation call
  294. //-----------------------------------------------------------------------------
  295. void CPostFrameNavigationHook::EnqueueEntityNavigationQuery( CAI_BaseNPC *pNPC, CFunctor *pFunctor )
  296. {
  297. if ( ai_post_frame_navigation.GetBool() == false )
  298. return;
  299. m_Functors.AddToTail( pFunctor );
  300. pNPC->SetNavigationDeferred( true );
  301. }
  302. //
  303. // Deferred Navigation calls go here
  304. //
  305. // ================================================================
  306. // Class Methods
  307. // ================================================================
  308. //-----------------------------------------------------------------------------
  309. // Purpose: Static debug function to clear schedules for all NPCS
  310. // Input :
  311. // Output :
  312. //-----------------------------------------------------------------------------
  313. void CAI_BaseNPC::ClearAllSchedules(void)
  314. {
  315. CAI_BaseNPC *npc = gEntList.NextEntByClass( (CAI_BaseNPC *)NULL );
  316. while (npc)
  317. {
  318. npc->ClearSchedule( "CAI_BaseNPC::ClearAllSchedules" );
  319. npc->GetNavigator()->ClearGoal();
  320. npc = gEntList.NextEntByClass(npc);
  321. }
  322. }
  323. // ==============================================================================
  324. //-----------------------------------------------------------------------------
  325. // Purpose:
  326. // Input :
  327. // Output :
  328. //-----------------------------------------------------------------------------
  329. bool CAI_BaseNPC::Event_Gibbed( const CTakeDamageInfo &info )
  330. {
  331. bool gibbed = CorpseGib( info );
  332. if ( gibbed )
  333. {
  334. // don't remove players!
  335. UTIL_Remove( this );
  336. SetThink( NULL ); //We're going away, so don't think anymore.
  337. }
  338. else
  339. {
  340. CorpseFade();
  341. }
  342. return gibbed;
  343. }
  344. //=========================================================
  345. // GetFlinchActivity - determines the best type of flinch
  346. // anim to play.
  347. //=========================================================
  348. Activity CAI_BaseNPC::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
  349. {
  350. Activity flinchActivity;
  351. switch ( LastHitGroup() )
  352. {
  353. // pick a region-specific flinch
  354. case HITGROUP_HEAD:
  355. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_HEAD : ACT_FLINCH_HEAD;
  356. break;
  357. case HITGROUP_STOMACH:
  358. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_STOMACH : ACT_FLINCH_STOMACH;
  359. break;
  360. case HITGROUP_LEFTARM:
  361. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTARM : ACT_FLINCH_LEFTARM;
  362. break;
  363. case HITGROUP_RIGHTARM:
  364. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTARM : ACT_FLINCH_RIGHTARM;
  365. break;
  366. case HITGROUP_LEFTLEG:
  367. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_LEFTLEG : ACT_FLINCH_LEFTLEG;
  368. break;
  369. case HITGROUP_RIGHTLEG:
  370. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_RIGHTLEG : ACT_FLINCH_RIGHTLEG;
  371. break;
  372. case HITGROUP_CHEST:
  373. flinchActivity = bGesture ? ACT_GESTURE_FLINCH_CHEST : ACT_FLINCH_CHEST;
  374. break;
  375. case HITGROUP_GEAR:
  376. case HITGROUP_GENERIC:
  377. default:
  378. // just get a generic flinch.
  379. if ( bHeavyDamage )
  380. {
  381. flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH;
  382. }
  383. else
  384. {
  385. flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
  386. }
  387. break;
  388. }
  389. // do we have a sequence for the ideal activity?
  390. if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE )
  391. {
  392. if ( bHeavyDamage )
  393. {
  394. flinchActivity = bGesture ? ACT_GESTURE_BIG_FLINCH : ACT_BIG_FLINCH;
  395. // If we fail at finding a big flinch, resort to a small one
  396. if ( SelectWeightedSequence ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE )
  397. {
  398. flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
  399. }
  400. }
  401. else
  402. {
  403. flinchActivity = bGesture ? ACT_GESTURE_SMALL_FLINCH : ACT_SMALL_FLINCH;
  404. }
  405. }
  406. return flinchActivity;
  407. }
  408. //-----------------------------------------------------------------------------
  409. // Purpose:
  410. // Input :
  411. //-----------------------------------------------------------------------------
  412. void CAI_BaseNPC::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput )
  413. {
  414. if ( !m_bDidDeathCleanup )
  415. {
  416. m_bDidDeathCleanup = true;
  417. if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
  418. {
  419. // bail out of this script here
  420. m_hCine->CancelScript();
  421. // now keep going with the death code
  422. }
  423. if ( GetHintNode() )
  424. {
  425. GetHintNode()->Unlock();
  426. SetHintNode( NULL );
  427. }
  428. if( bFireDeathOutput )
  429. {
  430. m_OnDeath.FireOutput( pCulprit, this );
  431. }
  432. // Vacate any strategy slot I might have
  433. VacateStrategySlot();
  434. // Remove from squad if in one
  435. if (m_pSquad)
  436. {
  437. // If I'm in idle it means that I didn't see who killed me
  438. // and my squad is still in idle state. Tell squad we have
  439. // an enemy to wake them up and put the enemy position at
  440. // my death position
  441. if ( m_NPCState == NPC_STATE_IDLE && pCulprit)
  442. {
  443. // If we already have some danger memory, don't do this cheat
  444. if ( GetEnemies()->GetDangerMemory() == NULL )
  445. {
  446. UpdateEnemyMemory( pCulprit, GetAbsOrigin() );
  447. }
  448. }
  449. // Remove from squad
  450. m_pSquad->RemoveFromSquad(this, true);
  451. m_pSquad = NULL;
  452. }
  453. RemoveActorFromScriptedScenes( this, false /*all scenes*/ );
  454. }
  455. else
  456. DevMsg( "Unexpected double-death-cleanup\n" );
  457. }
  458. void CAI_BaseNPC::SelectDeathPose( const CTakeDamageInfo &info )
  459. {
  460. if ( !GetModelPtr() || (info.GetDamageType() & DMG_PREVENT_PHYSICS_FORCE) )
  461. return;
  462. if ( ShouldPickADeathPose() == false )
  463. return;
  464. Activity aActivity = ACT_INVALID;
  465. int iDeathFrame = 0;
  466. SelectDeathPoseActivityAndFrame( this, info, LastHitGroup(), aActivity, iDeathFrame );
  467. if ( aActivity == ACT_INVALID )
  468. {
  469. SetDeathPose( ACT_INVALID );
  470. SetDeathPoseFrame( 0 );
  471. return;
  472. }
  473. SetDeathPose( SelectWeightedSequence( aActivity ) );
  474. SetDeathPoseFrame( iDeathFrame );
  475. }
  476. //-----------------------------------------------------------------------------
  477. // Purpose:
  478. // Input :
  479. //-----------------------------------------------------------------------------
  480. void CAI_BaseNPC::Event_Killed( const CTakeDamageInfo &info )
  481. {
  482. if (IsCurSchedule(SCHED_NPC_FREEZE))
  483. {
  484. // We're frozen; don't die.
  485. return;
  486. }
  487. Wake( false );
  488. //Adrian: Select a death pose to extrapolate the ragdoll's velocity.
  489. SelectDeathPose( info );
  490. m_lifeState = LIFE_DYING;
  491. CleanupOnDeath( info.GetAttacker() );
  492. StopLoopingSounds();
  493. DeathSound( info );
  494. if ( ( GetFlags() & FL_NPC ) && ( ShouldGib( info ) == false ) )
  495. {
  496. SetTouch( NULL );
  497. }
  498. BaseClass::Event_Killed( info );
  499. if ( m_bFadeCorpse )
  500. {
  501. m_bImportanRagdoll = RagdollManager_SaveImportant( this );
  502. }
  503. // Make sure this condition is fired too (OnTakeDamage breaks out before this happens on death)
  504. SetCondition( COND_LIGHT_DAMAGE );
  505. SetIdealState( NPC_STATE_DEAD );
  506. // Some characters rely on getting a state transition, even to death.
  507. // zombies, for instance. When a character becomes a ragdoll, their
  508. // server entity ceases to think, so we have to set the dead state here
  509. // because the AI code isn't going to pick up the change on the next think
  510. // for us.
  511. // Adrian - Only set this if we are going to become a ragdoll. We still want to
  512. // select SCHED_DIE or do something special when this NPC dies and we wont
  513. // catch the change of state if we set this to whatever the ideal state is.
  514. if ( CanBecomeRagdoll() || IsRagdoll() )
  515. SetState( NPC_STATE_DEAD );
  516. // If the remove-no-ragdoll flag is set in the damage type, we're being
  517. // told to remove ourselves immediately on death. This is used when something
  518. // else has some special reason for us to vanish instead of creating a ragdoll.
  519. // i.e. The barnacle does this because it's already got a ragdoll for us.
  520. if ( info.GetDamageType() & DMG_REMOVENORAGDOLL )
  521. {
  522. if ( !IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
  523. {
  524. // Go away
  525. RemoveDeferred();
  526. }
  527. }
  528. }
  529. //-----------------------------------------------------------------------------
  530. void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
  531. {
  532. BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
  533. #ifdef HL2_EPISODIC
  534. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  535. if ( pPlayer->IRelationType( this ) != D_LI )
  536. {
  537. CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx();
  538. if ( alyx )
  539. {
  540. alyx->EnemyIgnited( this );
  541. }
  542. }
  543. #endif
  544. }
  545. //-----------------------------------------------------------------------------
  546. ConVar ai_block_damage( "ai_block_damage","0" );
  547. bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info )
  548. {
  549. if ( ai_block_damage.GetBool() )
  550. return false;
  551. // FIXME: hook a friendly damage filter to the npc instead?
  552. if ( (CapabilitiesGet() & bits_CAP_FRIENDLY_DMG_IMMUNE) && info.GetAttacker() && info.GetAttacker() != this )
  553. {
  554. // check attackers relationship with me
  555. CBaseCombatCharacter *npcEnemy = info.GetAttacker()->MyCombatCharacterPointer();
  556. bool bHitByVehicle = false;
  557. if ( !npcEnemy )
  558. {
  559. if ( info.GetAttacker()->GetServerVehicle() )
  560. {
  561. bHitByVehicle = true;
  562. }
  563. }
  564. if ( bHitByVehicle || (npcEnemy && npcEnemy->IRelationType( this ) == D_LI) )
  565. {
  566. m_fNoDamageDecal = true;
  567. if ( npcEnemy && npcEnemy->IsPlayer() )
  568. {
  569. m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this );
  570. // This also counts as being harmed by player's squad.
  571. m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
  572. }
  573. return false;
  574. }
  575. }
  576. if ( !BaseClass::PassesDamageFilter( info ) )
  577. {
  578. m_fNoDamageDecal = true;
  579. return false;
  580. }
  581. return true;
  582. }
  583. //-----------------------------------------------------------------------------
  584. // Purpose:
  585. // Input :
  586. // Output :
  587. //-----------------------------------------------------------------------------
  588. int CAI_BaseNPC::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  589. {
  590. Forget( bits_MEMORY_INCOVER );
  591. if ( !BaseClass::OnTakeDamage_Alive( info ) )
  592. return 0;
  593. if ( GetSleepState() == AISS_WAITING_FOR_THREAT )
  594. Wake();
  595. // NOTE: This must happen after the base class is called; we need to reduce
  596. // health before the pain sound, since some NPCs use the final health
  597. // level as a modifier to determine which pain sound to use.
  598. // REVISIT: Combine soldiers shoot each other a lot and then talk about it
  599. // this improves that case a bunch, but it seems kind of harsh.
  600. if ( !m_pSquad || !m_pSquad->SquadIsMember( info.GetAttacker() ) )
  601. {
  602. PainSound( info );// "Ouch!"
  603. }
  604. // See if we're running a dynamic interaction that should break when I am damaged.
  605. if ( IsActiveDynamicInteraction() )
  606. {
  607. ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
  608. if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_DAMAGE )
  609. {
  610. // Can only break when we're in the action anim
  611. if ( m_hCine->IsPlayingAction() )
  612. {
  613. m_hCine->StopActionLoop( true );
  614. }
  615. }
  616. }
  617. // If we're not allowed to die, refuse to die
  618. // Allow my interaction partner to kill me though
  619. if ( m_iHealth <= 0 && HasInteractionCantDie() && info.GetAttacker() != m_hInteractionPartner )
  620. {
  621. m_iHealth = 1;
  622. }
  623. #if 0
  624. // HACKHACK Don't kill npcs in a script. Let them break their scripts first
  625. // THIS is a Half-Life 1 hack that's not cutting the mustard in the scripts
  626. // that have been authored for Half-Life 2 thus far. (sjb)
  627. if ( m_NPCState == NPC_STATE_SCRIPT )
  628. {
  629. SetCondition( COND_LIGHT_DAMAGE );
  630. }
  631. #endif
  632. // -----------------------------------
  633. // Fire outputs
  634. // -----------------------------------
  635. if ( m_flLastDamageTime != gpGlobals->curtime )
  636. {
  637. // only fire once per frame
  638. m_OnDamaged.FireOutput( info.GetAttacker(), this);
  639. if( info.GetAttacker()->IsPlayer() )
  640. {
  641. m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this );
  642. // This also counts as being harmed by player's squad.
  643. m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
  644. }
  645. else
  646. {
  647. // See if the person that injured me is an NPC.
  648. CAI_BaseNPC *pAttacker = dynamic_cast<CAI_BaseNPC *>( info.GetAttacker() );
  649. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  650. if( pAttacker && pAttacker->IsAlive() && pPlayer )
  651. {
  652. if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() )
  653. {
  654. m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this );
  655. }
  656. }
  657. }
  658. }
  659. if( (info.GetDamageType() & DMG_CRUSH) && !(info.GetDamageType() & DMG_PHYSGUN) && info.GetDamage() >= MIN_PHYSICS_FLINCH_DAMAGE )
  660. {
  661. SetCondition( COND_PHYSICS_DAMAGE );
  662. }
  663. if ( m_iHealth <= ( m_iMaxHealth / 2 ) )
  664. {
  665. m_OnHalfHealth.FireOutput( info.GetAttacker(), this );
  666. }
  667. // react to the damage (get mad)
  668. if ( ( (GetFlags() & FL_NPC) == 0 ) || !info.GetAttacker() )
  669. return 1;
  670. // If the attacker was an NPC or client update my position memory
  671. if ( info.GetAttacker()->GetFlags() & (FL_NPC | FL_CLIENT) )
  672. {
  673. // ------------------------------------------------------------------
  674. // DO NOT CHANGE THIS CODE W/O CONSULTING
  675. // Only update information about my attacker I don't see my attacker
  676. // ------------------------------------------------------------------
  677. if ( !FInViewCone( info.GetAttacker() ) || !FVisible( info.GetAttacker() ) )
  678. {
  679. // -------------------------------------------------------------
  680. // If I have an inflictor (enemy / grenade) update memory with
  681. // position of inflictor, otherwise update with an position
  682. // estimate for where the attack came from
  683. // ------------------------------------------------------
  684. Vector vAttackPos;
  685. if (info.GetInflictor())
  686. {
  687. vAttackPos = info.GetInflictor()->GetAbsOrigin();
  688. }
  689. else
  690. {
  691. vAttackPos = (GetAbsOrigin() + ( g_vecAttackDir * 64 ));
  692. }
  693. // ----------------------------------------------------------------
  694. // If I already have an enemy, assume that the attack
  695. // came from the enemy and update my enemy's position
  696. // unless I already know about the attacker or I can see my enemy
  697. // ----------------------------------------------------------------
  698. if ( GetEnemy() != NULL &&
  699. !GetEnemies()->HasMemory( info.GetAttacker() ) &&
  700. !HasCondition(COND_SEE_ENEMY) )
  701. {
  702. UpdateEnemyMemory(GetEnemy(), vAttackPos, GetEnemy());
  703. }
  704. // ----------------------------------------------------------------
  705. // If I already know about this enemy, update his position
  706. // ----------------------------------------------------------------
  707. else if (GetEnemies()->HasMemory( info.GetAttacker() ))
  708. {
  709. UpdateEnemyMemory(info.GetAttacker(), vAttackPos);
  710. }
  711. // -----------------------------------------------------------------
  712. // Otherwise just note the position, but don't add enemy to my list
  713. // -----------------------------------------------------------------
  714. else
  715. {
  716. UpdateEnemyMemory(NULL, vAttackPos);
  717. }
  718. }
  719. // add pain to the conditions
  720. if ( IsLightDamage( info ) )
  721. {
  722. SetCondition( COND_LIGHT_DAMAGE );
  723. }
  724. if ( IsHeavyDamage( info ) )
  725. {
  726. SetCondition( COND_HEAVY_DAMAGE );
  727. }
  728. ForceGatherConditions();
  729. // Keep track of how much consecutive damage I have recieved
  730. if ((gpGlobals->curtime - m_flLastDamageTime) < 1.0)
  731. {
  732. m_flSumDamage += info.GetDamage();
  733. }
  734. else
  735. {
  736. m_flSumDamage = info.GetDamage();
  737. }
  738. m_flLastDamageTime = gpGlobals->curtime;
  739. if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
  740. m_flLastPlayerDamageTime = gpGlobals->curtime;
  741. GetEnemies()->OnTookDamageFrom( info.GetAttacker() );
  742. if (m_flSumDamage > m_iMaxHealth*0.3)
  743. {
  744. SetCondition(COND_REPEATED_DAMAGE);
  745. }
  746. NotifyFriendsOfDamage( info.GetAttacker() );
  747. }
  748. // ---------------------------------------------------------------
  749. // Insert a combat sound so that nearby NPCs know I've been hit
  750. // ---------------------------------------------------------------
  751. CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1024, 0.5, this, SOUNDENT_CHANNEL_INJURY );
  752. return 1;
  753. }
  754. //=========================================================
  755. // OnTakeDamage_Dying - takedamage function called when a npc's
  756. // corpse is damaged.
  757. //=========================================================
  758. int CAI_BaseNPC::OnTakeDamage_Dying( const CTakeDamageInfo &info )
  759. {
  760. if ( info.GetDamageType() & DMG_PLASMA )
  761. {
  762. if ( m_takedamage != DAMAGE_EVENTS_ONLY )
  763. {
  764. m_iHealth -= info.GetDamage();
  765. if (m_iHealth < -500)
  766. {
  767. UTIL_Remove(this);
  768. }
  769. }
  770. }
  771. return BaseClass::OnTakeDamage_Dying( info );
  772. }
  773. //=========================================================
  774. // OnTakeDamage_Dead - takedamage function called when a npc's
  775. // corpse is damaged.
  776. //=========================================================
  777. int CAI_BaseNPC::OnTakeDamage_Dead( const CTakeDamageInfo &info )
  778. {
  779. Vector vecDir;
  780. // 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).
  781. vecDir = vec3_origin;
  782. if ( info.GetInflictor() )
  783. {
  784. vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter();
  785. VectorNormalize( vecDir );
  786. g_vecAttackDir = vecDir;
  787. }
  788. #if 0// turn this back on when the bounding box issues are resolved.
  789. SetGroundEntity( NULL );
  790. GetLocalOrigin().z += 1;
  791. // let the damage scoot the corpse around a bit.
  792. if ( info.GetInflictor() && (info.GetAttacker()->GetSolid() != SOLID_TRIGGER) )
  793. {
  794. ApplyAbsVelocityImpulse( vecDir * -DamageForce( flDamage ) );
  795. }
  796. #endif
  797. // 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.
  798. if ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) )
  799. {
  800. // Accumulate corpse gibbing damage, so you can gib with multiple hits
  801. if ( m_takedamage != DAMAGE_EVENTS_ONLY )
  802. {
  803. m_iHealth -= info.GetDamage() * 0.1;
  804. }
  805. }
  806. if ( info.GetDamageType() & DMG_PLASMA )
  807. {
  808. if ( m_takedamage != DAMAGE_EVENTS_ONLY )
  809. {
  810. m_iHealth -= info.GetDamage();
  811. if (m_iHealth < -500)
  812. {
  813. UTIL_Remove(this);
  814. }
  815. }
  816. }
  817. return 1;
  818. }
  819. //-----------------------------------------------------------------------------
  820. //-----------------------------------------------------------------------------
  821. void CAI_BaseNPC::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity )
  822. {
  823. CAI_BaseNPC *pAttacker = pAttackerEntity->MyNPCPointer();
  824. if ( pAttacker )
  825. {
  826. const Vector &origin = GetAbsOrigin();
  827. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  828. {
  829. const float NEAR_Z = 10*12;
  830. const float NEAR_XY_SQ = Square( 50*12 );
  831. CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i];
  832. if ( pNpc && pNpc != this )
  833. {
  834. const Vector &originNpc = pNpc->GetAbsOrigin();
  835. if ( fabsf( originNpc.z - origin.z ) < NEAR_Z )
  836. {
  837. if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ )
  838. {
  839. if ( pNpc->GetSquad() == GetSquad() || IRelationType( pNpc ) == D_LI )
  840. pNpc->OnFriendDamaged( this, pAttacker );
  841. }
  842. }
  843. }
  844. }
  845. }
  846. }
  847. //-----------------------------------------------------------------------------
  848. //-----------------------------------------------------------------------------
  849. void CAI_BaseNPC::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity *pAttacker )
  850. {
  851. if ( GetSleepState() != AISS_WAITING_FOR_INPUT )
  852. {
  853. float distSqToThreat = ( GetAbsOrigin() - pAttacker->GetAbsOrigin() ).LengthSqr();
  854. if ( GetSleepState() != AISS_AWAKE && distSqToThreat < Square( 20 * 12 ) )
  855. Wake();
  856. if ( distSqToThreat < Square( 50 * 12 ) )
  857. ForceGatherConditions();
  858. }
  859. }
  860. //-----------------------------------------------------------------------------
  861. //-----------------------------------------------------------------------------
  862. bool CAI_BaseNPC::IsLightDamage( const CTakeDamageInfo &info )
  863. {
  864. // ALL nonzero damage is light damage! Mask off COND_LIGHT_DAMAGE if you want to ignore light damage.
  865. return ( info.GetDamage() > 0 );
  866. }
  867. bool CAI_BaseNPC::IsHeavyDamage( const CTakeDamageInfo &info )
  868. {
  869. return ( info.GetDamage() > 20 );
  870. }
  871. void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, int iClassIgnore, CBaseEntity *pEntityIgnore )
  872. {
  873. RadiusDamage( info, GetAbsOrigin(), info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore );
  874. }
  875. void CAI_BaseNPC::DoRadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrc, int iClassIgnore, CBaseEntity *pEntityIgnore )
  876. {
  877. RadiusDamage( info, vecSrc, info.GetDamage() * 2.5, iClassIgnore, pEntityIgnore );
  878. }
  879. //-----------------------------------------------------------------------------
  880. // Set the contents types that are solid by default to all NPCs
  881. //-----------------------------------------------------------------------------
  882. unsigned int CAI_BaseNPC::PhysicsSolidMaskForEntity( void ) const
  883. {
  884. return MASK_NPCSOLID;
  885. }
  886. //=========================================================
  887. //-----------------------------------------------------------------------------
  888. // Purpose:
  889. //-----------------------------------------------------------------------------
  890. void CAI_BaseNPC::DecalTrace( trace_t *pTrace, char const *decalName )
  891. {
  892. if ( m_fNoDamageDecal )
  893. {
  894. m_fNoDamageDecal = false;
  895. // @Note (toml 04-23-03): e3, don't decal face on damage if still alive
  896. return;
  897. }
  898. BaseClass::DecalTrace( pTrace, decalName );
  899. }
  900. //-----------------------------------------------------------------------------
  901. // Purpose:
  902. //-----------------------------------------------------------------------------
  903. void CAI_BaseNPC::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
  904. {
  905. if ( m_fNoDamageDecal )
  906. {
  907. m_fNoDamageDecal = false;
  908. // @Note (toml 04-23-03): e3, don't decal face on damage if still alive
  909. return;
  910. }
  911. BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName );
  912. }
  913. //---------------------------------------------------------
  914. // Return the number by which to multiply incoming damage
  915. // based on the hitgroup it hits. This exists mainly so
  916. // that individual NPC's can have more or less resistance
  917. // to damage done to certain hitgroups.
  918. //---------------------------------------------------------
  919. float CAI_BaseNPC::GetHitgroupDamageMultiplier( int iHitGroup, const CTakeDamageInfo &info )
  920. {
  921. switch( iHitGroup )
  922. {
  923. case HITGROUP_GENERIC:
  924. return 1.0f;
  925. case HITGROUP_HEAD:
  926. return sk_npc_head.GetFloat();
  927. case HITGROUP_CHEST:
  928. return sk_npc_chest.GetFloat();
  929. case HITGROUP_STOMACH:
  930. return sk_npc_stomach.GetFloat();
  931. case HITGROUP_LEFTARM:
  932. case HITGROUP_RIGHTARM:
  933. return sk_npc_arm.GetFloat();
  934. case HITGROUP_LEFTLEG:
  935. case HITGROUP_RIGHTLEG:
  936. return sk_npc_leg.GetFloat();
  937. default:
  938. return 1.0f;
  939. }
  940. }
  941. //=========================================================
  942. // TraceAttack
  943. //=========================================================
  944. void CAI_BaseNPC::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  945. {
  946. m_fNoDamageDecal = false;
  947. if ( m_takedamage == DAMAGE_NO )
  948. return;
  949. CTakeDamageInfo subInfo = info;
  950. SetLastHitGroup( ptr->hitgroup );
  951. m_nForceBone = ptr->physicsbone; // save this bone for physics forces
  952. Assert( m_nForceBone > -255 && m_nForceBone < 256 );
  953. bool bDebug = showhitlocation.GetBool();
  954. switch ( ptr->hitgroup )
  955. {
  956. case HITGROUP_GENERIC:
  957. if( bDebug ) DevMsg("Hit Location: Generic\n");
  958. break;
  959. // hit gear, react but don't bleed
  960. case HITGROUP_GEAR:
  961. subInfo.SetDamage( 0.01 );
  962. ptr->hitgroup = HITGROUP_GENERIC;
  963. if( bDebug ) DevMsg("Hit Location: Gear\n");
  964. break;
  965. case HITGROUP_HEAD:
  966. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  967. if( bDebug ) DevMsg("Hit Location: Head\n");
  968. break;
  969. case HITGROUP_CHEST:
  970. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  971. if( bDebug ) DevMsg("Hit Location: Chest\n");
  972. break;
  973. case HITGROUP_STOMACH:
  974. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  975. if( bDebug ) DevMsg("Hit Location: Stomach\n");
  976. break;
  977. case HITGROUP_LEFTARM:
  978. case HITGROUP_RIGHTARM:
  979. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  980. if( bDebug ) DevMsg("Hit Location: Left/Right Arm\n");
  981. break
  982. ;
  983. case HITGROUP_LEFTLEG:
  984. case HITGROUP_RIGHTLEG:
  985. subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
  986. if( bDebug ) DevMsg("Hit Location: Left/Right Leg\n");
  987. break;
  988. default:
  989. if( bDebug ) DevMsg("Hit Location: UNKNOWN\n");
  990. break;
  991. }
  992. if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK ) )
  993. {
  994. if( !IsPlayer() || ( IsPlayer() && g_pGameRules->IsMultiplayer() ) )
  995. {
  996. // NPC's always bleed. Players only bleed in multiplayer.
  997. SpawnBlood( ptr->endpos, vecDir, BloodColor(), subInfo.GetDamage() );// a little surface blood.
  998. }
  999. TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() );
  1000. if ( ptr->hitgroup == HITGROUP_HEAD && m_iHealth - subInfo.GetDamage() > 0 )
  1001. {
  1002. m_fNoDamageDecal = true;
  1003. }
  1004. }
  1005. // Airboat gun will impart major force if it's about to kill him....
  1006. if ( info.GetDamageType() & DMG_AIRBOAT )
  1007. {
  1008. if ( subInfo.GetDamage() >= GetHealth() )
  1009. {
  1010. float flMagnitude = subInfo.GetDamageForce().Length();
  1011. if ( (flMagnitude != 0.0f) && (flMagnitude < 400.0f * 65.0f) )
  1012. {
  1013. subInfo.ScaleDamageForce( 400.0f * 65.0f / flMagnitude );
  1014. }
  1015. }
  1016. }
  1017. if( info.GetInflictor() )
  1018. {
  1019. subInfo.SetInflictor( info.GetInflictor() );
  1020. }
  1021. else
  1022. {
  1023. subInfo.SetInflictor( info.GetAttacker() );
  1024. }
  1025. AddMultiDamage( subInfo, this );
  1026. }
  1027. //-----------------------------------------------------------------------------
  1028. // Purpose: Checks if point is in spread angle between source and target Pos
  1029. // Used to prevent friendly fire
  1030. // Input : Source of attack, target position, spread angle
  1031. // Output :
  1032. //-----------------------------------------------------------------------------
  1033. bool CAI_BaseNPC::PointInSpread( CBaseCombatCharacter *pCheckEntity, const Vector &sourcePos, const Vector &targetPos, const Vector &testPoint, float flSpread, float maxDistOffCenter )
  1034. {
  1035. float distOffLine = CalcDistanceToLine2D( testPoint.AsVector2D(), sourcePos.AsVector2D(), targetPos.AsVector2D() );
  1036. if ( distOffLine < maxDistOffCenter )
  1037. {
  1038. Vector toTarget = targetPos - sourcePos;
  1039. float distTarget = VectorNormalize(toTarget);
  1040. Vector toTest = testPoint - sourcePos;
  1041. float distTest = VectorNormalize(toTest);
  1042. // Only reject if target is on other side
  1043. if (distTarget > distTest)
  1044. {
  1045. toTarget.z = 0.0;
  1046. toTest.z = 0.0;
  1047. float dotProduct = DotProduct(toTarget,toTest);
  1048. if (dotProduct > flSpread)
  1049. {
  1050. return true;
  1051. }
  1052. else if( dotProduct > 0.0f )
  1053. {
  1054. // If this guy is in front, do the hull/line test:
  1055. if( pCheckEntity )
  1056. {
  1057. float flBBoxDist = NAI_Hull::Width( pCheckEntity->GetHullType() );
  1058. flBBoxDist *= 1.414f; // sqrt(2)
  1059. // !!!BUGBUG - this 2d check will stop a citizen shooting at a gunship or strider
  1060. // if another citizen is between them, even though the strider or gunship may be
  1061. // high up in the air (sjb)
  1062. distOffLine = CalcDistanceToLine( testPoint, sourcePos, targetPos );
  1063. if( distOffLine < flBBoxDist )
  1064. {
  1065. return true;
  1066. }
  1067. }
  1068. }
  1069. }
  1070. }
  1071. return false;
  1072. }
  1073. //-----------------------------------------------------------------------------
  1074. // Purpose: Checks if player is in spread angle between source and target Pos
  1075. // Used to prevent friendly fire
  1076. // Input : Source of attack, target position, spread angle
  1077. // Output :
  1078. //-----------------------------------------------------------------------------
  1079. bool CAI_BaseNPC::PlayerInSpread( const Vector &sourcePos, const Vector &targetPos, float flSpread, float maxDistOffCenter, bool ignoreHatedPlayers )
  1080. {
  1081. // loop through all players
  1082. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  1083. {
  1084. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  1085. if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) != D_HT ) )
  1086. {
  1087. if ( PointInSpread( pPlayer, sourcePos, targetPos, pPlayer->WorldSpaceCenter(), flSpread, maxDistOffCenter ) )
  1088. return true;
  1089. }
  1090. }
  1091. return false;
  1092. }
  1093. //-----------------------------------------------------------------------------
  1094. // Purpose: Checks if player is in range of given location. Used by NPCs
  1095. // to prevent friendly fire
  1096. // Input :
  1097. // Output :
  1098. //-----------------------------------------------------------------------------
  1099. CBaseEntity *CAI_BaseNPC::PlayerInRange( const Vector &vecLocation, float flDist )
  1100. {
  1101. // loop through all players
  1102. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  1103. {
  1104. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  1105. if (pPlayer && (vecLocation - pPlayer->WorldSpaceCenter() ).Length2D() <= flDist)
  1106. {
  1107. return pPlayer;
  1108. }
  1109. }
  1110. return NULL;
  1111. }
  1112. #define BULLET_WIZZDIST 80.0
  1113. #define SLOPE ( -1.0 / BULLET_WIZZDIST )
  1114. void BulletWizz( Vector vecSrc, Vector vecEndPos, edict_t *pShooter, bool isTracer )
  1115. {
  1116. CBasePlayer *pPlayer;
  1117. Vector vecBulletPath;
  1118. Vector vecPlayerPath;
  1119. Vector vecBulletDir;
  1120. Vector vecNearestPoint;
  1121. float flDist;
  1122. float flBulletDist;
  1123. vecBulletPath = vecEndPos - vecSrc;
  1124. vecBulletDir = vecBulletPath;
  1125. VectorNormalize(vecBulletDir);
  1126. // see how near this bullet passed by player in a single player game
  1127. // for multiplayer, we need to go through the list of clients.
  1128. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  1129. {
  1130. pPlayer = UTIL_PlayerByIndex( i );
  1131. if ( !pPlayer )
  1132. continue;
  1133. // Don't hear one's own bullets
  1134. if( pPlayer->edict() == pShooter )
  1135. continue;
  1136. vecPlayerPath = pPlayer->EarPosition() - vecSrc;
  1137. flDist = DotProduct( vecPlayerPath, vecBulletDir );
  1138. vecNearestPoint = vecSrc + vecBulletDir * flDist;
  1139. // FIXME: minus m_vecViewOffset?
  1140. flBulletDist = ( vecNearestPoint - pPlayer->EarPosition() ).Length();
  1141. }
  1142. }
  1143. //-----------------------------------------------------------------------------
  1144. // Hits triggers with raycasts
  1145. //-----------------------------------------------------------------------------
  1146. class CTriggerTraceEnum : public IEntityEnumerator
  1147. {
  1148. public:
  1149. CTriggerTraceEnum( Ray_t *pRay, const CTakeDamageInfo &info, const Vector& dir, int contentsMask ) :
  1150. m_info( info ), m_VecDir(dir), m_ContentsMask(contentsMask), m_pRay(pRay)
  1151. {
  1152. }
  1153. virtual bool EnumEntity( IHandleEntity *pHandleEntity )
  1154. {
  1155. trace_t tr;
  1156. CBaseEntity *pEnt = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
  1157. // Done to avoid hitting an entity that's both solid & a trigger.
  1158. if ( pEnt->IsSolid() )
  1159. return true;
  1160. enginetrace->ClipRayToEntity( *m_pRay, m_ContentsMask, pHandleEntity, &tr );
  1161. if (tr.fraction < 1.0f)
  1162. {
  1163. pEnt->DispatchTraceAttack( m_info, m_VecDir, &tr );
  1164. ApplyMultiDamage();
  1165. }
  1166. return true;
  1167. }
  1168. private:
  1169. Vector m_VecDir;
  1170. int m_ContentsMask;
  1171. Ray_t *m_pRay;
  1172. CTakeDamageInfo m_info;
  1173. };
  1174. void CBaseEntity::TraceAttackToTriggers( const CTakeDamageInfo &info, const Vector& start, const Vector& end, const Vector& dir )
  1175. {
  1176. Ray_t ray;
  1177. ray.Init( start, end );
  1178. CTriggerTraceEnum triggerTraceEnum( &ray, info, dir, MASK_SHOT );
  1179. enginetrace->EnumerateEntities( ray, true, &triggerTraceEnum );
  1180. }
  1181. //-----------------------------------------------------------------------------
  1182. // Purpose:
  1183. // Output : const char
  1184. //-----------------------------------------------------------------------------
  1185. const char *CAI_BaseNPC::GetTracerType( void )
  1186. {
  1187. if ( GetActiveWeapon() )
  1188. {
  1189. return GetActiveWeapon()->GetTracerType();
  1190. }
  1191. return BaseClass::GetTracerType();
  1192. }
  1193. //-----------------------------------------------------------------------------
  1194. // Purpose:
  1195. // Input : &vecTracerSrc -
  1196. // &tr -
  1197. // iTracerType -
  1198. //-----------------------------------------------------------------------------
  1199. void CAI_BaseNPC::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
  1200. {
  1201. if ( GetActiveWeapon() )
  1202. {
  1203. GetActiveWeapon()->MakeTracer( vecTracerSrc, tr, iTracerType );
  1204. return;
  1205. }
  1206. BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
  1207. }
  1208. //-----------------------------------------------------------------------------
  1209. // Purpose:
  1210. //-----------------------------------------------------------------------------
  1211. void CAI_BaseNPC::FireBullets( const FireBulletsInfo_t &info )
  1212. {
  1213. #ifdef HL2_DLL
  1214. // If we're shooting at a bullseye, become perfectly accurate if the bullseye demands it
  1215. if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
  1216. {
  1217. CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(GetEnemy());
  1218. if ( pBullseye && pBullseye->UsePerfectAccuracy() )
  1219. {
  1220. FireBulletsInfo_t accurateInfo = info;
  1221. accurateInfo.m_vecSpread = vec3_origin;
  1222. BaseClass::FireBullets( accurateInfo );
  1223. return;
  1224. }
  1225. }
  1226. #endif
  1227. BaseClass::FireBullets( info );
  1228. }
  1229. //-----------------------------------------------------------------------------
  1230. // Shot statistics
  1231. //-----------------------------------------------------------------------------
  1232. void CBaseEntity::UpdateShotStatistics( const trace_t &tr )
  1233. {
  1234. if ( ai_shot_stats.GetBool() )
  1235. {
  1236. CAI_BaseNPC *pNpc = MyNPCPointer();
  1237. if ( pNpc )
  1238. {
  1239. pNpc->m_TotalShots++;
  1240. if ( tr.m_pEnt == pNpc->GetEnemy() )
  1241. {
  1242. pNpc->m_TotalHits++;
  1243. }
  1244. }
  1245. }
  1246. }
  1247. //-----------------------------------------------------------------------------
  1248. // Handle shot entering water
  1249. //-----------------------------------------------------------------------------
  1250. void CBaseEntity::HandleShotImpactingGlass( const FireBulletsInfo_t &info,
  1251. const trace_t &tr, const Vector &vecDir, ITraceFilter *pTraceFilter )
  1252. {
  1253. // Move through the glass until we're at the other side
  1254. Vector testPos = tr.endpos + ( vecDir * MAX_GLASS_PENETRATION_DEPTH );
  1255. CEffectData data;
  1256. data.m_vNormal = tr.plane.normal;
  1257. data.m_vOrigin = tr.endpos;
  1258. DispatchEffect( "GlassImpact", data );
  1259. trace_t penetrationTrace;
  1260. // Re-trace as if the bullet had passed right through
  1261. UTIL_TraceLine( testPos, tr.endpos, MASK_SHOT, pTraceFilter, &penetrationTrace );
  1262. // See if we found the surface again
  1263. if ( penetrationTrace.startsolid || tr.fraction == 0.0f || penetrationTrace.fraction == 1.0f )
  1264. return;
  1265. //FIXME: This is technically frustrating MultiDamage, but multiple shots hitting multiple targets in one call
  1266. // would do exactly the same anyway...
  1267. // Impact the other side (will look like an exit effect)
  1268. DoImpactEffect( penetrationTrace, GetAmmoDef()->DamageType(info.m_iAmmoType) );
  1269. data.m_vNormal = penetrationTrace.plane.normal;
  1270. data.m_vOrigin = penetrationTrace.endpos;
  1271. DispatchEffect( "GlassImpact", data );
  1272. // Refire the round, as if starting from behind the glass
  1273. FireBulletsInfo_t behindGlassInfo;
  1274. behindGlassInfo.m_iShots = 1;
  1275. behindGlassInfo.m_vecSrc = penetrationTrace.endpos;
  1276. behindGlassInfo.m_vecDirShooting = vecDir;
  1277. behindGlassInfo.m_vecSpread = vec3_origin;
  1278. behindGlassInfo.m_flDistance = info.m_flDistance*( 1.0f - tr.fraction );
  1279. behindGlassInfo.m_iAmmoType = info.m_iAmmoType;
  1280. behindGlassInfo.m_iTracerFreq = info.m_iTracerFreq;
  1281. behindGlassInfo.m_flDamage = info.m_flDamage;
  1282. behindGlassInfo.m_pAttacker = info.m_pAttacker ? info.m_pAttacker : this;
  1283. behindGlassInfo.m_nFlags = info.m_nFlags;
  1284. FireBullets( behindGlassInfo );
  1285. }
  1286. //-----------------------------------------------------------------------------
  1287. // Computes the tracer start position
  1288. //-----------------------------------------------------------------------------
  1289. #define SHOT_UNDERWATER_BUBBLE_DIST 400
  1290. void CBaseEntity::CreateBubbleTrailTracer( const Vector &vecShotSrc, const Vector &vecShotEnd, const Vector &vecShotDir )
  1291. {
  1292. int nBubbles;
  1293. Vector vecBubbleEnd;
  1294. float flLengthSqr = vecShotSrc.DistToSqr( vecShotEnd );
  1295. if ( flLengthSqr > SHOT_UNDERWATER_BUBBLE_DIST * SHOT_UNDERWATER_BUBBLE_DIST )
  1296. {
  1297. VectorMA( vecShotSrc, SHOT_UNDERWATER_BUBBLE_DIST, vecShotDir, vecBubbleEnd );
  1298. nBubbles = WATER_BULLET_BUBBLES_PER_INCH * SHOT_UNDERWATER_BUBBLE_DIST;
  1299. }
  1300. else
  1301. {
  1302. float flLength = sqrt(flLengthSqr) - 0.1f;
  1303. nBubbles = WATER_BULLET_BUBBLES_PER_INCH * flLength;
  1304. VectorMA( vecShotSrc, flLength, vecShotDir, vecBubbleEnd );
  1305. }
  1306. Vector vecTracerSrc;
  1307. ComputeTracerStartPosition( vecShotSrc, &vecTracerSrc );
  1308. UTIL_BubbleTrail( vecTracerSrc, vecBubbleEnd, nBubbles );
  1309. }
  1310. //=========================================================
  1311. //=========================================================
  1312. void CAI_BaseNPC::MakeDamageBloodDecal ( int cCount, float flNoise, trace_t *ptr, Vector vecDir )
  1313. {
  1314. // make blood decal on the wall!
  1315. trace_t Bloodtr;
  1316. Vector vecTraceDir;
  1317. int i;
  1318. if ( !IsAlive() )
  1319. {
  1320. // dealing with a dead npc.
  1321. if ( m_iMaxHealth <= 0 )
  1322. {
  1323. // no blood decal for a npc that has already decalled its limit.
  1324. return;
  1325. }
  1326. else
  1327. {
  1328. m_iMaxHealth -= 1;
  1329. }
  1330. }
  1331. for ( i = 0 ; i < cCount ; i++ )
  1332. {
  1333. vecTraceDir = vecDir;
  1334. vecTraceDir.x += random->RandomFloat( -flNoise, flNoise );
  1335. vecTraceDir.y += random->RandomFloat( -flNoise, flNoise );
  1336. vecTraceDir.z += random->RandomFloat( -flNoise, flNoise );
  1337. AI_TraceLine( ptr->endpos, ptr->endpos + vecTraceDir * 172, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &Bloodtr);
  1338. if ( Bloodtr.fraction != 1.0 )
  1339. {
  1340. UTIL_BloodDecalTrace( &Bloodtr, BloodColor() );
  1341. }
  1342. }
  1343. }
  1344. //-----------------------------------------------------------------------------
  1345. // Purpose:
  1346. // Input : &tr -
  1347. // nDamageType -
  1348. //-----------------------------------------------------------------------------
  1349. void CAI_BaseNPC::DoImpactEffect( trace_t &tr, int nDamageType )
  1350. {
  1351. if ( GetActiveWeapon() != NULL )
  1352. {
  1353. GetActiveWeapon()->DoImpactEffect( tr, nDamageType );
  1354. return;
  1355. }
  1356. BaseClass::DoImpactEffect( tr, nDamageType );
  1357. }
  1358. //---------------------------------------------------------
  1359. //---------------------------------------------------------
  1360. #define InterruptFromCondition( iCondition ) \
  1361. AI_RemapFromGlobal( ( AI_IdIsLocal( iCondition ) ? GetClassScheduleIdSpace()->ConditionLocalToGlobal( iCondition ) : iCondition ) )
  1362. void CAI_BaseNPC::SetCondition( int iCondition )
  1363. {
  1364. int interrupt = InterruptFromCondition( iCondition );
  1365. if ( interrupt == -1 )
  1366. {
  1367. Assert(0);
  1368. return;
  1369. }
  1370. m_Conditions.Set( interrupt );
  1371. }
  1372. //---------------------------------------------------------
  1373. //---------------------------------------------------------
  1374. bool CAI_BaseNPC::HasCondition( int iCondition )
  1375. {
  1376. int interrupt = InterruptFromCondition( iCondition );
  1377. if ( interrupt == -1 )
  1378. {
  1379. Assert(0);
  1380. return false;
  1381. }
  1382. bool bReturn = m_Conditions.IsBitSet(interrupt);
  1383. return (bReturn);
  1384. }
  1385. //---------------------------------------------------------
  1386. //---------------------------------------------------------
  1387. bool CAI_BaseNPC::HasCondition( int iCondition, bool bUseIgnoreConditions )
  1388. {
  1389. if ( bUseIgnoreConditions )
  1390. return HasCondition( iCondition );
  1391. int interrupt = InterruptFromCondition( iCondition );
  1392. if ( interrupt == -1 )
  1393. {
  1394. Assert(0);
  1395. return false;
  1396. }
  1397. bool bReturn = m_ConditionsPreIgnore.IsBitSet(interrupt);
  1398. return (bReturn);
  1399. }
  1400. //---------------------------------------------------------
  1401. //---------------------------------------------------------
  1402. void CAI_BaseNPC::ClearCondition( int iCondition )
  1403. {
  1404. int interrupt = InterruptFromCondition( iCondition );
  1405. if ( interrupt == -1 )
  1406. {
  1407. Assert(0);
  1408. return;
  1409. }
  1410. m_Conditions.Clear(interrupt);
  1411. }
  1412. //---------------------------------------------------------
  1413. //---------------------------------------------------------
  1414. void CAI_BaseNPC::ClearConditions( int *pConditions, int nConditions )
  1415. {
  1416. for ( int i = 0; i < nConditions; ++i )
  1417. {
  1418. int iCondition = pConditions[i];
  1419. int interrupt = InterruptFromCondition( iCondition );
  1420. if ( interrupt == -1 )
  1421. {
  1422. Assert(0);
  1423. continue;
  1424. }
  1425. m_Conditions.Clear( interrupt );
  1426. }
  1427. }
  1428. //---------------------------------------------------------
  1429. //---------------------------------------------------------
  1430. void CAI_BaseNPC::SetIgnoreConditions( int *pConditions, int nConditions )
  1431. {
  1432. for ( int i = 0; i < nConditions; ++i )
  1433. {
  1434. int iCondition = pConditions[i];
  1435. int interrupt = InterruptFromCondition( iCondition );
  1436. if ( interrupt == -1 )
  1437. {
  1438. Assert(0);
  1439. continue;
  1440. }
  1441. m_InverseIgnoreConditions.Clear( interrupt ); // clear means ignore
  1442. }
  1443. }
  1444. void CAI_BaseNPC::ClearIgnoreConditions( int *pConditions, int nConditions )
  1445. {
  1446. for ( int i = 0; i < nConditions; ++i )
  1447. {
  1448. int iCondition = pConditions[i];
  1449. int interrupt = InterruptFromCondition( iCondition );
  1450. if ( interrupt == -1 )
  1451. {
  1452. Assert(0);
  1453. continue;
  1454. }
  1455. m_InverseIgnoreConditions.Set( interrupt ); // set means don't ignore
  1456. }
  1457. }
  1458. //---------------------------------------------------------
  1459. //---------------------------------------------------------
  1460. bool CAI_BaseNPC::HasInterruptCondition( int iCondition )
  1461. {
  1462. if( !GetCurSchedule() )
  1463. {
  1464. return false;
  1465. }
  1466. int interrupt = InterruptFromCondition( iCondition );
  1467. if ( interrupt == -1 )
  1468. {
  1469. Assert(0);
  1470. return false;
  1471. }
  1472. return ( m_Conditions.IsBitSet( interrupt ) && GetCurSchedule()->HasInterrupt( interrupt ) );
  1473. }
  1474. //---------------------------------------------------------
  1475. //---------------------------------------------------------
  1476. bool CAI_BaseNPC::ConditionInterruptsCurSchedule( int iCondition )
  1477. {
  1478. if( !GetCurSchedule() )
  1479. {
  1480. return false;
  1481. }
  1482. int interrupt = InterruptFromCondition( iCondition );
  1483. if ( interrupt == -1 )
  1484. {
  1485. Assert(0);
  1486. return false;
  1487. }
  1488. return ( GetCurSchedule()->HasInterrupt( interrupt ) );
  1489. }
  1490. //---------------------------------------------------------
  1491. //---------------------------------------------------------
  1492. bool CAI_BaseNPC::ConditionInterruptsSchedule( int localScheduleID, int iCondition )
  1493. {
  1494. CAI_Schedule *pSchedule = GetSchedule( localScheduleID );
  1495. if ( !pSchedule )
  1496. return false;
  1497. int interrupt = InterruptFromCondition( iCondition );
  1498. if ( interrupt == -1 )
  1499. {
  1500. Assert(0);
  1501. return false;
  1502. }
  1503. return ( pSchedule->HasInterrupt( interrupt ) );
  1504. }
  1505. //-----------------------------------------------------------------------------
  1506. // Purpose: Sets the interrupt conditions for a scripted schedule
  1507. // Input : interrupt - the level of interrupt we allow
  1508. //-----------------------------------------------------------------------------
  1509. void CAI_BaseNPC::SetScriptedScheduleIgnoreConditions( Interruptability_t interrupt )
  1510. {
  1511. static int g_GeneralConditions[] =
  1512. {
  1513. COND_CAN_MELEE_ATTACK1,
  1514. COND_CAN_MELEE_ATTACK2,
  1515. COND_CAN_RANGE_ATTACK1,
  1516. COND_CAN_RANGE_ATTACK2,
  1517. COND_ENEMY_DEAD,
  1518. COND_HEAR_BULLET_IMPACT,
  1519. COND_HEAR_COMBAT,
  1520. COND_HEAR_DANGER,
  1521. COND_HEAR_PHYSICS_DANGER,
  1522. COND_NEW_ENEMY,
  1523. COND_PROVOKED,
  1524. COND_SEE_ENEMY,
  1525. COND_SEE_FEAR,
  1526. COND_SMELL,
  1527. };
  1528. static int g_DamageConditions[] =
  1529. {
  1530. COND_HEAVY_DAMAGE,
  1531. COND_LIGHT_DAMAGE,
  1532. COND_RECEIVED_ORDERS,
  1533. };
  1534. ClearIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
  1535. ClearIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );
  1536. if ( interrupt > GENERAL_INTERRUPTABILITY )
  1537. SetIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
  1538. if ( interrupt > DAMAGEORDEATH_INTERRUPTABILITY )
  1539. SetIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );
  1540. }
  1541. //-----------------------------------------------------------------------------
  1542. // Returns whether we currently have any interrupt conditions that would
  1543. // interrupt the given schedule.
  1544. //-----------------------------------------------------------------------------
  1545. bool CAI_BaseNPC::HasConditionsToInterruptSchedule( int nLocalScheduleID )
  1546. {
  1547. CAI_Schedule *pSchedule = GetSchedule( nLocalScheduleID );
  1548. if ( !pSchedule )
  1549. return false;
  1550. CAI_ScheduleBits bitsMask;
  1551. pSchedule->GetInterruptMask( &bitsMask );
  1552. CAI_ScheduleBits bitsOut;
  1553. AccessConditionBits().And( bitsMask, &bitsOut );
  1554. return !bitsOut.IsAllClear();
  1555. }
  1556. //-----------------------------------------------------------------------------
  1557. //-----------------------------------------------------------------------------
  1558. bool CAI_BaseNPC::IsCustomInterruptConditionSet( int nCondition )
  1559. {
  1560. int interrupt = InterruptFromCondition( nCondition );
  1561. if ( interrupt == -1 )
  1562. {
  1563. Assert(0);
  1564. return false;
  1565. }
  1566. return m_CustomInterruptConditions.IsBitSet( interrupt );
  1567. }
  1568. //-----------------------------------------------------------------------------
  1569. // Purpose: Sets a flag in the custom interrupt flags, translating the condition
  1570. // to the proper global space, if necessary
  1571. //-----------------------------------------------------------------------------
  1572. void CAI_BaseNPC::SetCustomInterruptCondition( int nCondition )
  1573. {
  1574. int interrupt = InterruptFromCondition( nCondition );
  1575. if ( interrupt == -1 )
  1576. {
  1577. Assert(0);
  1578. return;
  1579. }
  1580. m_CustomInterruptConditions.Set( interrupt );
  1581. }
  1582. //-----------------------------------------------------------------------------
  1583. // Purpose: Clears a flag in the custom interrupt flags, translating the condition
  1584. // to the proper global space, if necessary
  1585. //-----------------------------------------------------------------------------
  1586. void CAI_BaseNPC::ClearCustomInterruptCondition( int nCondition )
  1587. {
  1588. int interrupt = InterruptFromCondition( nCondition );
  1589. if ( interrupt == -1 )
  1590. {
  1591. Assert(0);
  1592. return;
  1593. }
  1594. m_CustomInterruptConditions.Clear( interrupt );
  1595. }
  1596. //-----------------------------------------------------------------------------
  1597. // Purpose: Clears all the custom interrupt flags.
  1598. //-----------------------------------------------------------------------------
  1599. void CAI_BaseNPC::ClearCustomInterruptConditions()
  1600. {
  1601. m_CustomInterruptConditions.ClearAll();
  1602. }
  1603. //-----------------------------------------------------------------------------
  1604. void CAI_BaseNPC::SetDistLook( float flDistLook )
  1605. {
  1606. m_pSenses->SetDistLook( flDistLook );
  1607. }
  1608. //-----------------------------------------------------------------------------
  1609. bool CAI_BaseNPC::QueryHearSound( CSound *pSound )
  1610. {
  1611. if ( pSound->SoundContext() & SOUND_CONTEXT_COMBINE_ONLY )
  1612. return false;
  1613. if ( pSound->SoundContext() & SOUND_CONTEXT_ALLIES_ONLY )
  1614. {
  1615. if ( !IsPlayerAlly() )
  1616. return false;
  1617. }
  1618. if ( pSound->IsSoundType( SOUND_PLAYER ) && GetState() == NPC_STATE_IDLE && !FVisible( pSound->GetSoundReactOrigin() ) )
  1619. {
  1620. // NPC's that are IDLE should disregard player movement sounds if they can't see them.
  1621. // This does not affect them hearing the player's weapon.
  1622. // !!!BUGBUG - this probably makes NPC's not hear doors opening, because doors opening put SOUND_PLAYER
  1623. // in the world, but the door's model will block the FVisible() trace and this code will then
  1624. // deduce that the sound can not be heard
  1625. return false;
  1626. }
  1627. // Disregard footsteps from our own class type
  1628. if ( pSound->IsSoundType( SOUND_COMBAT ) && pSound->SoundChannel() == SOUNDENT_CHANNEL_NPC_FOOTSTEP )
  1629. {
  1630. if ( pSound->m_hOwner && pSound->m_hOwner->ClassMatches( m_iClassname ) )
  1631. return false;
  1632. }
  1633. if( ShouldIgnoreSound( pSound ) )
  1634. return false;
  1635. return true;
  1636. }
  1637. //-----------------------------------------------------------------------------
  1638. bool CAI_BaseNPC::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
  1639. {
  1640. if ( bOnlyHateOrFearIfNPC && pEntity->IsNPC() )
  1641. {
  1642. Disposition_t disposition = IRelationType( pEntity );
  1643. return ( disposition == D_HT || disposition == D_FR );
  1644. }
  1645. return true;
  1646. }
  1647. //-----------------------------------------------------------------------------
  1648. void CAI_BaseNPC::OnLooked( int iDistance )
  1649. {
  1650. // DON'T let visibility information from last frame sit around!
  1651. static int conditionsToClear[] =
  1652. {
  1653. COND_SEE_HATE,
  1654. COND_SEE_DISLIKE,
  1655. COND_SEE_ENEMY,
  1656. COND_SEE_FEAR,
  1657. COND_SEE_NEMESIS,
  1658. COND_SEE_PLAYER,
  1659. COND_LOST_PLAYER,
  1660. COND_ENEMY_WENT_NULL,
  1661. };
  1662. bool bHadSeePlayer = HasCondition(COND_SEE_PLAYER);
  1663. ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
  1664. AISightIter_t iter;
  1665. CBaseEntity *pSightEnt;
  1666. pSightEnt = GetSenses()->GetFirstSeenEntity( &iter );
  1667. while( pSightEnt )
  1668. {
  1669. if ( pSightEnt->IsPlayer() )
  1670. {
  1671. // if we see a client, remember that (mostly for scripted AI)
  1672. SetCondition(COND_SEE_PLAYER);
  1673. m_flLastSawPlayerTime = gpGlobals->curtime;
  1674. }
  1675. Disposition_t relation = IRelationType( pSightEnt );
  1676. // the looker will want to consider this entity
  1677. // don't check anything else about an entity that can't be seen, or an entity that you don't care about.
  1678. if ( relation != D_NU )
  1679. {
  1680. if ( pSightEnt == GetEnemy() )
  1681. {
  1682. // we know this ent is visible, so if it also happens to be our enemy, store that now.
  1683. SetCondition(COND_SEE_ENEMY);
  1684. }
  1685. // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when
  1686. // we see npcs other than the Enemy.
  1687. switch ( relation )
  1688. {
  1689. case D_HT:
  1690. {
  1691. int priority = IRelationPriority( pSightEnt );
  1692. if (priority < 0)
  1693. {
  1694. SetCondition(COND_SEE_DISLIKE);
  1695. }
  1696. else if (priority > 10)
  1697. {
  1698. SetCondition(COND_SEE_NEMESIS);
  1699. }
  1700. else
  1701. {
  1702. SetCondition(COND_SEE_HATE);
  1703. }
  1704. UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin());
  1705. break;
  1706. }
  1707. case D_FR:
  1708. UpdateEnemyMemory(pSightEnt,pSightEnt->GetAbsOrigin());
  1709. SetCondition(COND_SEE_FEAR);
  1710. break;
  1711. case D_LI:
  1712. case D_NU:
  1713. break;
  1714. default:
  1715. DevWarning( 2, "%s can't assess %s\n", GetClassname(), pSightEnt->GetClassname() );
  1716. break;
  1717. }
  1718. }
  1719. pSightEnt = GetSenses()->GetNextSeenEntity( &iter );
  1720. }
  1721. // Did we lose the player?
  1722. if ( bHadSeePlayer && !HasCondition(COND_SEE_PLAYER) )
  1723. {
  1724. SetCondition(COND_LOST_PLAYER);
  1725. }
  1726. }
  1727. //-----------------------------------------------------------------------------
  1728. void CAI_BaseNPC::OnListened()
  1729. {
  1730. AISoundIter_t iter;
  1731. CSound *pCurrentSound;
  1732. static int conditionsToClear[] =
  1733. {
  1734. COND_HEAR_DANGER,
  1735. COND_HEAR_COMBAT,
  1736. COND_HEAR_WORLD,
  1737. COND_HEAR_PLAYER,
  1738. COND_HEAR_THUMPER,
  1739. COND_HEAR_BUGBAIT,
  1740. COND_HEAR_PHYSICS_DANGER,
  1741. COND_HEAR_BULLET_IMPACT,
  1742. COND_HEAR_MOVE_AWAY,
  1743. COND_NO_HEAR_DANGER,
  1744. COND_SMELL,
  1745. };
  1746. ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
  1747. pCurrentSound = GetSenses()->GetFirstHeardSound( &iter );
  1748. while ( pCurrentSound )
  1749. {
  1750. // the npc cares about this sound, and it's close enough to hear.
  1751. int condition = COND_NONE;
  1752. if ( pCurrentSound->FIsSound() )
  1753. {
  1754. // this is an audible sound.
  1755. switch( pCurrentSound->SoundTypeNoContext() )
  1756. {
  1757. case SOUND_DANGER:
  1758. if( gpGlobals->curtime > m_flIgnoreDangerSoundsUntil)
  1759. condition = COND_HEAR_DANGER;
  1760. break;
  1761. case SOUND_THUMPER: condition = COND_HEAR_THUMPER; break;
  1762. case SOUND_BUGBAIT: condition = COND_HEAR_BUGBAIT; break;
  1763. case SOUND_COMBAT:
  1764. if ( pCurrentSound->SoundChannel() == SOUNDENT_CHANNEL_SPOOKY_NOISE )
  1765. {
  1766. condition = COND_HEAR_SPOOKY;
  1767. }
  1768. else
  1769. {
  1770. condition = COND_HEAR_COMBAT;
  1771. }
  1772. break;
  1773. case SOUND_WORLD: condition = COND_HEAR_WORLD; break;
  1774. case SOUND_PLAYER: condition = COND_HEAR_PLAYER; break;
  1775. case SOUND_BULLET_IMPACT: condition = COND_HEAR_BULLET_IMPACT; break;
  1776. case SOUND_PHYSICS_DANGER: condition = COND_HEAR_PHYSICS_DANGER; break;
  1777. case SOUND_DANGER_SNIPERONLY:/* silence warning */ break;
  1778. case SOUND_MOVE_AWAY: condition = COND_HEAR_MOVE_AWAY; break;
  1779. case SOUND_PLAYER_VEHICLE: condition = COND_HEAR_PLAYER; break;
  1780. default:
  1781. DevMsg( "**ERROR: NPC %s hearing sound of unknown type %d!\n", GetClassname(), pCurrentSound->SoundType() );
  1782. break;
  1783. }
  1784. }
  1785. else
  1786. {
  1787. // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent
  1788. condition = COND_SMELL;
  1789. }
  1790. if ( condition != COND_NONE )
  1791. {
  1792. SetCondition( condition );
  1793. }
  1794. pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
  1795. }
  1796. if( !HasCondition( COND_HEAR_DANGER ) )
  1797. {
  1798. SetCondition( COND_NO_HEAR_DANGER );
  1799. }
  1800. // Sound outputs
  1801. if ( HasCondition( COND_HEAR_WORLD ) )
  1802. {
  1803. m_OnHearWorld.FireOutput(this, this);
  1804. }
  1805. if ( HasCondition( COND_HEAR_PLAYER ) )
  1806. {
  1807. m_OnHearPlayer.FireOutput(this, this);
  1808. }
  1809. if ( HasCondition( COND_HEAR_COMBAT ) ||
  1810. HasCondition( COND_HEAR_BULLET_IMPACT ) ||
  1811. HasCondition( COND_HEAR_DANGER ) )
  1812. {
  1813. m_OnHearCombat.FireOutput(this, this);
  1814. }
  1815. }
  1816. //=========================================================
  1817. // FValidateHintType - tells use whether or not the npc cares
  1818. // about the type of Hint Node given
  1819. //=========================================================
  1820. bool CAI_BaseNPC::FValidateHintType ( CAI_Hint *pHint )
  1821. {
  1822. return false;
  1823. }
  1824. //-----------------------------------------------------------------------------
  1825. // Purpose: Override in subclasses to associate specific hint types
  1826. // with activities
  1827. //-----------------------------------------------------------------------------
  1828. Activity CAI_BaseNPC::GetHintActivity( short sHintType, Activity HintsActivity )
  1829. {
  1830. if ( HintsActivity != ACT_INVALID )
  1831. return HintsActivity;
  1832. return ACT_IDLE;
  1833. }
  1834. //-----------------------------------------------------------------------------
  1835. // Purpose: Override in subclasses to give specific hint types delays
  1836. // before they can be used again
  1837. // Input :
  1838. // Output :
  1839. //-----------------------------------------------------------------------------
  1840. float CAI_BaseNPC::GetHintDelay( short sHintType )
  1841. {
  1842. return 0;
  1843. }
  1844. //-----------------------------------------------------------------------------
  1845. // Purpose: Return incoming grenade if spotted
  1846. // Input :
  1847. // Output :
  1848. //-----------------------------------------------------------------------------
  1849. CBaseGrenade* CAI_BaseNPC::IncomingGrenade(void)
  1850. {
  1851. int iDist;
  1852. AIEnemiesIter_t iter;
  1853. for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  1854. {
  1855. CBaseGrenade* pBG = dynamic_cast<CBaseGrenade*>((CBaseEntity*)pEMemory->hEnemy);
  1856. // Make sure this memory is for a grenade and grenade is not dead
  1857. if (!pBG || pBG->m_lifeState == LIFE_DEAD)
  1858. continue;
  1859. // Make sure it's visible
  1860. if (!FVisible(pBG))
  1861. continue;
  1862. // Check if it's near me
  1863. iDist = ( pBG->GetAbsOrigin() - GetAbsOrigin() ).Length();
  1864. if ( iDist <= NPC_GRENADE_FEAR_DIST )
  1865. return pBG;
  1866. // Check if it's headed towards me
  1867. Vector vGrenadeDir = GetAbsOrigin() - pBG->GetAbsOrigin();
  1868. Vector vGrenadeVel;
  1869. pBG->GetVelocity( &vGrenadeVel, NULL );
  1870. VectorNormalize(vGrenadeDir);
  1871. VectorNormalize(vGrenadeVel);
  1872. float flDotPr = DotProduct(vGrenadeDir, vGrenadeVel);
  1873. if (flDotPr > 0.85)
  1874. return pBG;
  1875. }
  1876. return NULL;
  1877. }
  1878. //-----------------------------------------------------------------------------
  1879. // Purpose: Check my physical state with the environment
  1880. // Input :
  1881. // Output :
  1882. //-----------------------------------------------------------------------------
  1883. void CAI_BaseNPC::TryRestoreHull(void)
  1884. {
  1885. if ( IsUsingSmallHull() && GetCurSchedule() )
  1886. {
  1887. trace_t tr;
  1888. Vector vUpBit = GetAbsOrigin();
  1889. vUpBit.z += 1;
  1890. AI_TraceHull( GetAbsOrigin(), vUpBit, GetHullMins(),
  1891. GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  1892. if ( !tr.startsolid && (tr.fraction == 1.0) )
  1893. {
  1894. SetHullSizeNormal();
  1895. }
  1896. }
  1897. }
  1898. //=========================================================
  1899. //=========================================================
  1900. int CAI_BaseNPC::GetSoundInterests( void )
  1901. {
  1902. return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_PLAYER_VEHICLE |
  1903. SOUND_BULLET_IMPACT;
  1904. }
  1905. //---------------------------------------------------------
  1906. //---------------------------------------------------------
  1907. int CAI_BaseNPC::GetSoundPriority( CSound *pSound )
  1908. {
  1909. int iSoundTypeNoContext = pSound->SoundTypeNoContext();
  1910. int iSoundContext = pSound->SoundContext();
  1911. if( iSoundTypeNoContext & SOUND_DANGER )
  1912. {
  1913. return SOUND_PRIORITY_HIGHEST;
  1914. }
  1915. if( iSoundTypeNoContext & SOUND_COMBAT )
  1916. {
  1917. if( iSoundContext & SOUND_CONTEXT_EXPLOSION )
  1918. {
  1919. return SOUND_PRIORITY_VERY_HIGH;
  1920. }
  1921. return SOUND_PRIORITY_HIGH;
  1922. }
  1923. return SOUND_PRIORITY_NORMAL;
  1924. }
  1925. //---------------------------------------------------------
  1926. // Return the loudest sound of this type in the sound list
  1927. //---------------------------------------------------------
  1928. CSound *CAI_BaseNPC::GetLoudestSoundOfType( int iType )
  1929. {
  1930. return CSoundEnt::GetLoudestSoundOfType( iType, EarPosition() );
  1931. }
  1932. //=========================================================
  1933. // GetBestSound - returns a pointer to the sound the npc
  1934. // should react to. Right now responds only to nearest sound.
  1935. //=========================================================
  1936. CSound* CAI_BaseNPC::GetBestSound( int validTypes )
  1937. {
  1938. if ( m_pLockedBestSound->m_iType != SOUND_NONE )
  1939. return m_pLockedBestSound;
  1940. CSound *pResult = GetSenses()->GetClosestSound( false, validTypes );
  1941. if ( pResult == NULL)
  1942. DevMsg( "Warning: NULL Return from GetBestSound\n" ); // condition previously set now no longer true. Have seen this when play too many sounds...
  1943. return pResult;
  1944. }
  1945. //=========================================================
  1946. // PBestScent - returns a pointer to the scent the npc
  1947. // should react to. Right now responds only to nearest scent
  1948. //=========================================================
  1949. CSound* CAI_BaseNPC::GetBestScent( void )
  1950. {
  1951. CSound *pResult = GetSenses()->GetClosestSound( true );
  1952. if ( pResult == NULL)
  1953. DevMsg("Warning: NULL Return from GetBestScent\n" );
  1954. return pResult;
  1955. }
  1956. //-----------------------------------------------------------------------------
  1957. void CAI_BaseNPC::LockBestSound()
  1958. {
  1959. UnlockBestSound();
  1960. CSound *pBestSound = GetBestSound();
  1961. if ( pBestSound )
  1962. *m_pLockedBestSound = *pBestSound;
  1963. }
  1964. //-----------------------------------------------------------------------------
  1965. void CAI_BaseNPC::UnlockBestSound()
  1966. {
  1967. if ( m_pLockedBestSound->m_iType != SOUND_NONE )
  1968. {
  1969. m_pLockedBestSound->m_iType = SOUND_NONE;
  1970. OnListened(); // reset hearing conditions
  1971. }
  1972. }
  1973. //-----------------------------------------------------------------------------
  1974. // Purpose: Return true if the specified sound is visible. Handles sounds generated by entities correctly.
  1975. // Input : *pSound -
  1976. //-----------------------------------------------------------------------------
  1977. bool CAI_BaseNPC::SoundIsVisible( CSound *pSound )
  1978. {
  1979. CBaseEntity *pBlocker = NULL;
  1980. if ( !FVisible( pSound->GetSoundReactOrigin(), MASK_BLOCKLOS, &pBlocker ) )
  1981. {
  1982. // Is the blocker the sound owner?
  1983. if ( pBlocker && pBlocker == pSound->m_hOwner )
  1984. return true;
  1985. return false;
  1986. }
  1987. return true;
  1988. }
  1989. //-----------------------------------------------------------------------------
  1990. // Purpose: Returns true if target is in legal range of eye movements
  1991. // Input :
  1992. // Output :
  1993. //-----------------------------------------------------------------------------
  1994. bool CAI_BaseNPC::ValidEyeTarget(const Vector &lookTargetPos)
  1995. {
  1996. Vector vHeadDir = HeadDirection3D( );
  1997. Vector lookTargetDir = lookTargetPos - EyePosition();
  1998. VectorNormalize(lookTargetDir);
  1999. // Only look if it doesn't crank my eyeballs too far
  2000. float dotPr = DotProduct(lookTargetDir, vHeadDir);
  2001. if (dotPr > 0.7)
  2002. {
  2003. return true;
  2004. }
  2005. return false;
  2006. }
  2007. //-----------------------------------------------------------------------------
  2008. // Purpose: Integrate head turn over time
  2009. // Input :
  2010. // Output :
  2011. //-----------------------------------------------------------------------------
  2012. void CAI_BaseNPC::SetHeadDirection( const Vector &vTargetPos, float flInterval)
  2013. {
  2014. if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD))
  2015. return;
  2016. #ifdef DEBUG_LOOK
  2017. // Draw line in body, head, and eye directions
  2018. Vector vEyePos = EyePosition();
  2019. Vector vHeadDir;
  2020. HeadDirection3D(&vHeadDir);
  2021. Vector vBodyDir;
  2022. BodyDirection2D(&vBodyDir);
  2023. //UNDONE <<TODO>>
  2024. // currently eye dir just returns head dir, so use vTargetPos for now
  2025. //Vector vEyeDir; w
  2026. //EyeDirection3D(&vEyeDir);
  2027. NDebugOverlay::Line( vEyePos, vEyePos+(50*vHeadDir), 255, 0, 0, false, 0.1 );
  2028. NDebugOverlay::Line( vEyePos, vEyePos+(50*vBodyDir), 0, 255, 0, false, 0.1 );
  2029. NDebugOverlay::Line( vEyePos, vTargetPos, 0, 0, 255, false, 0.1 );
  2030. #endif
  2031. //--------------------------------------
  2032. // Set head yaw
  2033. //--------------------------------------
  2034. float flDesiredYaw = VecToYaw(vTargetPos - GetLocalOrigin()) - GetLocalAngles().y;
  2035. if (flDesiredYaw > 180)
  2036. flDesiredYaw -= 360;
  2037. if (flDesiredYaw < -180)
  2038. flDesiredYaw += 360;
  2039. float iRate = 0.8;
  2040. // Make frame rate independent
  2041. float timeToUse = flInterval;
  2042. while (timeToUse > 0)
  2043. {
  2044. m_flHeadYaw = (iRate * m_flHeadYaw) + (1-iRate)*flDesiredYaw;
  2045. timeToUse -= 0.1;
  2046. }
  2047. if (m_flHeadYaw > 360) m_flHeadYaw = 0;
  2048. m_flHeadYaw = SetBoneController( 0, m_flHeadYaw );
  2049. //--------------------------------------
  2050. // Set head pitch
  2051. //--------------------------------------
  2052. Vector vEyePosition = EyePosition();
  2053. float fTargetDist = (vTargetPos - vEyePosition).Length();
  2054. float fVertDist = vTargetPos.z - vEyePosition.z;
  2055. float flDesiredPitch = -RAD2DEG(atan(fVertDist/fTargetDist));
  2056. // Make frame rate independent
  2057. timeToUse = flInterval;
  2058. while (timeToUse > 0)
  2059. {
  2060. m_flHeadPitch = (iRate * m_flHeadPitch) + (1-iRate)*flDesiredPitch;
  2061. timeToUse -= 0.1;
  2062. }
  2063. if (m_flHeadPitch > 360) m_flHeadPitch = 0;
  2064. SetBoneController( 1, m_flHeadPitch );
  2065. }
  2066. //------------------------------------------------------------------------------
  2067. // Purpose : Calculate the NPC's eye direction in 2D world space
  2068. // Input :
  2069. // Output :
  2070. //------------------------------------------------------------------------------
  2071. Vector CAI_BaseNPC::EyeDirection2D( void )
  2072. {
  2073. // UNDONE
  2074. // For now just return head direction....
  2075. return HeadDirection2D( );
  2076. }
  2077. //------------------------------------------------------------------------------
  2078. // Purpose : Calculate the NPC's eye direction in 2D world space
  2079. // Input :
  2080. // Output :
  2081. //------------------------------------------------------------------------------
  2082. Vector CAI_BaseNPC::EyeDirection3D( void )
  2083. {
  2084. // UNDONE //<<TODO>>
  2085. // For now just return head direction....
  2086. return HeadDirection3D( );
  2087. }
  2088. //------------------------------------------------------------------------------
  2089. // Purpose : Calculate the NPC's head direction in 2D world space
  2090. // Input :
  2091. // Output :
  2092. //------------------------------------------------------------------------------
  2093. Vector CAI_BaseNPC::HeadDirection2D( void )
  2094. {
  2095. // UNDONE <<TODO>>
  2096. // This does not account for head rotations in the animation,
  2097. // only those done via bone controllers
  2098. // Head yaw is in local cooridnate so it must be added to the body's yaw
  2099. QAngle bodyAngles = BodyAngles();
  2100. float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y;
  2101. // Convert head yaw into facing direction
  2102. return UTIL_YawToVector( flWorldHeadYaw );
  2103. }
  2104. //------------------------------------------------------------------------------
  2105. // Purpose : Calculate the NPC's head direction in 3D world space
  2106. // Input :
  2107. // Output :
  2108. //------------------------------------------------------------------------------
  2109. Vector CAI_BaseNPC::HeadDirection3D( void )
  2110. {
  2111. Vector vHeadDirection;
  2112. // UNDONE <<TODO>>
  2113. // This does not account for head rotations in the animation,
  2114. // only those done via bone controllers
  2115. // Head yaw is in local cooridnate so it must be added to the body's yaw
  2116. QAngle bodyAngles = BodyAngles();
  2117. float flWorldHeadYaw = m_flHeadYaw + bodyAngles.y;
  2118. // Convert head yaw into facing direction
  2119. AngleVectors( QAngle( m_flHeadPitch, flWorldHeadYaw, 0), &vHeadDirection );
  2120. return vHeadDirection;
  2121. }
  2122. //-----------------------------------------------------------------------------
  2123. // Purpose: Look at other NPCs and clients from time to time
  2124. // Input :
  2125. // Output :
  2126. //-----------------------------------------------------------------------------
  2127. CBaseEntity *CAI_BaseNPC::EyeLookTarget( void )
  2128. {
  2129. if (m_flNextEyeLookTime < gpGlobals->curtime)
  2130. {
  2131. CBaseEntity* pBestEntity = NULL;
  2132. float fBestDist = MAX_COORD_RANGE;
  2133. float fTestDist;
  2134. CBaseEntity *pEntity = NULL;
  2135. for ( CEntitySphereQuery sphere( GetAbsOrigin(), 1024, 0 ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() )
  2136. {
  2137. if (pEntity == this)
  2138. {
  2139. continue;
  2140. }
  2141. CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
  2142. if (pNPC || (pEntity->GetFlags() & FL_CLIENT))
  2143. {
  2144. fTestDist = (GetAbsOrigin() - pEntity->EyePosition()).Length();
  2145. if (fTestDist < fBestDist)
  2146. {
  2147. if (ValidEyeTarget(pEntity->EyePosition()))
  2148. {
  2149. fBestDist = fTestDist;
  2150. pBestEntity = pEntity;
  2151. }
  2152. }
  2153. }
  2154. }
  2155. if (pBestEntity)
  2156. {
  2157. m_flNextEyeLookTime = gpGlobals->curtime + random->RandomInt(1,5);
  2158. m_hEyeLookTarget = pBestEntity;
  2159. }
  2160. }
  2161. return m_hEyeLookTarget;
  2162. }
  2163. //-----------------------------------------------------------------------------
  2164. // Purpose: Set direction that the NPC aiming their gun
  2165. // returns true is the passed Vector is in
  2166. // the caller's forward aim cone. The dot product is performed
  2167. // in 2d, making the view cone infinitely tall. By default, the
  2168. // callers aim cone is assumed to be very narrow
  2169. //-----------------------------------------------------------------------------
  2170. bool CAI_BaseNPC::FInAimCone( const Vector &vecSpot )
  2171. {
  2172. Vector los = ( vecSpot - GetAbsOrigin() );
  2173. // do this in 2D
  2174. los.z = 0;
  2175. VectorNormalize( los );
  2176. Vector facingDir = BodyDirection2D( );
  2177. float flDot = DotProduct( los, facingDir );
  2178. if (CapabilitiesGet() & bits_CAP_AIM_GUN)
  2179. {
  2180. // FIXME: query current animation for ranges
  2181. return ( flDot > DOT_30DEGREE );
  2182. }
  2183. if ( flDot > 0.994 )//!!!BUGBUG - magic number same as FacingIdeal(), what is this?
  2184. return true;
  2185. return false;
  2186. }
  2187. //-----------------------------------------------------------------------------
  2188. // Purpose: Cache whatever pose parameters we intend to use
  2189. //-----------------------------------------------------------------------------
  2190. void CAI_BaseNPC::PopulatePoseParameters( void )
  2191. {
  2192. m_poseAim_Pitch = LookupPoseParameter( "aim_pitch" );
  2193. m_poseAim_Yaw = LookupPoseParameter( "aim_yaw" );
  2194. m_poseMove_Yaw = LookupPoseParameter( "move_yaw" );
  2195. BaseClass::PopulatePoseParameters();
  2196. }
  2197. //-----------------------------------------------------------------------------
  2198. // Purpose: Set direction that the NPC aiming their gun
  2199. //-----------------------------------------------------------------------------
  2200. void CAI_BaseNPC::SetAim( const Vector &aimDir )
  2201. {
  2202. QAngle angDir;
  2203. VectorAngles( aimDir, angDir );
  2204. float curPitch = GetPoseParameter( m_poseAim_Pitch );
  2205. float curYaw = GetPoseParameter( m_poseAim_Yaw );
  2206. float newPitch;
  2207. float newYaw;
  2208. if( GetEnemy() )
  2209. {
  2210. // clamp and dampen movement
  2211. newPitch = curPitch + 0.8 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
  2212. float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
  2213. // float flNewTargetYaw = UTIL_ApproachAngle( flRelativeYaw, curYaw, 20 );
  2214. // float newYaw = curYaw + 0.8 * UTIL_AngleDiff( flNewTargetYaw, curYaw );
  2215. newYaw = curYaw + UTIL_AngleDiff( flRelativeYaw, curYaw );
  2216. }
  2217. else
  2218. {
  2219. // Sweep your weapon more slowly if you're not fighting someone
  2220. newPitch = curPitch + 0.6 * UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 20 ), curPitch );
  2221. float flRelativeYaw = UTIL_AngleDiff( angDir.y, GetAbsAngles().y );
  2222. newYaw = curYaw + 0.6 * UTIL_AngleDiff( flRelativeYaw, curYaw );
  2223. }
  2224. newPitch = AngleNormalize( newPitch );
  2225. newYaw = AngleNormalize( newYaw );
  2226. SetPoseParameter( m_poseAim_Pitch, newPitch );
  2227. SetPoseParameter( m_poseAim_Yaw, newYaw );
  2228. // Msg("yaw %.0f (%.0f %.0f)\n", newYaw, angDir.y, GetAbsAngles().y );
  2229. // Calculate our interaction yaw.
  2230. // If we've got a small adjustment off our abs yaw, use that.
  2231. // Otherwise, use our abs yaw.
  2232. if ( fabs(newYaw) < 20 )
  2233. {
  2234. m_flInteractionYaw = angDir.y;
  2235. }
  2236. else
  2237. {
  2238. m_flInteractionYaw = GetAbsAngles().y;
  2239. }
  2240. }
  2241. void CAI_BaseNPC::RelaxAim( )
  2242. {
  2243. float curPitch = GetPoseParameter( m_poseAim_Pitch );
  2244. float curYaw = GetPoseParameter( m_poseAim_Yaw );
  2245. // dampen existing aim
  2246. float newPitch = AngleNormalize( UTIL_ApproachAngle( 0, curPitch, 3 ) );
  2247. float newYaw = AngleNormalize( UTIL_ApproachAngle( 0, curYaw, 2 ) );
  2248. SetPoseParameter( m_poseAim_Pitch, newPitch );
  2249. SetPoseParameter( m_poseAim_Yaw, newYaw );
  2250. // DevMsg("relax aim %.0f %0.f\n", newPitch, newYaw );
  2251. }
  2252. //-----------------------------------------------------------------------------
  2253. void CAI_BaseNPC::AimGun()
  2254. {
  2255. if (GetEnemy())
  2256. {
  2257. Vector vecShootOrigin;
  2258. vecShootOrigin = Weapon_ShootPosition();
  2259. Vector vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
  2260. SetAim( vecShootDir );
  2261. }
  2262. else
  2263. {
  2264. RelaxAim( );
  2265. }
  2266. }
  2267. //-----------------------------------------------------------------------------
  2268. // Purpose: Set direction that the NPC is looking
  2269. // Input :
  2270. // Output :
  2271. //-----------------------------------------------------------------------------
  2272. void CAI_BaseNPC::MaintainLookTargets ( float flInterval )
  2273. {
  2274. // --------------------------------------------------------
  2275. // Try to look at enemy if I have one
  2276. // --------------------------------------------------------
  2277. if ((CBaseEntity*)GetEnemy())
  2278. {
  2279. if ( ValidEyeTarget(GetEnemy()->EyePosition()) )
  2280. {
  2281. SetHeadDirection(GetEnemy()->EyePosition(),flInterval);
  2282. SetViewtarget( GetEnemy()->EyePosition() );
  2283. return;
  2284. }
  2285. }
  2286. #if 0
  2287. // --------------------------------------------------------
  2288. // First check if I've been assigned to look at an entity
  2289. // --------------------------------------------------------
  2290. CBaseEntity *lookTarget = EyeLookTarget();
  2291. if (lookTarget && ValidEyeTarget(lookTarget->EyePosition()))
  2292. {
  2293. SetHeadDirection(lookTarget->EyePosition(),flInterval);
  2294. SetViewtarget( lookTarget->EyePosition() );
  2295. return;
  2296. }
  2297. #endif
  2298. // --------------------------------------------------------
  2299. // If I'm moving, look at my target position
  2300. // --------------------------------------------------------
  2301. if (GetNavigator()->IsGoalActive() && ValidEyeTarget(GetNavigator()->GetCurWaypointPos()))
  2302. {
  2303. SetHeadDirection(GetNavigator()->GetCurWaypointPos(),flInterval);
  2304. SetViewtarget( GetNavigator()->GetCurWaypointPos() );
  2305. return;
  2306. }
  2307. // -------------------------------------
  2308. // If I hear a combat sounds look there
  2309. // -------------------------------------
  2310. if ( HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER) )
  2311. {
  2312. CSound *pSound = GetBestSound();
  2313. if ( pSound && pSound->IsSoundType(SOUND_COMBAT | SOUND_DANGER) )
  2314. {
  2315. if (ValidEyeTarget( pSound->GetSoundOrigin() ))
  2316. {
  2317. SetHeadDirection(pSound->GetSoundOrigin(),flInterval);
  2318. SetViewtarget( pSound->GetSoundOrigin() );
  2319. return;
  2320. }
  2321. }
  2322. }
  2323. // -------------------------------------
  2324. // Otherwise just look around randomly
  2325. // -------------------------------------
  2326. // Check that look target position is still valid
  2327. if (m_flNextEyeLookTime > gpGlobals->curtime)
  2328. {
  2329. if (!ValidEyeTarget(m_vEyeLookTarget))
  2330. {
  2331. // Force choosing of new look target
  2332. m_flNextEyeLookTime = 0;
  2333. }
  2334. }
  2335. if (m_flNextEyeLookTime < gpGlobals->curtime)
  2336. {
  2337. Vector vBodyDir = BodyDirection2D( );
  2338. /*
  2339. Vector lookSpread = Vector(0.82,0.82,0.22);
  2340. float x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  2341. float y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  2342. float z = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  2343. QAngle angles;
  2344. VectorAngles( vBodyDir, angles );
  2345. Vector forward, right, up;
  2346. AngleVectors( angles, &forward, &right, &up );
  2347. Vector vecDir = vBodyDir + (x * lookSpread.x * forward) + (y * lookSpread.y * right) + (z * lookSpread.z * up);
  2348. float lookDist = random->RandomInt(50,2000);
  2349. m_vEyeLookTarget = EyePosition() + lookDist*vecDir;
  2350. */
  2351. m_vEyeLookTarget = EyePosition() + 500*vBodyDir;
  2352. m_flNextEyeLookTime = gpGlobals->curtime + 0.5; // random->RandomInt(1,5);
  2353. }
  2354. SetHeadDirection(m_vEyeLookTarget,flInterval);
  2355. // ----------------------------------------------------
  2356. // Integrate eye turn in frame rate independent manner
  2357. // ----------------------------------------------------
  2358. float timeToUse = flInterval;
  2359. while (timeToUse > 0)
  2360. {
  2361. m_vCurEyeTarget = ((1 - m_flEyeIntegRate) * m_vCurEyeTarget + m_flEyeIntegRate * m_vEyeLookTarget);
  2362. timeToUse -= 0.1;
  2363. }
  2364. SetViewtarget( m_vCurEyeTarget );
  2365. }
  2366. //-----------------------------------------------------------------------------
  2367. // Let the motor deal with turning presentation issues
  2368. //-----------------------------------------------------------------------------
  2369. void CAI_BaseNPC::MaintainTurnActivity( )
  2370. {
  2371. // Don't bother if we're in a vehicle
  2372. if ( IsInAVehicle() )
  2373. return;
  2374. AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainTurnActivity );
  2375. GetMotor()->MaintainTurnActivity( );
  2376. }
  2377. //-----------------------------------------------------------------------------
  2378. // Here's where all motion occurs
  2379. //-----------------------------------------------------------------------------
  2380. void CAI_BaseNPC::PerformMovement()
  2381. {
  2382. // don't bother to move if the npc isn't alive
  2383. if (!IsAlive())
  2384. return;
  2385. AI_PROFILE_SCOPE(CAI_BaseNPC_PerformMovement);
  2386. g_AIMoveTimer.Start();
  2387. float flInterval = ( m_flTimeLastMovement != FLT_MAX ) ? gpGlobals->curtime - m_flTimeLastMovement : 0.1;
  2388. m_pNavigator->Move( ROUND_TO_TICKS( flInterval ) );
  2389. m_flTimeLastMovement = gpGlobals->curtime;
  2390. g_AIMoveTimer.End();
  2391. }
  2392. //-----------------------------------------------------------------------------
  2393. // Updates to npc after movement is completed
  2394. //-----------------------------------------------------------------------------
  2395. void CAI_BaseNPC::PostMovement()
  2396. {
  2397. AI_PROFILE_SCOPE( CAI_BaseNPC_PostMovement );
  2398. InvalidateBoneCache();
  2399. if ( GetModelPtr() && GetModelPtr()->SequencesAvailable() )
  2400. {
  2401. float flInterval = GetAnimTimeInterval();
  2402. if ( CapabilitiesGet() & bits_CAP_AIM_GUN )
  2403. {
  2404. AI_PROFILE_SCOPE( CAI_BaseNPC_PM_AimGun );
  2405. AimGun();
  2406. }
  2407. else
  2408. {
  2409. // NPCs with bits_CAP_AIM_GUN update this in SetAim, called by AimGun.
  2410. m_flInteractionYaw = GetAbsAngles().y;
  2411. }
  2412. // set look targets for npcs with animated faces
  2413. if ( CapabilitiesGet() & bits_CAP_ANIMATEDFACE )
  2414. {
  2415. AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainLookTargets );
  2416. MaintainLookTargets(flInterval);
  2417. }
  2418. }
  2419. {
  2420. AI_PROFILE_SCOPE( CAI_BaseNPC_PM_MaintainTurnActivity );
  2421. // update turning as needed
  2422. MaintainTurnActivity( );
  2423. }
  2424. }
  2425. //-----------------------------------------------------------------------------
  2426. float g_AINextDisabledMessageTime;
  2427. extern bool IsInCommentaryMode( void );
  2428. bool CAI_BaseNPC::PreThink( void )
  2429. {
  2430. // ----------------------------------------------------------
  2431. // Skip AI if its been disabled or networks haven't been
  2432. // loaded, and put a warning message on the screen
  2433. //
  2434. // Don't do this if the convar wants it hidden
  2435. // ----------------------------------------------------------
  2436. if ( (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI || !g_pAINetworkManager->NetworksLoaded()) )
  2437. {
  2438. if ( gpGlobals->curtime >= g_AINextDisabledMessageTime && !IsInCommentaryMode() )
  2439. {
  2440. g_AINextDisabledMessageTime = gpGlobals->curtime + 0.5f;
  2441. hudtextparms_s tTextParam;
  2442. tTextParam.x = 0.7;
  2443. tTextParam.y = 0.65;
  2444. tTextParam.effect = 0;
  2445. tTextParam.r1 = 255;
  2446. tTextParam.g1 = 255;
  2447. tTextParam.b1 = 255;
  2448. tTextParam.a1 = 255;
  2449. tTextParam.r2 = 255;
  2450. tTextParam.g2 = 255;
  2451. tTextParam.b2 = 255;
  2452. tTextParam.a2 = 255;
  2453. tTextParam.fadeinTime = 0;
  2454. tTextParam.fadeoutTime = 0;
  2455. tTextParam.holdTime = 0.6;
  2456. tTextParam.fxTime = 0;
  2457. tTextParam.channel = 1;
  2458. UTIL_HudMessageAll( tTextParam, "A.I. Disabled...\n" );
  2459. }
  2460. SetActivity( ACT_IDLE );
  2461. return false;
  2462. }
  2463. // --------------------------------------------------------
  2464. // If debug stepping
  2465. // --------------------------------------------------------
  2466. if (CAI_BaseNPC::m_nDebugBits & bits_debugStepAI)
  2467. {
  2468. if (m_nDebugCurIndex >= CAI_BaseNPC::m_nDebugPauseIndex)
  2469. {
  2470. if (!GetNavigator()->IsGoalActive())
  2471. {
  2472. m_flPlaybackRate = 0;
  2473. }
  2474. return false;
  2475. }
  2476. else
  2477. {
  2478. m_flPlaybackRate = 1;
  2479. }
  2480. }
  2481. if ( m_hOpeningDoor.Get() && AIIsDebuggingDoors( this ) )
  2482. {
  2483. NDebugOverlay::Line( EyePosition(), m_hOpeningDoor->WorldSpaceCenter(), 255, 255, 255, false, .1 );
  2484. }
  2485. return true;
  2486. }
  2487. //-----------------------------------------------------------------------------
  2488. void CAI_BaseNPC::RunAnimation( void )
  2489. {
  2490. VPROF_BUDGET( "CAI_BaseNPC_RunAnimation", VPROF_BUDGETGROUP_SERVER_ANIM );
  2491. if ( !GetModelPtr() )
  2492. return;
  2493. float flInterval = GetAnimTimeInterval();
  2494. StudioFrameAdvance( ); // animate
  2495. if ((CAI_BaseNPC::m_nDebugBits & bits_debugStepAI) && !GetNavigator()->IsGoalActive())
  2496. {
  2497. flInterval = 0;
  2498. }
  2499. // start or end a fidget
  2500. // This needs a better home -- switching animations over time should be encapsulated on a per-activity basis
  2501. // perhaps MaintainActivity() or a ShiftAnimationOverTime() or something.
  2502. if ( m_NPCState != NPC_STATE_SCRIPT && m_NPCState != NPC_STATE_DEAD && m_Activity == ACT_IDLE && IsActivityFinished() )
  2503. {
  2504. int iSequence;
  2505. // FIXME: this doesn't reissue a translation, so if the idle activity translation changes over time, it'll never get reset
  2506. if ( SequenceLoops() )
  2507. {
  2508. // animation does loop, which means we're playing subtle idle. Might need to fidget.
  2509. iSequence = SelectWeightedSequence ( m_translatedActivity );
  2510. }
  2511. else
  2512. {
  2513. // animation that just ended doesn't loop! That means we just finished a fidget
  2514. // and should return to our heaviest weighted idle (the subtle one)
  2515. iSequence = SelectHeaviestSequence ( m_translatedActivity );
  2516. }
  2517. if ( iSequence != ACTIVITY_NOT_AVAILABLE )
  2518. {
  2519. ResetSequence( iSequence ); // Set to new anim (if it's there)
  2520. //Adrian: Basically everywhere else in the AI code this variable gets set to whatever our sequence is.
  2521. //But here it doesn't and not setting it causes any animation set through here to be stomped by the
  2522. //ideal sequence before it has a chance of playing out (since there's code that reselects the ideal
  2523. //sequence if it doesn't match the current one).
  2524. if ( hl2_episodic.GetBool() )
  2525. {
  2526. m_nIdealSequence = iSequence;
  2527. }
  2528. }
  2529. }
  2530. DispatchAnimEvents( this );
  2531. }
  2532. //-----------------------------------------------------------------------------
  2533. void CAI_BaseNPC::PostRun( void )
  2534. {
  2535. AI_PROFILE_SCOPE(CAI_BaseNPC_PostRun);
  2536. g_AIPostRunTimer.Start();
  2537. if ( !IsMoving() )
  2538. {
  2539. if ( GetIdealActivity() == ACT_WALK ||
  2540. GetIdealActivity() == ACT_RUN ||
  2541. GetIdealActivity() == ACT_WALK_AIM ||
  2542. GetIdealActivity() == ACT_RUN_AIM )
  2543. {
  2544. PostRunStopMoving();
  2545. }
  2546. }
  2547. RunAnimation();
  2548. // update slave items (weapons)
  2549. Weapon_FrameUpdate();
  2550. g_AIPostRunTimer.End();
  2551. }
  2552. //-----------------------------------------------------------------------------
  2553. void CAI_BaseNPC::PostRunStopMoving()
  2554. {
  2555. DbgNavMsg1( this, "NPC %s failed to stop properly, slamming activity\n", GetDebugName() );
  2556. if ( !GetNavigator()->SetGoalFromStoppingPath() )
  2557. SetIdealActivity( GetStoppedActivity() ); // This is to prevent running in place
  2558. }
  2559. //-----------------------------------------------------------------------------
  2560. bool CAI_BaseNPC::ShouldAlwaysThink()
  2561. {
  2562. // @TODO (toml 07-08-03): This needs to be beefed up.
  2563. // There are simple cases already seen where an AI can briefly leave
  2564. // the PVS while navigating to the player. Perhaps should incorporate a heuristic taking into
  2565. // account mode, enemy, last time saw player, player range etc. For example, if enemy is player,
  2566. // and player is within 100 feet, and saw the player within the past 15 seconds, keep running...
  2567. return HasSpawnFlags(SF_NPC_ALWAYSTHINK);
  2568. }
  2569. //-----------------------------------------------------------------------------
  2570. // Purpose: Return true if the Player should be running the auto-move-out-of-way
  2571. // avoidance code, which also means that the NPC shouldn't care about running into the Player.
  2572. //-----------------------------------------------------------------------------
  2573. bool CAI_BaseNPC::ShouldPlayerAvoid( void )
  2574. {
  2575. if ( GetState() == NPC_STATE_SCRIPT )
  2576. return true;
  2577. if ( IsInAScript() )
  2578. return true;
  2579. if ( IsInLockedScene() == true )
  2580. return true;
  2581. if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) )
  2582. return true;
  2583. return false;
  2584. }
  2585. //-----------------------------------------------------------------------------
  2586. void CAI_BaseNPC::UpdateEfficiency( bool bInPVS )
  2587. {
  2588. // Sleeping NPCs always dormant
  2589. if ( GetSleepState() != AISS_AWAKE )
  2590. {
  2591. SetEfficiency( AIE_DORMANT );
  2592. return;
  2593. }
  2594. m_bInChoreo = ( GetState() == NPC_STATE_SCRIPT || IsCurSchedule( SCHED_SCENE_GENERIC, false ) );
  2595. if ( !ShouldUseEfficiency() )
  2596. {
  2597. SetEfficiency( AIE_NORMAL );
  2598. SetMoveEfficiency( AIME_NORMAL );
  2599. return;
  2600. }
  2601. //---------------------------------
  2602. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  2603. static Vector vPlayerEyePosition;
  2604. static Vector vPlayerForward;
  2605. static int iPrevFrame = -1;
  2606. if ( gpGlobals->framecount != iPrevFrame )
  2607. {
  2608. iPrevFrame = gpGlobals->framecount;
  2609. if ( pPlayer )
  2610. {
  2611. pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL );
  2612. }
  2613. }
  2614. Vector vToNPC = GetAbsOrigin() - vPlayerEyePosition;
  2615. float playerDist = VectorNormalize( vToNPC );
  2616. bool bPlayerFacing;
  2617. bool bClientPVSExpanded = UTIL_ClientPVSIsExpanded();
  2618. if ( pPlayer )
  2619. {
  2620. bPlayerFacing = ( bClientPVSExpanded || ( bInPVS && vPlayerForward.Dot( vToNPC ) > 0 ) );
  2621. }
  2622. else
  2623. {
  2624. playerDist = 0;
  2625. bPlayerFacing = true;
  2626. }
  2627. //---------------------------------
  2628. bool bInVisibilityPVS = ( bClientPVSExpanded && UTIL_FindClientInVisibilityPVS( edict() ) != NULL );
  2629. //---------------------------------
  2630. if ( ( bInPVS && ( bPlayerFacing || playerDist < 25*12 ) ) || bClientPVSExpanded )
  2631. {
  2632. SetMoveEfficiency( AIME_NORMAL );
  2633. }
  2634. else
  2635. {
  2636. SetMoveEfficiency( AIME_EFFICIENT );
  2637. }
  2638. //---------------------------------
  2639. if ( !IsRetail() && ai_efficiency_override.GetInt() > AIE_NORMAL && ai_efficiency_override.GetInt() <= AIE_DORMANT )
  2640. {
  2641. SetEfficiency( (AI_Efficiency_t)ai_efficiency_override.GetInt() );
  2642. return;
  2643. }
  2644. //---------------------------------
  2645. // Some conditions will always force normal
  2646. if ( gpGlobals->curtime - GetLastAttackTime() < .15 )
  2647. {
  2648. SetEfficiency( AIE_NORMAL );
  2649. return;
  2650. }
  2651. bool bFramerateOk = ( gpGlobals->frametime < ai_frametime_limit.GetFloat() );
  2652. if ( m_bForceConditionsGather ||
  2653. gpGlobals->curtime - GetLastAttackTime() < .2 ||
  2654. gpGlobals->curtime - m_flLastDamageTime < .2 ||
  2655. ( GetState() < NPC_STATE_IDLE || GetState() > NPC_STATE_SCRIPT ) ||
  2656. ( ( bInPVS || bInVisibilityPVS ) &&
  2657. ( ( GetTask() && !TaskIsRunning() ) ||
  2658. GetTaskInterrupt() > 0 ||
  2659. m_bInChoreo ) ) )
  2660. {
  2661. SetEfficiency( ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT );
  2662. return;
  2663. }
  2664. AI_Efficiency_t minEfficiency;
  2665. if ( !ShouldDefaultEfficient() )
  2666. {
  2667. minEfficiency = ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT;
  2668. }
  2669. else
  2670. {
  2671. minEfficiency = ( bFramerateOk ) ? AIE_EFFICIENT : AIE_VERY_EFFICIENT;
  2672. }
  2673. // Stay normal if there's any chance of a relevant danger sound
  2674. bool bPotentialDanger = false;
  2675. if ( GetSoundInterests() & SOUND_DANGER )
  2676. {
  2677. int iSound = CSoundEnt::ActiveList();
  2678. while ( iSound != SOUNDLIST_EMPTY )
  2679. {
  2680. CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound );
  2681. float hearingSensitivity = HearingSensitivity();
  2682. Vector vEarPosition = EarPosition();
  2683. if ( pCurrentSound && (SOUND_DANGER & pCurrentSound->SoundType()) )
  2684. {
  2685. float flHearDistanceSq = pCurrentSound->Volume() * hearingSensitivity;
  2686. flHearDistanceSq *= flHearDistanceSq;
  2687. if ( pCurrentSound->GetSoundOrigin().DistToSqr( vEarPosition ) <= flHearDistanceSq )
  2688. {
  2689. bPotentialDanger = true;
  2690. break;
  2691. }
  2692. }
  2693. iSound = pCurrentSound->NextSound();
  2694. }
  2695. }
  2696. if ( bPotentialDanger )
  2697. {
  2698. SetEfficiency( minEfficiency );
  2699. return;
  2700. }
  2701. //---------------------------------
  2702. if ( !pPlayer )
  2703. {
  2704. // No heuristic currently for dedicated servers
  2705. SetEfficiency( minEfficiency );
  2706. return;
  2707. }
  2708. enum
  2709. {
  2710. DIST_NEAR,
  2711. DIST_MID,
  2712. DIST_FAR
  2713. };
  2714. int range;
  2715. if ( bInPVS )
  2716. {
  2717. if ( playerDist < 15*12 )
  2718. {
  2719. SetEfficiency( minEfficiency );
  2720. return;
  2721. }
  2722. range = ( playerDist < 50*12 ) ? DIST_NEAR :
  2723. ( playerDist < 200*12 ) ? DIST_MID : DIST_FAR;
  2724. }
  2725. else
  2726. {
  2727. range = ( playerDist < 25*12 ) ? DIST_NEAR :
  2728. ( playerDist < 100*12 ) ? DIST_MID : DIST_FAR;
  2729. }
  2730. // Efficiency mappings
  2731. int state = GetState();
  2732. if (state == NPC_STATE_SCRIPT ) // Treat script as alert. Already confirmed not in PVS
  2733. state = NPC_STATE_ALERT;
  2734. static AI_Efficiency_t mappings[] =
  2735. {
  2736. // Idle
  2737. // In PVS
  2738. // Facing
  2739. AIE_NORMAL,
  2740. AIE_EFFICIENT,
  2741. AIE_EFFICIENT,
  2742. // Not facing
  2743. AIE_EFFICIENT,
  2744. AIE_EFFICIENT,
  2745. AIE_VERY_EFFICIENT,
  2746. // Not in PVS
  2747. AIE_VERY_EFFICIENT,
  2748. AIE_SUPER_EFFICIENT,
  2749. AIE_SUPER_EFFICIENT,
  2750. // Alert
  2751. // In PVS
  2752. // Facing
  2753. AIE_NORMAL,
  2754. AIE_EFFICIENT,
  2755. AIE_EFFICIENT,
  2756. // Not facing
  2757. AIE_NORMAL,
  2758. AIE_EFFICIENT,
  2759. AIE_EFFICIENT,
  2760. // Not in PVS
  2761. AIE_EFFICIENT,
  2762. AIE_VERY_EFFICIENT,
  2763. AIE_SUPER_EFFICIENT,
  2764. // Combat
  2765. // In PVS
  2766. // Facing
  2767. AIE_NORMAL,
  2768. AIE_NORMAL,
  2769. AIE_EFFICIENT,
  2770. // Not facing
  2771. AIE_NORMAL,
  2772. AIE_EFFICIENT,
  2773. AIE_EFFICIENT,
  2774. // Not in PVS
  2775. AIE_NORMAL,
  2776. AIE_EFFICIENT,
  2777. AIE_VERY_EFFICIENT,
  2778. };
  2779. static const int stateBase[] = { 0, 9, 18 };
  2780. const int NOT_FACING_OFFSET = 3;
  2781. const int NO_PVS_OFFSET = 6;
  2782. int iStateOffset = stateBase[state - NPC_STATE_IDLE] ;
  2783. int iFacingOffset = (!bInPVS || bPlayerFacing) ? 0 : NOT_FACING_OFFSET;
  2784. int iPVSOffset = (bInPVS) ? 0 : NO_PVS_OFFSET;
  2785. int iMapping = iStateOffset + iPVSOffset + iFacingOffset + range;
  2786. Assert( iMapping < ARRAYSIZE( mappings ) );
  2787. AI_Efficiency_t efficiency = mappings[iMapping];
  2788. //---------------------------------
  2789. AI_Efficiency_t maxEfficiency = AIE_SUPER_EFFICIENT;
  2790. if ( bInVisibilityPVS && state >= NPC_STATE_ALERT )
  2791. {
  2792. maxEfficiency = AIE_EFFICIENT;
  2793. }
  2794. else if ( bInVisibilityPVS || HasCondition( COND_SEE_PLAYER ) )
  2795. {
  2796. maxEfficiency = AIE_VERY_EFFICIENT;
  2797. }
  2798. //---------------------------------
  2799. SetEfficiency( clamp( efficiency, minEfficiency, maxEfficiency ) );
  2800. }
  2801. //-----------------------------------------------------------------------------
  2802. void CAI_BaseNPC::UpdateSleepState( bool bInPVS )
  2803. {
  2804. if ( GetSleepState() > AISS_AWAKE )
  2805. {
  2806. CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
  2807. if ( !pLocalPlayer )
  2808. {
  2809. if ( gpGlobals->maxClients > 1 )
  2810. {
  2811. Wake();
  2812. }
  2813. else
  2814. {
  2815. Warning( "CAI_BaseNPC::UpdateSleepState called with NULL pLocalPlayer\n" );
  2816. }
  2817. return;
  2818. }
  2819. if ( m_flWakeRadius > .1 && !(pLocalPlayer->GetFlags() & FL_NOTARGET) && ( pLocalPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() <= Square(m_flWakeRadius) )
  2820. Wake();
  2821. else if ( GetSleepState() == AISS_WAITING_FOR_PVS )
  2822. {
  2823. if ( bInPVS )
  2824. Wake();
  2825. }
  2826. else if ( GetSleepState() == AISS_WAITING_FOR_THREAT )
  2827. {
  2828. if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
  2829. Wake();
  2830. else
  2831. {
  2832. if ( bInPVS )
  2833. {
  2834. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  2835. {
  2836. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  2837. if ( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) && pPlayer->FVisible( this ) )
  2838. Wake();
  2839. }
  2840. }
  2841. // Should check for visible danger sounds
  2842. if ( (GetSoundInterests() & SOUND_DANGER) && !(HasSpawnFlags(SF_NPC_WAIT_TILL_SEEN)) )
  2843. {
  2844. int iSound = CSoundEnt::ActiveList();
  2845. while ( iSound != SOUNDLIST_EMPTY )
  2846. {
  2847. CSound *pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound );
  2848. Assert( pCurrentSound );
  2849. if ( (pCurrentSound->SoundType() & SOUND_DANGER) &&
  2850. GetSenses()->CanHearSound( pCurrentSound ) &&
  2851. SoundIsVisible( pCurrentSound ))
  2852. {
  2853. Wake();
  2854. break;
  2855. }
  2856. iSound = pCurrentSound->NextSound();
  2857. }
  2858. }
  2859. }
  2860. }
  2861. }
  2862. else
  2863. {
  2864. // NPC is awake
  2865. // Don't let an NPC sleep if they're running a script!
  2866. if( !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT )
  2867. {
  2868. if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS) )
  2869. {
  2870. if( !HasCondition(COND_IN_PVS) )
  2871. {
  2872. SetSleepState( AISS_WAITING_FOR_PVS );
  2873. Sleep();
  2874. }
  2875. }
  2876. if( HasSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS) )
  2877. {
  2878. if( HasCondition(COND_IN_PVS) )
  2879. {
  2880. // OK, we're in the player's PVS. Now switch to regular old AUTO_PVS sleep rules.
  2881. AddSleepFlags(AI_SLEEP_FLAG_AUTO_PVS);
  2882. RemoveSleepFlags(AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS);
  2883. }
  2884. }
  2885. }
  2886. }
  2887. }
  2888. //-----------------------------------------------------------------------------
  2889. struct AIRebalanceInfo_t
  2890. {
  2891. CAI_BaseNPC * pNPC;
  2892. int iNextThinkTick;
  2893. bool bInPVS;
  2894. float dotPlayer;
  2895. float distPlayer;
  2896. };
  2897. int __cdecl ThinkRebalanceCompare( const AIRebalanceInfo_t *pLeft, const AIRebalanceInfo_t *pRight )
  2898. {
  2899. int base = pLeft->iNextThinkTick - pRight->iNextThinkTick;
  2900. if ( base != 0 )
  2901. return base;
  2902. if ( !pLeft->bInPVS && !pRight->bInPVS )
  2903. return 0;
  2904. if ( !pLeft->bInPVS )
  2905. return 1;
  2906. if ( !pRight->bInPVS )
  2907. return -1;
  2908. if ( pLeft->dotPlayer < 0 && pRight->dotPlayer < 0 )
  2909. return 0;
  2910. if ( pLeft->dotPlayer < 0 )
  2911. return 1;
  2912. if ( pRight->dotPlayer < 0 )
  2913. return -1;
  2914. const float NEAR_PLAYER = 50*12;
  2915. if ( pLeft->distPlayer < NEAR_PLAYER && pRight->distPlayer >= NEAR_PLAYER )
  2916. return -1;
  2917. if ( pRight->distPlayer < NEAR_PLAYER && pLeft->distPlayer >= NEAR_PLAYER )
  2918. return 1;
  2919. if ( pLeft->dotPlayer > pRight->dotPlayer )
  2920. return -1;
  2921. if ( pLeft->dotPlayer < pRight->dotPlayer )
  2922. return 1;
  2923. return 0;
  2924. }
  2925. inline bool CAI_BaseNPC::CanThinkRebalance()
  2926. {
  2927. if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink )
  2928. {
  2929. return false;
  2930. }
  2931. if ( m_bInChoreo )
  2932. {
  2933. return false;
  2934. }
  2935. if ( m_NPCState == NPC_STATE_DEAD )
  2936. {
  2937. return false;
  2938. }
  2939. if ( GetSleepState() != AISS_AWAKE )
  2940. {
  2941. return false;
  2942. }
  2943. if ( !m_bUsingStandardThinkTime /*&& m_iFrameBlocked == -1 */ )
  2944. {
  2945. return false;
  2946. }
  2947. return true;
  2948. }
  2949. void CAI_BaseNPC::RebalanceThinks()
  2950. {
  2951. bool bDebugThinkTicks = ai_debug_think_ticks.GetBool();
  2952. if ( bDebugThinkTicks )
  2953. {
  2954. static int iPrevTick;
  2955. static int nThinksInTick;
  2956. static int nRebalanceableThinksInTick;
  2957. if ( gpGlobals->tickcount != iPrevTick )
  2958. {
  2959. DevMsg( "NPC per tick is %d [%d] (tick %d, frame %d)\n", nRebalanceableThinksInTick, nThinksInTick, iPrevTick, gpGlobals->framecount );
  2960. iPrevTick = gpGlobals->tickcount;
  2961. nThinksInTick = 0;
  2962. nRebalanceableThinksInTick = 0;
  2963. }
  2964. nThinksInTick++;
  2965. if ( CanThinkRebalance() )
  2966. nRebalanceableThinksInTick++;
  2967. }
  2968. if ( ShouldRebalanceThinks() && gpGlobals->tickcount >= gm_iNextThinkRebalanceTick )
  2969. {
  2970. AI_PROFILE_SCOPE(AI_Think_Rebalance );
  2971. static CUtlVector<AIRebalanceInfo_t> rebalanceCandidates( 16, 64 );
  2972. gm_iNextThinkRebalanceTick = gpGlobals->tickcount + TIME_TO_TICKS( random->RandomFloat( 3, 5) );
  2973. int i;
  2974. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  2975. Vector vPlayerForward;
  2976. Vector vPlayerEyePosition;
  2977. vPlayerForward.Init();
  2978. vPlayerEyePosition.Init();
  2979. if ( pPlayer )
  2980. {
  2981. pPlayer->EyePositionAndVectors( &vPlayerEyePosition, &vPlayerForward, NULL, NULL );
  2982. }
  2983. int iTicksPer10Hz = TIME_TO_TICKS( .1 );
  2984. int iMinTickRebalance = gpGlobals->tickcount - 1; // -1 needed for alternate ticks
  2985. int iMaxTickRebalance = gpGlobals->tickcount + iTicksPer10Hz;
  2986. for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
  2987. {
  2988. CAI_BaseNPC *pCandidate = g_AI_Manager.AccessAIs()[i];
  2989. if ( pCandidate->CanThinkRebalance() &&
  2990. ( pCandidate->GetNextThinkTick() >= iMinTickRebalance &&
  2991. pCandidate->GetNextThinkTick() < iMaxTickRebalance ) )
  2992. {
  2993. int iInfo = rebalanceCandidates.AddToTail();
  2994. rebalanceCandidates[iInfo].pNPC = pCandidate;
  2995. rebalanceCandidates[iInfo].iNextThinkTick = pCandidate->GetNextThinkTick();
  2996. if ( pCandidate->IsFlaggedEfficient() )
  2997. {
  2998. rebalanceCandidates[iInfo].bInPVS = false;
  2999. }
  3000. else if ( pPlayer )
  3001. {
  3002. Vector vToCandidate = pCandidate->EyePosition() - vPlayerEyePosition;
  3003. rebalanceCandidates[iInfo].bInPVS = ( UTIL_FindClientInPVS( pCandidate->edict() ) != NULL );
  3004. rebalanceCandidates[iInfo].distPlayer = VectorNormalize( vToCandidate );
  3005. rebalanceCandidates[iInfo].dotPlayer = vPlayerForward.Dot( vToCandidate );
  3006. }
  3007. else
  3008. {
  3009. rebalanceCandidates[iInfo].bInPVS = true;
  3010. rebalanceCandidates[iInfo].dotPlayer = 1;
  3011. rebalanceCandidates[iInfo].distPlayer = 0;
  3012. }
  3013. }
  3014. else if ( bDebugThinkTicks )
  3015. DevMsg( " Ignoring %d\n", pCandidate->GetNextThinkTick() );
  3016. }
  3017. if ( rebalanceCandidates.Count() )
  3018. {
  3019. rebalanceCandidates.Sort( ThinkRebalanceCompare );
  3020. int iMaxThinkersPerTick = ceil( (float)( rebalanceCandidates.Count() + 1 ) / (float)iTicksPer10Hz ); // +1 to account for "this"
  3021. int iCurTickDistributing = MIN( gpGlobals->tickcount, rebalanceCandidates[0].iNextThinkTick );
  3022. int iRemainingThinksToDistribute = iMaxThinkersPerTick - 1; // Start with one fewer first time because "this" is
  3023. // always gets a slot in the current tick to avoid complications
  3024. // in the calculation of "last think"
  3025. if ( bDebugThinkTicks )
  3026. {
  3027. DevMsg( "Rebalance %d!\n", rebalanceCandidates.Count() + 1 );
  3028. DevMsg( " Distributing %d\n", iCurTickDistributing );
  3029. }
  3030. for ( i = 0; i < rebalanceCandidates.Count(); i++ )
  3031. {
  3032. if ( iRemainingThinksToDistribute == 0 || rebalanceCandidates[i].iNextThinkTick > iCurTickDistributing )
  3033. {
  3034. if ( rebalanceCandidates[i].iNextThinkTick <= iCurTickDistributing )
  3035. {
  3036. iCurTickDistributing = iCurTickDistributing + 1;
  3037. }
  3038. else
  3039. {
  3040. iCurTickDistributing = rebalanceCandidates[i].iNextThinkTick;
  3041. }
  3042. if ( bDebugThinkTicks )
  3043. DevMsg( " Distributing %d\n", iCurTickDistributing );
  3044. iRemainingThinksToDistribute = iMaxThinkersPerTick;
  3045. }
  3046. if ( rebalanceCandidates[i].pNPC->GetNextThinkTick() != iCurTickDistributing )
  3047. {
  3048. if ( bDebugThinkTicks )
  3049. DevMsg( " Bumping %d to %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick(), iCurTickDistributing );
  3050. rebalanceCandidates[i].pNPC->SetNextThink( TICKS_TO_TIME( iCurTickDistributing ) );
  3051. }
  3052. else if ( bDebugThinkTicks )
  3053. {
  3054. DevMsg( " Leaving %d\n", rebalanceCandidates[i].pNPC->GetNextThinkTick() );
  3055. }
  3056. iRemainingThinksToDistribute--;
  3057. }
  3058. }
  3059. rebalanceCandidates.RemoveAll();
  3060. if ( bDebugThinkTicks )
  3061. {
  3062. DevMsg( "New distribution is:\n");
  3063. for ( i = 0; i < g_AI_Manager.NumAIs(); i++ )
  3064. {
  3065. DevMsg( " %d\n", g_AI_Manager.AccessAIs()[i]->GetNextThinkTick() );
  3066. }
  3067. }
  3068. Assert( GetNextThinkTick() == TICK_NEVER_THINK ); // never change this objects tick
  3069. }
  3070. }
  3071. static float g_NpcTimeThisFrame;
  3072. static float g_StartTimeCurThink;
  3073. bool CAI_BaseNPC::PreNPCThink()
  3074. {
  3075. static int iPrevFrame = -1;
  3076. static float frameTimeLimit = FLT_MAX;
  3077. static const ConVar *pHostTimescale;
  3078. if ( frameTimeLimit == FLT_MAX )
  3079. {
  3080. pHostTimescale = cvar->FindVar( "host_timescale" );
  3081. }
  3082. bool bUseThinkLimits = ( !m_bInChoreo && ShouldUseFrameThinkLimits() );
  3083. #ifdef _DEBUG
  3084. const float NPC_THINK_LIMIT = 30.0 / 1000.0;
  3085. #else
  3086. const float NPC_THINK_LIMIT = ( !IsXbox() ) ? (10.0 / 1000.0) : (12.5 / 1000.0);
  3087. #endif
  3088. g_StartTimeCurThink = 0;
  3089. if ( bUseThinkLimits && VCRGetMode() == VCR_Disabled )
  3090. {
  3091. if ( m_iFrameBlocked == gpGlobals->framecount )
  3092. {
  3093. DbgFrameLimitMsg( "Stalled %d (%d)\n", this, gpGlobals->framecount );
  3094. SetNextThink( gpGlobals->curtime );
  3095. return false;
  3096. }
  3097. else if ( gpGlobals->framecount != iPrevFrame )
  3098. {
  3099. DbgFrameLimitMsg( "--- FRAME: %d (%d)\n", this, gpGlobals->framecount );
  3100. float timescale = pHostTimescale->GetFloat();
  3101. if ( timescale < 1 )
  3102. timescale = 1;
  3103. iPrevFrame = gpGlobals->framecount;
  3104. frameTimeLimit = NPC_THINK_LIMIT * timescale;
  3105. g_NpcTimeThisFrame = 0;
  3106. }
  3107. else
  3108. {
  3109. if ( g_NpcTimeThisFrame > NPC_THINK_LIMIT )
  3110. {
  3111. float timeSinceLastRealThink = gpGlobals->curtime - m_flLastRealThinkTime;
  3112. // Don't bump anyone more that a quarter second
  3113. if ( timeSinceLastRealThink <= .25 )
  3114. {
  3115. DbgFrameLimitMsg( "Bumped %d (%d)\n", this, gpGlobals->framecount );
  3116. m_iFrameBlocked = gpGlobals->framecount;
  3117. SetNextThink( gpGlobals->curtime );
  3118. return false;
  3119. }
  3120. else
  3121. {
  3122. DbgFrameLimitMsg( "(Over %d )\n", this );
  3123. }
  3124. }
  3125. }
  3126. DbgFrameLimitMsg( "Running %d (%d)\n", this, gpGlobals->framecount );
  3127. g_StartTimeCurThink = engine->Time();
  3128. m_iFrameBlocked = -1;
  3129. m_nLastThinkTick = TIME_TO_TICKS( m_flLastRealThinkTime );
  3130. }
  3131. return true;
  3132. }
  3133. void CAI_BaseNPC::PostNPCThink( void )
  3134. {
  3135. if ( g_StartTimeCurThink != 0.0 && VCRGetMode() == VCR_Disabled )
  3136. {
  3137. g_NpcTimeThisFrame += engine->Time() - g_StartTimeCurThink;
  3138. }
  3139. }
  3140. void CAI_BaseNPC::CallNPCThink( void )
  3141. {
  3142. RebalanceThinks();
  3143. //---------------------------------
  3144. m_bUsingStandardThinkTime = false;
  3145. //---------------------------------
  3146. if ( !PreNPCThink() )
  3147. {
  3148. return;
  3149. }
  3150. // reduce cache queries by locking model in memory
  3151. MDLCACHE_CRITICAL_SECTION();
  3152. this->NPCThink();
  3153. m_flLastRealThinkTime = gpGlobals->curtime;
  3154. PostNPCThink();
  3155. }
  3156. bool NPC_CheckBrushExclude( CBaseEntity *pEntity, CBaseEntity *pBrush )
  3157. {
  3158. CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
  3159. if ( pNPC )
  3160. {
  3161. return pNPC->GetMoveProbe()->ShouldBrushBeIgnored( pBrush );
  3162. }
  3163. return false;
  3164. }
  3165. class CTraceFilterPlayerAvoidance : public CTraceFilterEntitiesOnly
  3166. {
  3167. public:
  3168. CTraceFilterPlayerAvoidance( const CBaseEntity *pEntity ) { m_pIgnore = pEntity; }
  3169. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  3170. {
  3171. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  3172. if ( m_pIgnore == pEntity )
  3173. return false;
  3174. if ( pEntity->IsPlayer() )
  3175. return true;
  3176. return false;
  3177. }
  3178. private:
  3179. const CBaseEntity *m_pIgnore;
  3180. };
  3181. void CAI_BaseNPC::GetPlayerAvoidBounds( Vector *pMins, Vector *pMaxs )
  3182. {
  3183. *pMins = WorldAlignMins();
  3184. *pMaxs = WorldAlignMaxs();
  3185. }
  3186. ConVar ai_debug_avoidancebounds( "ai_debug_avoidancebounds", "0" );
  3187. void CAI_BaseNPC::SetPlayerAvoidState( void )
  3188. {
  3189. bool bShouldPlayerAvoid = false;
  3190. Vector vNothing;
  3191. GetSequenceLinearMotion( GetSequence(), &vNothing );
  3192. bool bIsMoving = ( IsMoving() || ( vNothing != vec3_origin ) );
  3193. //If we are coming out of a script, check if we are stuck inside the player.
  3194. if ( m_bPerformAvoidance || ( ShouldPlayerAvoid() && bIsMoving ) )
  3195. {
  3196. trace_t trace;
  3197. Vector vMins, vMaxs;
  3198. GetPlayerAvoidBounds( &vMins, &vMaxs );
  3199. CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
  3200. if ( pLocalPlayer )
  3201. {
  3202. bShouldPlayerAvoid = IsBoxIntersectingBox( GetAbsOrigin() + vMins, GetAbsOrigin() + vMaxs,
  3203. pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMins(), pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMaxs() );
  3204. }
  3205. if ( ai_debug_avoidancebounds.GetBool() )
  3206. {
  3207. int iRed = ( bShouldPlayerAvoid == true ) ? 255 : 0;
  3208. NDebugOverlay::Box( GetAbsOrigin(), vMins, vMaxs, iRed, 0, 255, 64, 0.1 );
  3209. }
  3210. }
  3211. m_bPlayerAvoidState = ShouldPlayerAvoid();
  3212. m_bPerformAvoidance = bShouldPlayerAvoid;
  3213. if ( GetCollisionGroup() == COLLISION_GROUP_NPC || GetCollisionGroup() == COLLISION_GROUP_NPC_ACTOR )
  3214. {
  3215. if ( m_bPerformAvoidance == true )
  3216. {
  3217. SetCollisionGroup( COLLISION_GROUP_NPC_ACTOR );
  3218. }
  3219. else
  3220. {
  3221. SetCollisionGroup( COLLISION_GROUP_NPC );
  3222. }
  3223. }
  3224. }
  3225. //-----------------------------------------------------------------------------
  3226. // Purpose: Enables player avoidance when the player's vphysics shadow penetrates our vphysics shadow. This can
  3227. // happen when the player is hit by a combine ball, which pushes them into an adjacent npc. Subclasses should
  3228. // override this if it causes problems, but in general this will solve cases of the player getting stuck in
  3229. // the NPC from being pushed.
  3230. //-----------------------------------------------------------------------------
  3231. void CAI_BaseNPC::PlayerPenetratingVPhysics( void )
  3232. {
  3233. m_bPerformAvoidance = true;
  3234. }
  3235. //-----------------------------------------------------------------------------
  3236. bool CAI_BaseNPC::CheckPVSCondition()
  3237. {
  3238. bool bInPVS = ( UTIL_FindClientInPVS( edict() ) != NULL ) || (UTIL_ClientPVSIsExpanded() && UTIL_FindClientInVisibilityPVS( edict() ));
  3239. if ( bInPVS )
  3240. SetCondition( COND_IN_PVS );
  3241. else
  3242. ClearCondition( COND_IN_PVS );
  3243. return bInPVS;
  3244. }
  3245. //-----------------------------------------------------------------------------
  3246. // NPC Think - calls out to core AI functions and handles this
  3247. // npc's specific animation events
  3248. //
  3249. void CAI_BaseNPC::NPCThink( void )
  3250. {
  3251. if ( m_bCheckContacts )
  3252. {
  3253. CheckPhysicsContacts();
  3254. }
  3255. Assert( !(m_NPCState == NPC_STATE_DEAD && m_lifeState == LIFE_ALIVE) );
  3256. //---------------------------------
  3257. SetNextThink( TICK_NEVER_THINK );
  3258. //---------------------------------
  3259. bool bInPVS = CheckPVSCondition();
  3260. //---------------------------------
  3261. UpdateSleepState( bInPVS );
  3262. //---------------------------------
  3263. bool bRanDecision = false;
  3264. if ( GetEfficiency() < AIE_DORMANT && GetSleepState() == AISS_AWAKE )
  3265. {
  3266. static CFastTimer timer;
  3267. float thinkLimit = ai_show_think_tolerance.GetFloat();
  3268. if ( thinkLimit > 0 )
  3269. timer.Start();
  3270. if ( g_pAINetworkManager && g_pAINetworkManager->IsInitialized() )
  3271. {
  3272. VPROF_BUDGET( "NPCs", VPROF_BUDGETGROUP_NPCS );
  3273. AI_PROFILE_SCOPE_BEGIN_( GetClassScheduleIdSpace()->GetClassName() ); // need to use a string stable from map load to map load
  3274. SetPlayerAvoidState();
  3275. if ( PreThink() )
  3276. {
  3277. if ( m_flNextDecisionTime <= gpGlobals->curtime )
  3278. {
  3279. bRanDecision = true;
  3280. m_ScheduleState.bTaskRanAutomovement = false;
  3281. m_ScheduleState.bTaskUpdatedYaw = false;
  3282. RunAI();
  3283. }
  3284. else
  3285. {
  3286. if ( m_ScheduleState.bTaskRanAutomovement )
  3287. AutoMovement();
  3288. if ( m_ScheduleState.bTaskUpdatedYaw )
  3289. GetMotor()->UpdateYaw();
  3290. }
  3291. PostRun();
  3292. PerformMovement();
  3293. m_bIsMoving = IsMoving();
  3294. PostMovement();
  3295. SetSimulationTime( gpGlobals->curtime );
  3296. }
  3297. else
  3298. m_flTimeLastMovement = FLT_MAX;
  3299. AI_PROFILE_SCOPE_END();
  3300. }
  3301. if ( thinkLimit > 0 )
  3302. {
  3303. timer.End();
  3304. float thinkTime = g_AIRunTimer.GetDuration().GetMillisecondsF();
  3305. if ( thinkTime > thinkLimit )
  3306. {
  3307. int color = (int)RemapVal( thinkTime, thinkLimit, thinkLimit * 3, 96.0, 255.0 );
  3308. if ( color > 255 )
  3309. color = 255;
  3310. else if ( color < 96 )
  3311. color = 96;
  3312. Vector right;
  3313. Vector vecPoint;
  3314. vecPoint = EyePosition() + Vector( 0, 0, 12 );
  3315. GetVectors( NULL, &right, NULL );
  3316. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), color, 0, 0, false , 1.0 );
  3317. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) + right * 16, color, 0, 0, false , 1.0 );
  3318. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 16 ) - right * 16, color, 0, 0, false , 1.0 );
  3319. }
  3320. }
  3321. }
  3322. m_bUsingStandardThinkTime = ( GetNextThinkTick() == TICK_NEVER_THINK );
  3323. UpdateEfficiency( bInPVS );
  3324. if ( m_bUsingStandardThinkTime )
  3325. {
  3326. static const char *ppszEfficiencies[] =
  3327. {
  3328. "AIE_NORMAL",
  3329. "AIE_EFFICIENT",
  3330. "AIE_VERY_EFFICIENT",
  3331. "AIE_SUPER_EFFICIENT",
  3332. "AIE_DORMANT",
  3333. };
  3334. static const char *ppszMoveEfficiencies[] =
  3335. {
  3336. "AIME_NORMAL",
  3337. "AIME_EFFICIENT",
  3338. };
  3339. if ( ai_debug_efficiency.GetBool() )
  3340. DevMsg( this, "Eff: %s, Move: %s\n", ppszEfficiencies[GetEfficiency()], ppszMoveEfficiencies[GetMoveEfficiency()] );
  3341. static float g_DecisionIntervals[] =
  3342. {
  3343. .1, // AIE_NORMAL
  3344. .2, // AIE_EFFICIENT
  3345. .4, // AIE_VERY_EFFICIENT
  3346. .6, // AIE_SUPER_EFFICIENT
  3347. };
  3348. if ( bRanDecision )
  3349. {
  3350. m_flNextDecisionTime = gpGlobals->curtime + g_DecisionIntervals[GetEfficiency()];
  3351. }
  3352. if ( GetMoveEfficiency() == AIME_NORMAL || GetEfficiency() == AIE_NORMAL )
  3353. {
  3354. SetNextThink( gpGlobals->curtime + .1 );
  3355. }
  3356. else
  3357. {
  3358. SetNextThink( gpGlobals->curtime + .2 );
  3359. }
  3360. }
  3361. else
  3362. {
  3363. m_flNextDecisionTime = 0;
  3364. }
  3365. }
  3366. //=========================================================
  3367. // CAI_BaseNPC - USE - will make a npc angry at whomever
  3368. // activated it.
  3369. //=========================================================
  3370. void CAI_BaseNPC::NPCUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  3371. {
  3372. return;
  3373. // Can't +USE NPCs running scripts
  3374. if ( GetState() == NPC_STATE_SCRIPT )
  3375. return;
  3376. if ( IsInAScript() )
  3377. return;
  3378. SetIdealState( NPC_STATE_ALERT );
  3379. }
  3380. //-----------------------------------------------------------------------------
  3381. // Purpose: Virtual function that allows us to have any npc ignore a set of
  3382. // shared conditions.
  3383. //
  3384. //-----------------------------------------------------------------------------
  3385. void CAI_BaseNPC::RemoveIgnoredConditions( void )
  3386. {
  3387. m_ConditionsPreIgnore = m_Conditions;
  3388. m_Conditions.And( m_InverseIgnoreConditions, &m_Conditions );
  3389. if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
  3390. m_hCine->RemoveIgnoredConditions();
  3391. }
  3392. //=========================================================
  3393. // RangeAttack1Conditions
  3394. //=========================================================
  3395. int CAI_BaseNPC::RangeAttack1Conditions ( float flDot, float flDist )
  3396. {
  3397. if ( flDist < 64)
  3398. {
  3399. return COND_TOO_CLOSE_TO_ATTACK;
  3400. }
  3401. else if (flDist > 784)
  3402. {
  3403. return COND_TOO_FAR_TO_ATTACK;
  3404. }
  3405. else if (flDot < 0.5)
  3406. {
  3407. return COND_NOT_FACING_ATTACK;
  3408. }
  3409. return COND_CAN_RANGE_ATTACK1;
  3410. }
  3411. //=========================================================
  3412. // RangeAttack2Conditions
  3413. //=========================================================
  3414. int CAI_BaseNPC::RangeAttack2Conditions ( float flDot, float flDist )
  3415. {
  3416. if ( flDist < 64)
  3417. {
  3418. return COND_TOO_CLOSE_TO_ATTACK;
  3419. }
  3420. else if (flDist > 512)
  3421. {
  3422. return COND_TOO_FAR_TO_ATTACK;
  3423. }
  3424. else if (flDot < 0.5)
  3425. {
  3426. return COND_NOT_FACING_ATTACK;
  3427. }
  3428. return COND_CAN_RANGE_ATTACK2;
  3429. }
  3430. //=========================================================
  3431. // MeleeAttack1Conditions
  3432. //=========================================================
  3433. int CAI_BaseNPC::MeleeAttack1Conditions ( float flDot, float flDist )
  3434. {
  3435. if ( flDist > 64)
  3436. {
  3437. return COND_TOO_FAR_TO_ATTACK;
  3438. }
  3439. else if (flDot < 0.7)
  3440. {
  3441. return 0;
  3442. }
  3443. else if (GetEnemy() == NULL)
  3444. {
  3445. return 0;
  3446. }
  3447. // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb)
  3448. if ( GetEnemy()->GetFlags() & FL_ONGROUND )
  3449. {
  3450. return COND_CAN_MELEE_ATTACK1;
  3451. }
  3452. return 0;
  3453. }
  3454. //=========================================================
  3455. // MeleeAttack2Conditions
  3456. //=========================================================
  3457. int CAI_BaseNPC::MeleeAttack2Conditions ( float flDot, float flDist )
  3458. {
  3459. if ( flDist > 64)
  3460. {
  3461. return COND_TOO_FAR_TO_ATTACK;
  3462. }
  3463. else if (flDot < 0.7)
  3464. {
  3465. return 0;
  3466. }
  3467. return COND_CAN_MELEE_ATTACK2;
  3468. }
  3469. // Get capability mask
  3470. int CAI_BaseNPC::CapabilitiesGet( void ) const
  3471. {
  3472. int capability = m_afCapability;
  3473. if ( GetActiveWeapon() )
  3474. {
  3475. capability |= GetActiveWeapon()->CapabilitiesGet();
  3476. }
  3477. return capability;
  3478. }
  3479. // Set capability mask
  3480. int CAI_BaseNPC::CapabilitiesAdd( int capability )
  3481. {
  3482. m_afCapability |= capability;
  3483. return m_afCapability;
  3484. }
  3485. // Set capability mask
  3486. int CAI_BaseNPC::CapabilitiesRemove( int capability )
  3487. {
  3488. m_afCapability &= ~capability;
  3489. return m_afCapability;
  3490. }
  3491. // Clear capability mask
  3492. void CAI_BaseNPC::CapabilitiesClear( void )
  3493. {
  3494. m_afCapability = 0;
  3495. }
  3496. //=========================================================
  3497. // ClearAttacks - clear out all attack conditions
  3498. //=========================================================
  3499. void CAI_BaseNPC::ClearAttackConditions( )
  3500. {
  3501. // Clear all attack conditions
  3502. ClearCondition( COND_CAN_RANGE_ATTACK1 );
  3503. ClearCondition( COND_CAN_RANGE_ATTACK2 );
  3504. ClearCondition( COND_CAN_MELEE_ATTACK1 );
  3505. ClearCondition( COND_CAN_MELEE_ATTACK2 );
  3506. ClearCondition( COND_WEAPON_HAS_LOS );
  3507. ClearCondition( COND_WEAPON_BLOCKED_BY_FRIEND );
  3508. ClearCondition( COND_WEAPON_PLAYER_IN_SPREAD ); // Player in shooting direction
  3509. ClearCondition( COND_WEAPON_PLAYER_NEAR_TARGET ); // Player near shooting position
  3510. ClearCondition( COND_WEAPON_SIGHT_OCCLUDED );
  3511. }
  3512. //=========================================================
  3513. // GatherAttackConditions - sets all of the bits for attacks that the
  3514. // npc is capable of carrying out on the passed entity.
  3515. //=========================================================
  3516. void CAI_BaseNPC::GatherAttackConditions( CBaseEntity *pTarget, float flDist )
  3517. {
  3518. AI_PROFILE_SCOPE(CAI_BaseNPC_GatherAttackConditions);
  3519. Vector vecLOS = ( pTarget->GetAbsOrigin() - GetAbsOrigin() );
  3520. vecLOS.z = 0;
  3521. VectorNormalize( vecLOS );
  3522. Vector vBodyDir = BodyDirection2D( );
  3523. float flDot = DotProduct( vecLOS, vBodyDir );
  3524. // we know the enemy is in front now. We'll find which attacks the npc is capable of by
  3525. // checking for corresponding Activities in the model file, then do the simple checks to validate
  3526. // those attack types.
  3527. int capability;
  3528. Vector targetPos;
  3529. bool bWeaponHasLOS;
  3530. int condition;
  3531. capability = CapabilitiesGet();
  3532. // Clear all attack conditions
  3533. AI_PROFILE_SCOPE_BEGIN( CAI_BaseNPC_GatherAttackConditions_PrimaryWeaponLOS );
  3534. // @TODO (toml 06-15-03): There are simple cases where
  3535. // the upper torso of the enemy is visible, and the NPC is at an angle below
  3536. // them, but the above test fails because BodyTarget returns the center of
  3537. // the target. This needs some better handling/closer evaluation
  3538. // Try the eyes first, as likely to succeed (because can see or else wouldn't be here) thus reducing
  3539. // the odds of the need for a second trace
  3540. ClearAttackConditions();
  3541. targetPos = pTarget->EyePosition();
  3542. bWeaponHasLOS = CurrentWeaponLOSCondition( targetPos, true );
  3543. AI_PROFILE_SCOPE_END();
  3544. if ( !bWeaponHasLOS )
  3545. {
  3546. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_SecondaryWeaponLOS );
  3547. ClearAttackConditions( );
  3548. targetPos = pTarget->BodyTarget( GetAbsOrigin() );
  3549. bWeaponHasLOS = CurrentWeaponLOSCondition( targetPos, true );
  3550. }
  3551. else
  3552. {
  3553. SetCondition( COND_WEAPON_HAS_LOS );
  3554. }
  3555. bool bWeaponIsReady = (GetActiveWeapon() && !IsWeaponStateChanging());
  3556. // FIXME: move this out of here
  3557. if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK1) && bWeaponIsReady )
  3558. {
  3559. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack1Condition );
  3560. condition = GetActiveWeapon()->WeaponRangeAttack1Condition(flDot, flDist);
  3561. if ( condition == COND_NOT_FACING_ATTACK && FInAimCone( targetPos ) )
  3562. DevMsg( "Warning: COND_NOT_FACING_ATTACK set but FInAimCone is true\n" );
  3563. if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS)
  3564. {
  3565. SetCondition(condition);
  3566. }
  3567. }
  3568. else if ( capability & bits_CAP_INNATE_RANGE_ATTACK1 )
  3569. {
  3570. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack1Condition );
  3571. condition = RangeAttack1Conditions( flDot, flDist );
  3572. if (condition != COND_CAN_RANGE_ATTACK1 || bWeaponHasLOS)
  3573. {
  3574. SetCondition(condition);
  3575. }
  3576. }
  3577. if ( (capability & bits_CAP_WEAPON_RANGE_ATTACK2) && bWeaponIsReady && ( GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK2 ) )
  3578. {
  3579. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponRangeAttack2Condition );
  3580. condition = GetActiveWeapon()->WeaponRangeAttack2Condition(flDot, flDist);
  3581. if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS)
  3582. {
  3583. SetCondition(condition);
  3584. }
  3585. }
  3586. else if ( capability & bits_CAP_INNATE_RANGE_ATTACK2 )
  3587. {
  3588. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_RangeAttack2Condition );
  3589. condition = RangeAttack2Conditions( flDot, flDist );
  3590. if (condition != COND_CAN_RANGE_ATTACK2 || bWeaponHasLOS)
  3591. {
  3592. SetCondition(condition);
  3593. }
  3594. }
  3595. if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK1) && bWeaponIsReady)
  3596. {
  3597. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack1Condition );
  3598. SetCondition(GetActiveWeapon()->WeaponMeleeAttack1Condition(flDot, flDist));
  3599. }
  3600. else if ( capability & bits_CAP_INNATE_MELEE_ATTACK1 )
  3601. {
  3602. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack1Condition );
  3603. SetCondition(MeleeAttack1Conditions ( flDot, flDist ));
  3604. }
  3605. if ( (capability & bits_CAP_WEAPON_MELEE_ATTACK2) && bWeaponIsReady)
  3606. {
  3607. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_WeaponMeleeAttack2Condition );
  3608. SetCondition(GetActiveWeapon()->WeaponMeleeAttack2Condition(flDot, flDist));
  3609. }
  3610. else if ( capability & bits_CAP_INNATE_MELEE_ATTACK2 )
  3611. {
  3612. AI_PROFILE_SCOPE( CAI_BaseNPC_GatherAttackConditions_MeleeAttack2Condition );
  3613. SetCondition(MeleeAttack2Conditions ( flDot, flDist ));
  3614. }
  3615. // -----------------------------------------------------------------
  3616. // If any attacks are possible clear attack specific bits
  3617. // -----------------------------------------------------------------
  3618. if (HasCondition(COND_CAN_RANGE_ATTACK2) ||
  3619. HasCondition(COND_CAN_RANGE_ATTACK1) ||
  3620. HasCondition(COND_CAN_MELEE_ATTACK2) ||
  3621. HasCondition(COND_CAN_MELEE_ATTACK1) )
  3622. {
  3623. ClearCondition(COND_TOO_CLOSE_TO_ATTACK);
  3624. ClearCondition(COND_TOO_FAR_TO_ATTACK);
  3625. ClearCondition(COND_WEAPON_BLOCKED_BY_FRIEND);
  3626. }
  3627. }
  3628. //=========================================================
  3629. // SetState
  3630. //=========================================================
  3631. void CAI_BaseNPC::SetState( NPC_STATE State )
  3632. {
  3633. NPC_STATE OldState;
  3634. OldState = m_NPCState;
  3635. if ( State != m_NPCState )
  3636. {
  3637. m_flLastStateChangeTime = gpGlobals->curtime;
  3638. }
  3639. switch( State )
  3640. {
  3641. // Drop enemy pointers when going to idle
  3642. case NPC_STATE_IDLE:
  3643. if ( GetEnemy() != NULL )
  3644. {
  3645. SetEnemy( NULL ); // not allowed to have an enemy anymore.
  3646. DevMsg( 2, "Stripped\n" );
  3647. }
  3648. break;
  3649. }
  3650. bool fNotifyChange = false;
  3651. if( m_NPCState != State )
  3652. {
  3653. // Don't notify if we're changing to a state we're already in!
  3654. fNotifyChange = true;
  3655. }
  3656. m_NPCState = State;
  3657. SetIdealState( State );
  3658. // Notify the character that its state has changed.
  3659. if( fNotifyChange )
  3660. {
  3661. OnStateChange( OldState, m_NPCState );
  3662. }
  3663. }
  3664. bool CAI_BaseNPC::WokeThisTick() const
  3665. {
  3666. return m_nWakeTick == gpGlobals->tickcount ? true : false;
  3667. }
  3668. //-----------------------------------------------------------------------------
  3669. //-----------------------------------------------------------------------------
  3670. void CAI_BaseNPC::Wake( bool bFireOutput )
  3671. {
  3672. if ( GetSleepState() != AISS_AWAKE )
  3673. {
  3674. m_nWakeTick = gpGlobals->tickcount;
  3675. SetSleepState( AISS_AWAKE );
  3676. RemoveEffects( EF_NODRAW );
  3677. if ( bFireOutput )
  3678. m_OnWake.FireOutput( this, this );
  3679. if ( m_bWakeSquad && GetSquad() )
  3680. {
  3681. AISquadIter_t iter;
  3682. for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) )
  3683. {
  3684. if ( pSquadMember->IsAlive() && pSquadMember != this )
  3685. {
  3686. pSquadMember->m_bWakeSquad = false;
  3687. pSquadMember->Wake();
  3688. }
  3689. }
  3690. }
  3691. }
  3692. }
  3693. //-----------------------------------------------------------------------------
  3694. //-----------------------------------------------------------------------------
  3695. void CAI_BaseNPC::Sleep()
  3696. {
  3697. // Don't render.
  3698. AddEffects( EF_NODRAW );
  3699. if( GetState() == NPC_STATE_SCRIPT )
  3700. {
  3701. Warning( "%s put to sleep while in Scripted state!\n", GetClassname() );
  3702. }
  3703. VacateStrategySlot();
  3704. // Slam my schedule.
  3705. SetSchedule( SCHED_SLEEP );
  3706. m_OnSleep.FireOutput( this, this );
  3707. }
  3708. //-----------------------------------------------------------------------------
  3709. // Sets all sensing-related conditions
  3710. //-----------------------------------------------------------------------------
  3711. void CAI_BaseNPC::PerformSensing( void )
  3712. {
  3713. GetSenses()->PerformSensing();
  3714. }
  3715. //-----------------------------------------------------------------------------
  3716. void CAI_BaseNPC::ClearSenseConditions( void )
  3717. {
  3718. static int conditionsToClear[] =
  3719. {
  3720. COND_SEE_HATE,
  3721. COND_SEE_DISLIKE,
  3722. COND_SEE_ENEMY,
  3723. COND_SEE_FEAR,
  3724. COND_SEE_NEMESIS,
  3725. COND_SEE_PLAYER,
  3726. COND_HEAR_DANGER,
  3727. COND_HEAR_COMBAT,
  3728. COND_HEAR_WORLD,
  3729. COND_HEAR_PLAYER,
  3730. COND_HEAR_THUMPER,
  3731. COND_HEAR_BUGBAIT,
  3732. COND_HEAR_PHYSICS_DANGER,
  3733. COND_HEAR_MOVE_AWAY,
  3734. COND_SMELL,
  3735. };
  3736. ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
  3737. }
  3738. //-----------------------------------------------------------------------------
  3739. void CAI_BaseNPC::CheckOnGround( void )
  3740. {
  3741. bool bScriptedWait = ( IsCurSchedule( SCHED_WAIT_FOR_SCRIPT ) || ( m_hCine && m_scriptState == CAI_BaseNPC::SCRIPT_WAIT ) );
  3742. if ( !bScriptedWait && !HasCondition( COND_FLOATING_OFF_GROUND ) )
  3743. {
  3744. // parented objects are never floating
  3745. if (GetMoveParent() != NULL)
  3746. return;
  3747. // NPCs in scripts with the fly flag shouldn't fall.
  3748. // FIXME: should NPCS with FL_FLY ever fall? Doesn't seem like they should.
  3749. if ( ( GetState() == NPC_STATE_SCRIPT ) && ( GetFlags() & FL_FLY ) )
  3750. return;
  3751. if ( ( GetNavType() == NAV_GROUND ) && ( GetMoveType() != MOVETYPE_VPHYSICS ) && ( GetMoveType() != MOVETYPE_NONE ) )
  3752. {
  3753. if ( m_CheckOnGroundTimer.Expired() )
  3754. {
  3755. m_CheckOnGroundTimer.Set(0.5);
  3756. // check a shrunk box centered around the foot
  3757. Vector maxs = WorldAlignMaxs();
  3758. Vector mins = WorldAlignMins();
  3759. if ( mins != maxs ) // some NPCs have no hull, so mins == maxs == vec3_origin
  3760. {
  3761. maxs -= Vector( 0.0f, 0.0f, 0.2f );
  3762. Vector vecStart = GetAbsOrigin() + Vector( 0, 0, .1f );
  3763. Vector vecDown = GetAbsOrigin();
  3764. vecDown.z -= 4.0;
  3765. trace_t trace;
  3766. m_pMoveProbe->TraceHull( vecStart, vecDown, mins, maxs, MASK_NPCSOLID, &trace );
  3767. if (trace.fraction == 1.0)
  3768. {
  3769. SetCondition( COND_FLOATING_OFF_GROUND );
  3770. SetGroundEntity( NULL );
  3771. }
  3772. else
  3773. {
  3774. if ( trace.startsolid && trace.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS &&
  3775. trace.m_pEnt->VPhysicsGetObject() && trace.m_pEnt->VPhysicsGetObject()->GetMass() < VPHYSICS_LARGE_OBJECT_MASS )
  3776. {
  3777. // stuck inside a small physics object?
  3778. m_CheckOnGroundTimer.Set(0.1f);
  3779. NPCPhysics_CreateSolver( this, trace.m_pEnt, true, 0.25f );
  3780. if ( VPhysicsGetObject() )
  3781. {
  3782. VPhysicsGetObject()->RecheckContactPoints();
  3783. }
  3784. }
  3785. // Check to see if someone changed the ground on us...
  3786. if ( trace.m_pEnt && trace.m_pEnt != GetGroundEntity() )
  3787. {
  3788. SetGroundEntity( trace.m_pEnt );
  3789. }
  3790. }
  3791. }
  3792. }
  3793. }
  3794. }
  3795. else
  3796. {
  3797. // parented objects are never floating
  3798. if ( bScriptedWait || GetMoveParent() != NULL || (GetFlags() & FL_ONGROUND ) || GetNavType() != NAV_GROUND )
  3799. {
  3800. ClearCondition( COND_FLOATING_OFF_GROUND );
  3801. }
  3802. }
  3803. }
  3804. void CAI_BaseNPC::NotifyPushMove()
  3805. {
  3806. // don't recheck ground when I'm being push-moved
  3807. m_CheckOnGroundTimer.Set( 0.5f );
  3808. }
  3809. //-----------------------------------------------------------------------------
  3810. // Purpose:
  3811. //-----------------------------------------------------------------------------
  3812. bool CAI_BaseNPC::CanFlinch( void )
  3813. {
  3814. if ( IsCurSchedule( SCHED_BIG_FLINCH ) )
  3815. return false;
  3816. if ( m_flNextFlinchTime >= gpGlobals->curtime )
  3817. return false;
  3818. return true;
  3819. }
  3820. //-----------------------------------------------------------------------------
  3821. // Purpose:
  3822. //-----------------------------------------------------------------------------
  3823. void CAI_BaseNPC::CheckFlinches( void )
  3824. {
  3825. // If we're currently flinching, don't allow gesture flinches to be overlaid
  3826. if ( IsCurSchedule( SCHED_BIG_FLINCH ) )
  3827. {
  3828. ClearCondition( COND_LIGHT_DAMAGE );
  3829. ClearCondition( COND_HEAVY_DAMAGE );
  3830. }
  3831. // If we've taken heavy damage, try to do a full schedule flinch
  3832. if ( HasCondition(COND_HEAVY_DAMAGE) )
  3833. {
  3834. // If we've already flinched recently, gesture flinch instead.
  3835. if ( HasMemory(bits_MEMORY_FLINCHED) )
  3836. {
  3837. // Clear the heavy damage condition so we don't interrupt schedules
  3838. // when we play a gesture flinch because we recently did a full flinch.
  3839. // Prevents the player from stun-locking enemies, even though they don't full flinch.
  3840. ClearCondition( COND_HEAVY_DAMAGE );
  3841. }
  3842. else if ( !HasInterruptCondition(COND_HEAVY_DAMAGE) )
  3843. {
  3844. // If we have taken heavy damage, but the current schedule doesn't
  3845. // break on that, resort to just playing a gesture flinch.
  3846. PlayFlinchGesture();
  3847. }
  3848. // Otherwise, do nothing. The heavy damage will interrupt our schedule and we'll flinch.
  3849. }
  3850. else if ( HasCondition( COND_LIGHT_DAMAGE ) )
  3851. {
  3852. // If we have taken light damage play gesture flinches
  3853. PlayFlinchGesture();
  3854. }
  3855. // If it's been a while since we did a full flinch, forget that we flinched so we'll flinch fully again
  3856. if ( HasMemory(bits_MEMORY_FLINCHED) && gpGlobals->curtime > m_flNextFlinchTime )
  3857. {
  3858. Forget(bits_MEMORY_FLINCHED);
  3859. }
  3860. }
  3861. //-----------------------------------------------------------------------------
  3862. void CAI_BaseNPC::GatherConditions( void )
  3863. {
  3864. m_bConditionsGathered = true;
  3865. g_AIConditionsTimer.Start();
  3866. if( gpGlobals->curtime > m_flTimePingEffect && m_flTimePingEffect > 0.0f )
  3867. {
  3868. // Turn off the pinging.
  3869. DispatchUpdateTransmitState();
  3870. m_flTimePingEffect = 0.0f;
  3871. }
  3872. if ( m_NPCState != NPC_STATE_NONE && m_NPCState != NPC_STATE_DEAD )
  3873. {
  3874. if ( FacingIdeal() )
  3875. Forget( bits_MEMORY_TURNING );
  3876. bool bForcedGather = m_bForceConditionsGather;
  3877. m_bForceConditionsGather = false;
  3878. if ( m_pfnThink != (BASEPTR)&CAI_BaseNPC::CallNPCThink )
  3879. {
  3880. if ( UTIL_FindClientInPVS( edict() ) != NULL )
  3881. SetCondition( COND_IN_PVS );
  3882. else
  3883. ClearCondition( COND_IN_PVS );
  3884. }
  3885. // Sample the environment. Do this unconditionally if there is a player in this
  3886. // npc's PVS. NPCs in COMBAT state are allowed to simulate when there is no player in
  3887. // the same PVS. This is so that any fights in progress will continue even if the player leaves the PVS.
  3888. if ( !IsFlaggedEfficient() &&
  3889. ( bForcedGather ||
  3890. HasCondition( COND_IN_PVS ) ||
  3891. ShouldAlwaysThink() ||
  3892. m_NPCState == NPC_STATE_COMBAT ) )
  3893. {
  3894. CheckOnGround();
  3895. if ( ShouldPlayIdleSound() )
  3896. {
  3897. AI_PROFILE_SCOPE(CAI_BaseNPC_IdleSound);
  3898. IdleSound();
  3899. }
  3900. PerformSensing();
  3901. GetEnemies()->RefreshMemories();
  3902. ChooseEnemy();
  3903. // Check to see if there is a better weapon available
  3904. if (Weapon_IsBetterAvailable())
  3905. {
  3906. SetCondition(COND_BETTER_WEAPON_AVAILABLE);
  3907. }
  3908. if ( GetCurSchedule() &&
  3909. ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT) &&
  3910. GetEnemy() &&
  3911. !HasCondition( COND_NEW_ENEMY ) &&
  3912. GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY ) )
  3913. {
  3914. // @Note (toml 05-05-04): There seems to be a case where an NPC can not respond
  3915. // to COND_NEW_ENEMY. Only evidence right now is save
  3916. // games after the fact, so for now, just patching it up
  3917. DevMsg( 2, "Had to force COND_NEW_ENEMY\n" );
  3918. SetCondition(COND_NEW_ENEMY);
  3919. }
  3920. }
  3921. else
  3922. {
  3923. // if not done, can have problems if leave PVS in same frame heard/saw things,
  3924. // since only PerformSensing clears conditions
  3925. ClearSenseConditions();
  3926. }
  3927. // do these calculations if npc has an enemy.
  3928. if ( GetEnemy() != NULL )
  3929. {
  3930. if ( !IsFlaggedEfficient() )
  3931. {
  3932. GatherEnemyConditions( GetEnemy() );
  3933. m_flLastEnemyTime = gpGlobals->curtime;
  3934. }
  3935. else
  3936. {
  3937. SetEnemy( NULL );
  3938. }
  3939. }
  3940. // do these calculations if npc has a target
  3941. if ( GetTarget() != NULL )
  3942. {
  3943. CheckTarget( GetTarget() );
  3944. }
  3945. CheckAmmo();
  3946. CheckFlinches();
  3947. CheckSquad();
  3948. }
  3949. else
  3950. ClearCondition( COND_IN_PVS );
  3951. g_AIConditionsTimer.End();
  3952. }
  3953. //-----------------------------------------------------------------------------
  3954. // Purpose:
  3955. //-----------------------------------------------------------------------------
  3956. void CAI_BaseNPC::PrescheduleThink( void )
  3957. {
  3958. #ifdef HL2_EPISODIC
  3959. CheckForScriptedNPCInteractions();
  3960. #endif
  3961. // If we use weapons, and our desired weapon state is not the current, fix it
  3962. if( (CapabilitiesGet() & bits_CAP_USE_WEAPONS) && (m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) )
  3963. {
  3964. if ( IsAlive() && !IsInAScript() )
  3965. {
  3966. if ( !IsCurSchedule( SCHED_MELEE_ATTACK1, false ) && !IsCurSchedule( SCHED_MELEE_ATTACK2, false ) &&
  3967. !IsCurSchedule( SCHED_RANGE_ATTACK1, false ) && !IsCurSchedule( SCHED_RANGE_ATTACK2, false ) )
  3968. {
  3969. if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED )
  3970. {
  3971. HolsterWeapon();
  3972. }
  3973. else if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED )
  3974. {
  3975. UnholsterWeapon();
  3976. }
  3977. }
  3978. }
  3979. else
  3980. {
  3981. // Throw away the request
  3982. m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
  3983. }
  3984. }
  3985. }
  3986. //-----------------------------------------------------------------------------
  3987. // Main entry point for processing AI
  3988. //-----------------------------------------------------------------------------
  3989. void CAI_BaseNPC::RunAI( void )
  3990. {
  3991. AI_PROFILE_SCOPE(CAI_BaseNPC_RunAI);
  3992. g_AIRunTimer.Start();
  3993. if( ai_debug_squads.GetBool() )
  3994. {
  3995. if( IsInSquad() && GetSquad() && !CAI_Squad::IsSilentMember(this ) && ( GetSquad()->IsLeader( this ) || GetSquad()->NumMembers() == 1 ) )
  3996. {
  3997. AISquadIter_t iter;
  3998. CAI_Squad *pSquad = GetSquad();
  3999. Vector right;
  4000. Vector vecPoint;
  4001. vecPoint = EyePosition() + Vector( 0, 0, 12 );
  4002. GetVectors( NULL, &right, NULL );
  4003. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 0, 255, 0, false , 0.1 );
  4004. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 0, 255, 0, false , 0.1 );
  4005. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 0, 255, 0, false , 0.1 );
  4006. for ( CAI_BaseNPC *pSquadMember = pSquad->GetFirstMember( &iter, false ); pSquadMember; pSquadMember = pSquad->GetNextMember( &iter, false ) )
  4007. {
  4008. if ( pSquadMember != this )
  4009. NDebugOverlay::Line( EyePosition(), pSquadMember->EyePosition(), 0,
  4010. CAI_Squad::IsSilentMember(pSquadMember) ? 127 : 255, 0, false , 0.1 );
  4011. }
  4012. }
  4013. }
  4014. if( ai_debug_loners.GetBool() && !IsInSquad() && AI_IsSinglePlayer() )
  4015. {
  4016. Vector right;
  4017. Vector vecPoint;
  4018. vecPoint = EyePosition() + Vector( 0, 0, 12 );
  4019. UTIL_GetLocalPlayer()->GetVectors( NULL, &right, NULL );
  4020. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 64 ), 255, 0, 0, false , 0.1 );
  4021. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) + right * 32, 255, 0, 0, false , 0.1 );
  4022. NDebugOverlay::Line( vecPoint, vecPoint + Vector( 0, 0, 32 ) - right * 32, 255, 0, 0, false , 0.1 );
  4023. }
  4024. #ifdef _DEBUG
  4025. m_bSelected = ( (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) != 0 );
  4026. #endif
  4027. m_bConditionsGathered = false;
  4028. m_bSkippedChooseEnemy = false;
  4029. if ( g_pDeveloper->GetInt() && !GetNavigator()->IsOnNetwork() )
  4030. {
  4031. AddTimedOverlay( "NPC w/no reachable nodes!", 5 );
  4032. }
  4033. AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_RunAI_GatherConditions);
  4034. GatherConditions();
  4035. RemoveIgnoredConditions();
  4036. AI_PROFILE_SCOPE_END();
  4037. if ( !m_bConditionsGathered )
  4038. m_bConditionsGathered = true; // derived class didn't call to base
  4039. TryRestoreHull();
  4040. g_AIPrescheduleThinkTimer.Start();
  4041. AI_PROFILE_SCOPE_BEGIN(CAI_RunAI_PrescheduleThink);
  4042. PrescheduleThink();
  4043. AI_PROFILE_SCOPE_END();
  4044. g_AIPrescheduleThinkTimer.End();
  4045. MaintainSchedule();
  4046. PostscheduleThink();
  4047. ClearTransientConditions();
  4048. g_AIRunTimer.End();
  4049. }
  4050. //-----------------------------------------------------------------------------
  4051. void CAI_BaseNPC::ClearTransientConditions()
  4052. {
  4053. // if the npc didn't use these conditions during the above call to MaintainSchedule()
  4054. // we throw them out cause we don't want them sitting around through the lifespan of a schedule
  4055. // that doesn't use them.
  4056. ClearCondition( COND_LIGHT_DAMAGE );
  4057. ClearCondition( COND_HEAVY_DAMAGE );
  4058. ClearCondition( COND_PHYSICS_DAMAGE );
  4059. ClearCondition( COND_PLAYER_PUSHING );
  4060. }
  4061. //-----------------------------------------------------------------------------
  4062. // Selecting the idle ideal state
  4063. //-----------------------------------------------------------------------------
  4064. NPC_STATE CAI_BaseNPC::SelectIdleIdealState()
  4065. {
  4066. // IDLE goes to ALERT upon hearing a sound
  4067. // IDLE goes to ALERT upon being injured
  4068. // IDLE goes to ALERT upon seeing food
  4069. // IDLE goes to COMBAT upon sighting an enemy
  4070. if ( HasCondition(COND_NEW_ENEMY) ||
  4071. HasCondition(COND_SEE_ENEMY) )
  4072. {
  4073. // new enemy! This means an idle npc has seen someone it dislikes, or
  4074. // that a npc in combat has found a more suitable target to attack
  4075. return NPC_STATE_COMBAT;
  4076. }
  4077. // Set our ideal yaw if we've taken damage
  4078. if ( HasCondition(COND_LIGHT_DAMAGE) ||
  4079. HasCondition(COND_HEAVY_DAMAGE) ||
  4080. (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) )
  4081. {
  4082. Vector vecEnemyLKP;
  4083. // Fill in where we're trying to look
  4084. if ( GetEnemy() )
  4085. {
  4086. vecEnemyLKP = GetEnemyLKP();
  4087. }
  4088. else
  4089. {
  4090. if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) )
  4091. {
  4092. vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY );
  4093. }
  4094. else
  4095. {
  4096. // Don't have an enemy, so face the direction the last attack came from (don't face north)
  4097. vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 );
  4098. }
  4099. }
  4100. // Set the ideal
  4101. GetMotor()->SetIdealYawToTarget( vecEnemyLKP );
  4102. return NPC_STATE_ALERT;
  4103. }
  4104. if ( HasCondition(COND_HEAR_DANGER) ||
  4105. HasCondition(COND_HEAR_COMBAT) ||
  4106. HasCondition(COND_HEAR_WORLD) ||
  4107. HasCondition(COND_HEAR_PLAYER) ||
  4108. HasCondition(COND_HEAR_THUMPER) ||
  4109. HasCondition(COND_HEAR_BULLET_IMPACT) )
  4110. {
  4111. // Interrupted by a sound. So make our ideal yaw the
  4112. // source of the sound!
  4113. CSound *pSound;
  4114. pSound = GetBestSound();
  4115. Assert( pSound != NULL );
  4116. if ( pSound )
  4117. {
  4118. // BRJ 1/7/04: This code used to set the ideal yaw.
  4119. // It's really side-effecty to set the yaw here.
  4120. // That is now done by the FACE_BESTSOUND schedule.
  4121. // Revert this change if it causes problems.
  4122. GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
  4123. if ( pSound->IsSoundType( SOUND_COMBAT | SOUND_DANGER | SOUND_BULLET_IMPACT ) )
  4124. {
  4125. return NPC_STATE_ALERT;
  4126. }
  4127. }
  4128. }
  4129. if ( HasInterruptCondition(COND_SMELL) )
  4130. {
  4131. return NPC_STATE_ALERT;
  4132. }
  4133. return NPC_STATE_INVALID;
  4134. }
  4135. //-----------------------------------------------------------------------------
  4136. // Selecting the alert ideal state
  4137. //-----------------------------------------------------------------------------
  4138. NPC_STATE CAI_BaseNPC::SelectAlertIdealState()
  4139. {
  4140. // ALERT goes to IDLE upon becoming bored
  4141. // ALERT goes to COMBAT upon sighting an enemy
  4142. if ( HasCondition(COND_NEW_ENEMY) ||
  4143. HasCondition(COND_SEE_ENEMY) ||
  4144. GetEnemy() != NULL )
  4145. {
  4146. return NPC_STATE_COMBAT;
  4147. }
  4148. // Set our ideal yaw if we've taken damage
  4149. if ( HasCondition(COND_LIGHT_DAMAGE) ||
  4150. HasCondition(COND_HEAVY_DAMAGE) ||
  4151. (!GetEnemy() && gpGlobals->curtime - GetEnemies()->LastTimeSeen( AI_UNKNOWN_ENEMY ) < TIME_CARE_ABOUT_DAMAGE ) )
  4152. {
  4153. Vector vecEnemyLKP;
  4154. // Fill in where we're trying to look
  4155. if ( GetEnemy() )
  4156. {
  4157. vecEnemyLKP = GetEnemyLKP();
  4158. }
  4159. else
  4160. {
  4161. if ( GetEnemies()->Find( AI_UNKNOWN_ENEMY ) )
  4162. {
  4163. vecEnemyLKP = GetEnemies()->LastKnownPosition( AI_UNKNOWN_ENEMY );
  4164. }
  4165. else
  4166. {
  4167. // Don't have an enemy, so face the direction the last attack came from (don't face north)
  4168. vecEnemyLKP = WorldSpaceCenter() + ( g_vecAttackDir * 128 );
  4169. }
  4170. }
  4171. // Set the ideal
  4172. GetMotor()->SetIdealYawToTarget( vecEnemyLKP );
  4173. return NPC_STATE_ALERT;
  4174. }
  4175. if ( HasCondition(COND_HEAR_DANGER) ||
  4176. HasCondition(COND_HEAR_COMBAT) )
  4177. {
  4178. CSound *pSound = GetBestSound();
  4179. AssertOnce( pSound != NULL );
  4180. if ( pSound )
  4181. {
  4182. GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() );
  4183. }
  4184. return NPC_STATE_ALERT;
  4185. }
  4186. if ( ShouldGoToIdleState() )
  4187. {
  4188. return NPC_STATE_IDLE;
  4189. }
  4190. return NPC_STATE_INVALID;
  4191. }
  4192. //-----------------------------------------------------------------------------
  4193. // Selecting the alert ideal state
  4194. //-----------------------------------------------------------------------------
  4195. NPC_STATE CAI_BaseNPC::SelectScriptIdealState()
  4196. {
  4197. if ( HasCondition(COND_TASK_FAILED) ||
  4198. HasCondition(COND_LIGHT_DAMAGE) ||
  4199. HasCondition(COND_HEAVY_DAMAGE) )
  4200. {
  4201. ExitScriptedSequence(); // This will set the ideal state
  4202. }
  4203. if ( m_IdealNPCState == NPC_STATE_IDLE )
  4204. {
  4205. // Exiting a script. Select the ideal state assuming we were idle now.
  4206. m_NPCState = NPC_STATE_IDLE;
  4207. NPC_STATE eIdealState = SelectIdealState();
  4208. m_NPCState = NPC_STATE_SCRIPT;
  4209. return eIdealState;
  4210. }
  4211. return NPC_STATE_INVALID;
  4212. }
  4213. //-----------------------------------------------------------------------------
  4214. // Purpose: Surveys the Conditions information available and finds the best
  4215. // new state for a npc.
  4216. //
  4217. // NOTICE the CAI_BaseNPC implementation of this function does not care about
  4218. // private conditions!
  4219. //
  4220. // Output : NPC_STATE - the suggested ideal state based on current conditions.
  4221. //-----------------------------------------------------------------------------
  4222. NPC_STATE CAI_BaseNPC::SelectIdealState( void )
  4223. {
  4224. // dvs: FIXME: lots of side effecty code in here!! this function should ONLY return an ideal state!
  4225. // ---------------------------
  4226. // Do some squad stuff first
  4227. // ---------------------------
  4228. if (m_pSquad)
  4229. {
  4230. switch( m_NPCState )
  4231. {
  4232. case NPC_STATE_IDLE:
  4233. case NPC_STATE_ALERT:
  4234. if ( HasCondition ( COND_NEW_ENEMY ) )
  4235. {
  4236. m_pSquad->SquadNewEnemy( GetEnemy() );
  4237. }
  4238. break;
  4239. }
  4240. }
  4241. // ---------------------------
  4242. // Set ideal state
  4243. // ---------------------------
  4244. switch ( m_NPCState )
  4245. {
  4246. case NPC_STATE_IDLE:
  4247. {
  4248. NPC_STATE nState = SelectIdleIdealState();
  4249. if ( nState != NPC_STATE_INVALID )
  4250. return nState;
  4251. }
  4252. break;
  4253. case NPC_STATE_ALERT:
  4254. {
  4255. NPC_STATE nState = SelectAlertIdealState();
  4256. if ( nState != NPC_STATE_INVALID )
  4257. return nState;
  4258. }
  4259. break;
  4260. case NPC_STATE_COMBAT:
  4261. // COMBAT goes to ALERT upon death of enemy
  4262. {
  4263. if ( GetEnemy() == NULL )
  4264. {
  4265. DevWarning( 2, "***Combat state with no enemy!\n" );
  4266. return NPC_STATE_ALERT;
  4267. }
  4268. break;
  4269. }
  4270. case NPC_STATE_SCRIPT:
  4271. {
  4272. NPC_STATE nState = SelectScriptIdealState();
  4273. if ( nState != NPC_STATE_INVALID )
  4274. return nState;
  4275. }
  4276. break;
  4277. case NPC_STATE_DEAD:
  4278. return NPC_STATE_DEAD;
  4279. }
  4280. // The best ideal state is the current ideal state.
  4281. return m_IdealNPCState;
  4282. }
  4283. //------------------------------------------------------------------------------
  4284. //------------------------------------------------------------------------------
  4285. void CAI_BaseNPC::GiveWeapon( string_t iszWeaponName )
  4286. {
  4287. CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(iszWeaponName) );
  4288. if ( !pWeapon )
  4289. {
  4290. Warning( "Couldn't create weapon %s to give NPC %s.\n", STRING(iszWeaponName), STRING(GetEntityName()) );
  4291. return;
  4292. }
  4293. // If I have a weapon already, drop it
  4294. if ( GetActiveWeapon() )
  4295. {
  4296. Weapon_Drop( GetActiveWeapon() );
  4297. }
  4298. // If I have a name, make my weapon match it with "_weapon" appended
  4299. if ( GetEntityName() != NULL_STRING )
  4300. {
  4301. pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", STRING(GetEntityName()) )) );
  4302. }
  4303. Weapon_Equip( pWeapon );
  4304. // Handle this case
  4305. OnGivenWeapon( pWeapon );
  4306. }
  4307. //-----------------------------------------------------------------------------
  4308. // Rather specific function that tells us if an NPC is in the process of
  4309. // moving to a weapon with the intent to pick it up.
  4310. //-----------------------------------------------------------------------------
  4311. bool CAI_BaseNPC::IsMovingToPickupWeapon()
  4312. {
  4313. return IsCurSchedule( SCHED_NEW_WEAPON );
  4314. }
  4315. //-----------------------------------------------------------------------------
  4316. //-----------------------------------------------------------------------------
  4317. bool CAI_BaseNPC::ShouldLookForBetterWeapon()
  4318. {
  4319. if( m_flNextWeaponSearchTime > gpGlobals->curtime )
  4320. return false;
  4321. if( !(CapabilitiesGet() & bits_CAP_USE_WEAPONS) )
  4322. return false;
  4323. // Already armed and currently fighting. Don't try to upgrade.
  4324. if( GetActiveWeapon() && m_NPCState == NPC_STATE_COMBAT )
  4325. return false;
  4326. if( IsMovingToPickupWeapon() )
  4327. return false;
  4328. if( !IsPlayerAlly() && GetActiveWeapon() )
  4329. return false;
  4330. if( IsInAScript() )
  4331. return false;
  4332. return true;
  4333. }
  4334. //-----------------------------------------------------------------------------
  4335. // Purpose: Check if a better weapon is available.
  4336. // For now check if there is a weapon and I don't have one. In
  4337. // the future
  4338. // UNDONE: actually rate the weapons based on there strength
  4339. // Input :
  4340. // Output :
  4341. //-----------------------------------------------------------------------------
  4342. bool CAI_BaseNPC::Weapon_IsBetterAvailable()
  4343. {
  4344. if( m_iszPendingWeapon != NULL_STRING )
  4345. {
  4346. // Some weapon is reserved for us.
  4347. return true;
  4348. }
  4349. if( ShouldLookForBetterWeapon() )
  4350. {
  4351. if( GetActiveWeapon() )
  4352. {
  4353. m_flNextWeaponSearchTime = gpGlobals->curtime + 2;
  4354. }
  4355. else
  4356. {
  4357. if( IsInPlayerSquad() )
  4358. {
  4359. // Look for a weapon frequently.
  4360. m_flNextWeaponSearchTime = gpGlobals->curtime + 1;
  4361. }
  4362. else
  4363. {
  4364. m_flNextWeaponSearchTime = gpGlobals->curtime + 2;
  4365. }
  4366. }
  4367. if ( Weapon_FindUsable( WEAPON_SEARCH_DELTA ) )
  4368. {
  4369. return true;
  4370. }
  4371. }
  4372. return false;
  4373. }
  4374. //-----------------------------------------------------------------------------
  4375. // Purpose: Returns true is weapon has a line of sight. If bSetConditions is
  4376. // true, also sets LOC conditions
  4377. // Input :
  4378. // Output :
  4379. //-----------------------------------------------------------------------------
  4380. bool CAI_BaseNPC::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
  4381. {
  4382. #if 0
  4383. // @TODO (toml 03-07-04): this code might be better (not tested)
  4384. Vector vecLocalRelativePosition;
  4385. VectorITransform( npcOwner->Weapon_ShootPosition(), npcOwner->EntityToWorldTransform(), vecLocalRelativePosition );
  4386. // Compute desired test transform
  4387. // Compute desired x axis
  4388. Vector xaxis;
  4389. VectorSubtract( targetPos, ownerPos, xaxis );
  4390. // FIXME: Insert angle test here?
  4391. float flAngle = acos( xaxis.z / xaxis.Length() );
  4392. xaxis.z = 0.0f;
  4393. float flLength = VectorNormalize( xaxis );
  4394. if ( flLength < 1e-3 )
  4395. return false;
  4396. Vector yaxis( -xaxis.y, xaxis.x, 0.0f );
  4397. matrix3x4_t losTestToWorld;
  4398. MatrixInitialize( losTestToWorld, ownerPos, xaxis, yaxis, zaxis );
  4399. Vector barrelPos;
  4400. VectorTransform( vecLocalRelativePosition, losTestToWorld, barrelPos );
  4401. #endif
  4402. bool bHaveLOS = true;
  4403. if (GetActiveWeapon())
  4404. {
  4405. bHaveLOS = GetActiveWeapon()->WeaponLOSCondition(ownerPos, targetPos, bSetConditions);
  4406. }
  4407. else if (CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1)
  4408. {
  4409. bHaveLOS = InnateWeaponLOSCondition(ownerPos, targetPos, bSetConditions);
  4410. }
  4411. else
  4412. {
  4413. if (bSetConditions)
  4414. {
  4415. SetCondition( COND_NO_WEAPON );
  4416. }
  4417. bHaveLOS = false;
  4418. }
  4419. // -------------------------------------------
  4420. // Check for friendly fire with the player
  4421. // -------------------------------------------
  4422. if ( CapabilitiesGet() & ( bits_CAP_NO_HIT_PLAYER | bits_CAP_NO_HIT_SQUADMATES ) )
  4423. {
  4424. float spread = 0.92f;
  4425. if ( GetActiveWeapon() )
  4426. {
  4427. Vector vSpread = GetAttackSpread( GetActiveWeapon() );
  4428. if ( vSpread.x > VECTOR_CONE_15DEGREES.x )
  4429. spread = FastCos( asin(vSpread.x) );
  4430. else // too much error because using point not box
  4431. spread = 0.99145f; // "15 degrees"
  4432. }
  4433. if ( CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER)
  4434. {
  4435. // Check shoot direction relative to player
  4436. if (PlayerInSpread( ownerPos, targetPos, spread, 8*12 ))
  4437. {
  4438. if (bSetConditions)
  4439. {
  4440. SetCondition( COND_WEAPON_PLAYER_IN_SPREAD );
  4441. }
  4442. bHaveLOS = false;
  4443. }
  4444. /* For grenades etc. check that player is clear?
  4445. // Check player position also
  4446. if (PlayerInRange( targetPos, 100 ))
  4447. {
  4448. SetCondition( COND_WEAPON_PLAYER_NEAR_TARGET );
  4449. }
  4450. */
  4451. }
  4452. if ( bHaveLOS )
  4453. {
  4454. if ( ( CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && m_pSquad && GetEnemy() )
  4455. {
  4456. if ( IsSquadmateInSpread( ownerPos, targetPos, spread, 8*12 ) )
  4457. {
  4458. SetCondition( COND_WEAPON_BLOCKED_BY_FRIEND );
  4459. bHaveLOS = false;
  4460. }
  4461. }
  4462. }
  4463. }
  4464. return bHaveLOS;
  4465. }
  4466. //-----------------------------------------------------------------------------
  4467. // Purpose: Check the innate weapon LOS for an owner at an arbitrary position
  4468. // If bSetConditions is true, LOS related conditions will also be set
  4469. // Input :
  4470. // Output :
  4471. //-----------------------------------------------------------------------------
  4472. bool CAI_BaseNPC::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
  4473. {
  4474. // --------------------
  4475. // Check for occlusion
  4476. // --------------------
  4477. // Base class version assumes innate weapon position is at eye level
  4478. Vector barrelPos = ownerPos + GetViewOffset();
  4479. trace_t tr;
  4480. AI_TraceLine( barrelPos, targetPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  4481. if ( tr.fraction == 1.0 )
  4482. {
  4483. return true;
  4484. }
  4485. CBaseEntity *pHitEntity = tr.m_pEnt;
  4486. // Translate a hit vehicle into its passenger if found
  4487. if ( GetEnemy() != NULL )
  4488. {
  4489. CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
  4490. if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
  4491. {
  4492. // Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is
  4493. // Also, check to see if the owner of the entity is the vehicle, in which case it's valid too.
  4494. // This catches vehicles that use bone followers.
  4495. CBaseEntity *pVehicleEnt = pCCEnemy->GetVehicleEntity();
  4496. if ( pHitEntity == pVehicleEnt || pHitEntity->GetOwnerEntity() == pVehicleEnt )
  4497. return true;
  4498. }
  4499. }
  4500. if ( pHitEntity == GetEnemy() )
  4501. {
  4502. return true;
  4503. }
  4504. else if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() )
  4505. {
  4506. if (IRelationType( pHitEntity ) == D_HT)
  4507. {
  4508. return true;
  4509. }
  4510. else if (bSetConditions)
  4511. {
  4512. SetCondition(COND_WEAPON_BLOCKED_BY_FRIEND);
  4513. }
  4514. }
  4515. else if (bSetConditions)
  4516. {
  4517. SetCondition(COND_WEAPON_SIGHT_OCCLUDED);
  4518. SetEnemyOccluder(tr.m_pEnt);
  4519. }
  4520. return false;
  4521. }
  4522. //=========================================================
  4523. // CanCheckAttacks - prequalifies a npc to do more fine
  4524. // checking of potential attacks.
  4525. //=========================================================
  4526. bool CAI_BaseNPC::FCanCheckAttacks( void )
  4527. {
  4528. // Not allowed to check attacks while climbing or jumping
  4529. // Otherwise schedule is interrupted while on ladder/etc
  4530. // which is NOT a legal place to attack from
  4531. if ( GetNavType() == NAV_CLIMB || GetNavType() == NAV_JUMP )
  4532. return false;
  4533. if ( HasCondition(COND_SEE_ENEMY) && !HasCondition( COND_ENEMY_TOO_FAR))
  4534. {
  4535. return true;
  4536. }
  4537. return false;
  4538. }
  4539. //-----------------------------------------------------------------------------
  4540. // Purpose: Return dist. to enemy (closest of origin/head/feet)
  4541. // Input :
  4542. // Output :
  4543. //-----------------------------------------------------------------------------
  4544. float CAI_BaseNPC::EnemyDistance( CBaseEntity *pEnemy )
  4545. {
  4546. Vector enemyDelta = pEnemy->WorldSpaceCenter() - WorldSpaceCenter();
  4547. // NOTE: We ignore rotation for computing height. Assume it isn't an effect
  4548. // we care about, so we simply use OBBSize().z for height.
  4549. // Otherwise you'd do this:
  4550. // pEnemy->CollisionProp()->WorldSpaceSurroundingBounds( &enemyMins, &enemyMaxs );
  4551. // float enemyHeight = enemyMaxs.z - enemyMins.z;
  4552. float enemyHeight = pEnemy->CollisionProp()->OBBSize().z;
  4553. float myHeight = CollisionProp()->OBBSize().z;
  4554. // max distance our centers can be apart with the boxes still overlapping
  4555. float flMaxZDist = ( enemyHeight + myHeight ) * 0.5f;
  4556. // see if the enemy is closer to my head, feet or in between
  4557. if ( enemyDelta.z > flMaxZDist )
  4558. {
  4559. // enemy feet above my head, compute distance from my head to his feet
  4560. enemyDelta.z -= flMaxZDist;
  4561. }
  4562. else if ( enemyDelta.z < -flMaxZDist )
  4563. {
  4564. // enemy head below my feet, return distance between my feet and his head
  4565. enemyDelta.z += flMaxZDist;
  4566. }
  4567. else
  4568. {
  4569. // boxes overlap in Z, no delta
  4570. enemyDelta.z = 0;
  4571. }
  4572. return enemyDelta.Length();
  4573. }
  4574. //-----------------------------------------------------------------------------
  4575. float CAI_BaseNPC::GetReactionDelay( CBaseEntity *pEnemy )
  4576. {
  4577. return ( m_NPCState == NPC_STATE_ALERT || m_NPCState == NPC_STATE_COMBAT ) ?
  4578. ai_reaction_delay_alert.GetFloat() :
  4579. ai_reaction_delay_idle.GetFloat();
  4580. }
  4581. //-----------------------------------------------------------------------------
  4582. // Purpose: Update information on my enemy
  4583. // Input :
  4584. // Output : Returns true is new enemy, false is known enemy
  4585. //-----------------------------------------------------------------------------
  4586. bool CAI_BaseNPC::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
  4587. {
  4588. bool firstHand = ( pInformer == NULL || pInformer == this );
  4589. AI_PROFILE_SCOPE(CAI_BaseNPC_UpdateEnemyMemory);
  4590. if ( GetEnemies() )
  4591. {
  4592. // If the was eluding me and allow the NPC to play a sound
  4593. if (GetEnemies()->HasEludedMe(pEnemy))
  4594. {
  4595. FoundEnemySound();
  4596. }
  4597. float reactionDelay = ( !pInformer || pInformer == this ) ? GetReactionDelay( pEnemy ) : 0.0;
  4598. bool result = GetEnemies()->UpdateMemory(GetNavigator()->GetNetwork(), pEnemy, position, reactionDelay, firstHand);
  4599. if ( !firstHand && pEnemy && result && GetState() == NPC_STATE_IDLE ) // if it's a new potential enemy
  4600. ForceDecisionThink();
  4601. if ( firstHand && pEnemy && m_pSquad )
  4602. {
  4603. m_pSquad->UpdateEnemyMemory( this, pEnemy, position );
  4604. }
  4605. return result;
  4606. }
  4607. return true;
  4608. }
  4609. //-----------------------------------------------------------------------------
  4610. // Purpose: Remembers the thing my enemy is hiding behind. Called when either
  4611. // COND_ENEMY_OCCLUDED or COND_WEAPON_SIGHT_OCCLUDED is set.
  4612. //-----------------------------------------------------------------------------
  4613. void CAI_BaseNPC::SetEnemyOccluder(CBaseEntity *pBlocker)
  4614. {
  4615. m_hEnemyOccluder = pBlocker;
  4616. }
  4617. //-----------------------------------------------------------------------------
  4618. // Purpose: Gets the thing my enemy is hiding behind (assuming they are hiding).
  4619. //-----------------------------------------------------------------------------
  4620. CBaseEntity *CAI_BaseNPC::GetEnemyOccluder(void)
  4621. {
  4622. return m_hEnemyOccluder.Get();
  4623. }
  4624. //-----------------------------------------------------------------------------
  4625. // Purpose: part of the Condition collection process
  4626. // gets and stores data and conditions pertaining to a npc's
  4627. // enemy.
  4628. // @TODO (toml 07-27-03): this should become subservient to the senses. right
  4629. // now, it yields different result
  4630. // Input :
  4631. // Output :
  4632. //-----------------------------------------------------------------------------
  4633. void CAI_BaseNPC::GatherEnemyConditions( CBaseEntity *pEnemy )
  4634. {
  4635. AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions);
  4636. ClearCondition( COND_ENEMY_FACING_ME );
  4637. ClearCondition( COND_BEHIND_ENEMY );
  4638. // ---------------------------
  4639. // Set visibility conditions
  4640. // ---------------------------
  4641. if ( HasCondition( COND_NEW_ENEMY ) || GetSenses()->GetTimeLastUpdate( GetEnemy() ) == gpGlobals->curtime )
  4642. {
  4643. AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_Visibility);
  4644. ClearCondition( COND_HAVE_ENEMY_LOS );
  4645. ClearCondition( COND_ENEMY_OCCLUDED );
  4646. CBaseEntity *pBlocker = NULL;
  4647. SetEnemyOccluder(NULL);
  4648. bool bSensesDidSee = GetSenses()->DidSeeEntity( pEnemy );
  4649. if ( !bSensesDidSee && ( ( EnemyDistance( pEnemy ) >= GetSenses()->GetDistLook() ) || !FVisible( pEnemy, MASK_BLOCKLOS, &pBlocker ) ) )
  4650. {
  4651. // No LOS to enemy
  4652. SetEnemyOccluder(pBlocker);
  4653. SetCondition( COND_ENEMY_OCCLUDED );
  4654. ClearCondition( COND_SEE_ENEMY );
  4655. if (HasMemory( bits_MEMORY_HAD_LOS ))
  4656. {
  4657. AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs);
  4658. // Send output event
  4659. if (GetEnemy()->IsPlayer())
  4660. {
  4661. m_OnLostPlayerLOS.FireOutput( GetEnemy(), this );
  4662. }
  4663. m_OnLostEnemyLOS.FireOutput( GetEnemy(), this );
  4664. }
  4665. Forget( bits_MEMORY_HAD_LOS );
  4666. }
  4667. else
  4668. {
  4669. // Have LOS but may not be in view cone
  4670. SetCondition( COND_HAVE_ENEMY_LOS );
  4671. if ( bSensesDidSee )
  4672. {
  4673. // Have LOS and in view cone
  4674. SetCondition( COND_SEE_ENEMY );
  4675. }
  4676. else
  4677. {
  4678. ClearCondition( COND_SEE_ENEMY );
  4679. }
  4680. if (!HasMemory( bits_MEMORY_HAD_LOS ))
  4681. {
  4682. AI_PROFILE_SCOPE(CAI_BaseNPC_GatherEnemyConditions_Outputs);
  4683. // Send output event
  4684. EHANDLE hEnemy;
  4685. hEnemy.Set( GetEnemy() );
  4686. if (GetEnemy()->IsPlayer())
  4687. {
  4688. m_OnFoundPlayer.Set(hEnemy, this, this);
  4689. m_OnFoundEnemy.Set(hEnemy, this, this);
  4690. }
  4691. else
  4692. {
  4693. m_OnFoundEnemy.Set(hEnemy, this, this);
  4694. }
  4695. }
  4696. Remember( bits_MEMORY_HAD_LOS );
  4697. }
  4698. AI_PROFILE_SCOPE_END();
  4699. }
  4700. // -------------------
  4701. // If enemy is dead
  4702. // -------------------
  4703. if ( !pEnemy->IsAlive() )
  4704. {
  4705. SetCondition( COND_ENEMY_DEAD );
  4706. ClearCondition( COND_SEE_ENEMY );
  4707. ClearCondition( COND_ENEMY_OCCLUDED );
  4708. return;
  4709. }
  4710. float flDistToEnemy = EnemyDistance(pEnemy);
  4711. AI_PROFILE_SCOPE_BEGIN(CAI_BaseNPC_GatherEnemyConditions_SeeEnemy);
  4712. if ( HasCondition( COND_SEE_ENEMY ) )
  4713. {
  4714. // Trail the enemy a bit if he's moving
  4715. if (pEnemy->GetSmoothedVelocity() != vec3_origin)
  4716. {
  4717. Vector vTrailPos = pEnemy->GetAbsOrigin() - pEnemy->GetSmoothedVelocity() * random->RandomFloat( -0.05, 0 );
  4718. UpdateEnemyMemory(pEnemy,vTrailPos);
  4719. }
  4720. else
  4721. {
  4722. UpdateEnemyMemory(pEnemy,pEnemy->GetAbsOrigin());
  4723. }
  4724. // If it's not an NPC, assume it can't see me
  4725. if ( pEnemy->MyCombatCharacterPointer() && pEnemy->MyCombatCharacterPointer()->FInViewCone ( this ) )
  4726. {
  4727. SetCondition ( COND_ENEMY_FACING_ME );
  4728. ClearCondition ( COND_BEHIND_ENEMY );
  4729. }
  4730. else
  4731. {
  4732. ClearCondition( COND_ENEMY_FACING_ME );
  4733. SetCondition ( COND_BEHIND_ENEMY );
  4734. }
  4735. }
  4736. else if ( (!HasCondition(COND_ENEMY_OCCLUDED) && !HasCondition(COND_SEE_ENEMY)) && ( flDistToEnemy <= 256 ) )
  4737. {
  4738. // if the enemy is not occluded, and unseen, that means it is behind or beside the npc.
  4739. // if the enemy is near enough the npc, we go ahead and let the npc know where the
  4740. // enemy is. Send the enemy in as the informer so this knowledge will be regarded as
  4741. // secondhand so that the NPC doesn't
  4742. UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), pEnemy );
  4743. }
  4744. AI_PROFILE_SCOPE_END();
  4745. float tooFar = m_flDistTooFar;
  4746. if ( GetActiveWeapon() && HasCondition(COND_SEE_ENEMY) )
  4747. {
  4748. tooFar = MAX( m_flDistTooFar, GetActiveWeapon()->m_fMaxRange1 );
  4749. }
  4750. if ( flDistToEnemy >= tooFar )
  4751. {
  4752. // enemy is very far away from npc
  4753. SetCondition( COND_ENEMY_TOO_FAR );
  4754. }
  4755. else
  4756. {
  4757. ClearCondition( COND_ENEMY_TOO_FAR );
  4758. }
  4759. if ( FCanCheckAttacks() )
  4760. {
  4761. // This may also call SetEnemyOccluder!
  4762. GatherAttackConditions( GetEnemy(), flDistToEnemy );
  4763. }
  4764. else
  4765. {
  4766. ClearAttackConditions();
  4767. }
  4768. // If my enemy has moved significantly, or if the enemy has changed update my path
  4769. UpdateEnemyPos();
  4770. // If my target entity has moved significantly, update my path
  4771. // This is an odd place to put this, but where else should it go?
  4772. UpdateTargetPos();
  4773. // ----------------------------------------------------------------------------
  4774. // Check if enemy is reachable via the node graph unless I'm not on a network
  4775. // ----------------------------------------------------------------------------
  4776. if (GetNavigator()->IsOnNetwork())
  4777. {
  4778. // Note that unreachablity times out
  4779. if (IsUnreachable(GetEnemy()))
  4780. {
  4781. SetCondition(COND_ENEMY_UNREACHABLE);
  4782. }
  4783. }
  4784. //-----------------------------------------------------------------------
  4785. // If I haven't seen the enemy in a while he may have eluded me
  4786. //-----------------------------------------------------------------------
  4787. if (gpGlobals->curtime - GetEnemyLastTimeSeen() > 8)
  4788. {
  4789. //-----------------------------------------------------------------------
  4790. // I'm at last known position at enemy isn't in sight then has eluded me
  4791. // ----------------------------------------------------------------------
  4792. Vector flEnemyLKP = GetEnemyLKP();
  4793. if (((flEnemyLKP - GetAbsOrigin()).Length2D() < 48) &&
  4794. !HasCondition(COND_SEE_ENEMY))
  4795. {
  4796. MarkEnemyAsEluded();
  4797. }
  4798. //-------------------------------------------------------------------
  4799. // If enemy isn't reachable, I can see last known position and enemy
  4800. // isn't there, then he has eluded me
  4801. // ------------------------------------------------------------------
  4802. if (!HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_UNREACHABLE))
  4803. {
  4804. if ( !FVisible( flEnemyLKP ) )
  4805. {
  4806. MarkEnemyAsEluded();
  4807. }
  4808. }
  4809. }
  4810. }
  4811. //-----------------------------------------------------------------------------
  4812. // In the case of goaltype enemy, update the goal position
  4813. //-----------------------------------------------------------------------------
  4814. float CAI_BaseNPC::GetGoalRepathTolerance( CBaseEntity *pGoalEnt, GoalType_t type, const Vector &curGoal, const Vector &curTargetPos )
  4815. {
  4816. float distToGoal = ( GetAbsOrigin() - curTargetPos ).Length() - GetNavigator()->GetArrivalDistance();
  4817. float distMoved1Sec = GetSmoothedVelocity().Length();
  4818. float result = 120; // FIXME: why 120?
  4819. if (distMoved1Sec > 0.0)
  4820. {
  4821. float t = distToGoal / distMoved1Sec;
  4822. result = clamp( 120.f * t, 0.f, 120.f );
  4823. // Msg("t %.2f : d %.0f (%.0f)\n", t, result, distMoved1Sec );
  4824. }
  4825. if ( !pGoalEnt->IsPlayer() )
  4826. result *= 1.20;
  4827. return result;
  4828. }
  4829. //-----------------------------------------------------------------------------
  4830. // In the case of goaltype enemy, update the goal position
  4831. //-----------------------------------------------------------------------------
  4832. void CAI_BaseNPC::UpdateEnemyPos()
  4833. {
  4834. // Don't perform path recomputations during a climb or a jump
  4835. if ( !GetNavigator()->IsInterruptable() )
  4836. return;
  4837. if ( m_AnyUpdateEnemyPosTimer.Expired() && m_UpdateEnemyPosTimer.Expired() )
  4838. {
  4839. // FIXME: does GetGoalRepathTolerance() limit re-routing enough to remove this?
  4840. // m_UpdateEnemyPosTimer.Set( 0.5, 1.0 );
  4841. // If my enemy has moved significantly, or if the enemy has changed update my path
  4842. if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY )
  4843. {
  4844. if (m_hEnemy != GetNavigator()->GetGoalTarget())
  4845. {
  4846. GetNavigator()->SetGoalTarget( m_hEnemy, vec3_origin );
  4847. }
  4848. else
  4849. {
  4850. Vector vEnemyLKP = GetEnemyLKP();
  4851. TranslateNavGoal( GetEnemy(), vEnemyLKP );
  4852. float tolerance = GetGoalRepathTolerance( GetEnemy(), GOALTYPE_ENEMY, GetNavigator()->GetGoalPos(), vEnemyLKP);
  4853. if ( (GetNavigator()->GetGoalPos() - vEnemyLKP).Length() > tolerance )
  4854. {
  4855. // 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?
  4856. m_AnyUpdateEnemyPosTimer.Set( 0.1 ); // FIXME: what's a reasonable interval?
  4857. if ( !GetNavigator()->RefindPathToGoal( false ) )
  4858. {
  4859. TaskFail( FAIL_NO_ROUTE );
  4860. }
  4861. }
  4862. }
  4863. }
  4864. }
  4865. }
  4866. //-----------------------------------------------------------------------------
  4867. // In the case of goaltype targetent, update the goal position
  4868. //-----------------------------------------------------------------------------
  4869. void CAI_BaseNPC::UpdateTargetPos()
  4870. {
  4871. // BRJ 10/7/02
  4872. // FIXME: make this check time based instead of distance based!
  4873. // Don't perform path recomputations during a climb or a jump
  4874. if ( !GetNavigator()->IsInterruptable() )
  4875. return;
  4876. // If my target entity has moved significantly, or has changed, update my path
  4877. // This is an odd place to put this, but where else should it go?
  4878. if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT )
  4879. {
  4880. if (m_hTargetEnt != GetNavigator()->GetGoalTarget())
  4881. {
  4882. GetNavigator()->SetGoalTarget( m_hTargetEnt, vec3_origin );
  4883. }
  4884. else if ( GetNavigator()->GetGoalFlags() & AIN_UPDATE_TARGET_POS )
  4885. {
  4886. if ( GetTarget() == NULL || (GetNavigator()->GetGoalPos() - GetTarget()->GetAbsOrigin()).Length() > GetGoalRepathTolerance( GetTarget(), GOALTYPE_TARGETENT, GetNavigator()->GetGoalPos(), GetTarget()->GetAbsOrigin()) )
  4887. {
  4888. if ( !GetNavigator()->RefindPathToGoal( false ) )
  4889. {
  4890. TaskFail( FAIL_NO_ROUTE );
  4891. }
  4892. }
  4893. }
  4894. }
  4895. }
  4896. //-----------------------------------------------------------------------------
  4897. // Purpose: part of the Condition collection process
  4898. // gets and stores data and conditions pertaining to a npc's
  4899. // enemy.
  4900. // Input :
  4901. // Output :
  4902. //-----------------------------------------------------------------------------
  4903. void CAI_BaseNPC::CheckTarget( CBaseEntity *pTarget )
  4904. {
  4905. AI_PROFILE_SCOPE(CAI_Enemies_CheckTarget);
  4906. ClearCondition ( COND_HAVE_TARGET_LOS );
  4907. ClearCondition ( COND_TARGET_OCCLUDED );
  4908. // ---------------------------
  4909. // Set visibility conditions
  4910. // ---------------------------
  4911. if ( ( EnemyDistance( pTarget ) >= GetSenses()->GetDistLook() ) || !FVisible( pTarget ) )
  4912. {
  4913. // No LOS to target
  4914. SetCondition( COND_TARGET_OCCLUDED );
  4915. }
  4916. else
  4917. {
  4918. // Have LOS (may not be in view cone)
  4919. SetCondition( COND_HAVE_TARGET_LOS );
  4920. }
  4921. UpdateTargetPos();
  4922. }
  4923. //-----------------------------------------------------------------------------
  4924. // Purpose: Creates a bullseye of limited lifespan at the provided position
  4925. // Input : vecOrigin - Where to create the bullseye
  4926. // duration - The lifespan of the bullseye
  4927. // Output : A BaseNPC pointer to the bullseye
  4928. //
  4929. // NOTES : It is the caller's responsibility to set up relationships with
  4930. // this bullseye!
  4931. //-----------------------------------------------------------------------------
  4932. CAI_BaseNPC *CAI_BaseNPC::CreateCustomTarget( const Vector &vecOrigin, float duration )
  4933. {
  4934. #ifdef HL2_DLL
  4935. CNPC_Bullseye *pTarget = (CNPC_Bullseye*)CreateEntityByName( "npc_bullseye" );
  4936. ASSERT( pTarget != NULL );
  4937. // Build a nonsolid bullseye and place it in the desired location
  4938. // The bullseye must take damage or the SetHealth 0 call will not be able
  4939. pTarget->AddSpawnFlags( SF_BULLSEYE_NONSOLID );
  4940. pTarget->SetAbsOrigin( vecOrigin );
  4941. pTarget->Spawn();
  4942. // Set it up to remove itself, unless told to be infinite (-1)
  4943. if( duration > -1 )
  4944. {
  4945. variant_t value;
  4946. value.SetFloat(0);
  4947. g_EventQueue.AddEvent( pTarget, "SetHealth", value, duration, this, this );
  4948. }
  4949. return pTarget;
  4950. #else
  4951. return NULL;
  4952. #endif// HL2_DLL
  4953. }
  4954. //-----------------------------------------------------------------------------
  4955. // Purpose:
  4956. // Input : eNewActivity -
  4957. // Output : Activity
  4958. //-----------------------------------------------------------------------------
  4959. Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity )
  4960. {
  4961. Assert( eNewActivity != ACT_INVALID );
  4962. if (eNewActivity == ACT_RANGE_ATTACK1)
  4963. {
  4964. if ( IsCrouching() )
  4965. {
  4966. eNewActivity = ACT_RANGE_ATTACK1_LOW;
  4967. }
  4968. }
  4969. else if (eNewActivity == ACT_RELOAD)
  4970. {
  4971. if (IsCrouching())
  4972. {
  4973. eNewActivity = ACT_RELOAD_LOW;
  4974. }
  4975. }
  4976. else if ( eNewActivity == ACT_IDLE )
  4977. {
  4978. if ( IsCrouching() )
  4979. {
  4980. eNewActivity = ACT_CROUCHIDLE;
  4981. }
  4982. }
  4983. // ====
  4984. // HACK : LEIPZIG 06 - The underlying problem is that the AR2 and SMG1 cannot map IDLE_ANGRY to a crouched equivalent automatically
  4985. // which causes the character to pop up and down in their idle state of firing while crouched. -- jdw
  4986. else if ( eNewActivity == ACT_IDLE_ANGRY_SMG1 )
  4987. {
  4988. if ( IsCrouching() )
  4989. {
  4990. eNewActivity = ACT_RANGE_AIM_LOW;
  4991. }
  4992. }
  4993. // ====
  4994. if (CapabilitiesGet() & bits_CAP_DUCK)
  4995. {
  4996. if (eNewActivity == ACT_RELOAD)
  4997. {
  4998. return GetReloadActivity(GetHintNode());
  4999. }
  5000. else if ((eNewActivity == ACT_COVER ) ||
  5001. (eNewActivity == ACT_IDLE && HasMemory(bits_MEMORY_INCOVER)))
  5002. {
  5003. Activity nCoverActivity = GetCoverActivity(GetHintNode());
  5004. // ---------------------------------------------------------------
  5005. // Some NPCs don't have a cover activity defined so just use idle
  5006. // ---------------------------------------------------------------
  5007. if (SelectWeightedSequence( nCoverActivity ) == ACTIVITY_NOT_AVAILABLE)
  5008. {
  5009. nCoverActivity = ACT_IDLE;
  5010. }
  5011. return nCoverActivity;
  5012. }
  5013. }
  5014. return eNewActivity;
  5015. }
  5016. //-----------------------------------------------------------------------------
  5017. Activity CAI_BaseNPC::TranslateActivity( Activity idealActivity, Activity *pIdealWeaponActivity )
  5018. {
  5019. const int MAX_TRIES = 5;
  5020. int count = 0;
  5021. bool bIdealWeaponRequired = false;
  5022. Activity idealWeaponActivity;
  5023. Activity baseTranslation;
  5024. bool bWeaponRequired = false;
  5025. Activity weaponTranslation;
  5026. Activity last;
  5027. Activity current;
  5028. idealWeaponActivity = Weapon_TranslateActivity( idealActivity, &bIdealWeaponRequired );
  5029. if ( pIdealWeaponActivity )
  5030. *pIdealWeaponActivity = idealWeaponActivity;
  5031. baseTranslation = idealActivity;
  5032. weaponTranslation = idealActivity;
  5033. last = idealActivity;
  5034. while ( count++ < MAX_TRIES )
  5035. {
  5036. current = NPC_TranslateActivity( last );
  5037. if ( current != last )
  5038. baseTranslation = current;
  5039. weaponTranslation = Weapon_TranslateActivity( current, &bWeaponRequired );
  5040. if ( weaponTranslation == last )
  5041. break;
  5042. last = weaponTranslation;
  5043. }
  5044. AssertMsg( count < MAX_TRIES, "Circular activity translation!" );
  5045. if ( last == ACT_SCRIPT_CUSTOM_MOVE )
  5046. return ACT_SCRIPT_CUSTOM_MOVE;
  5047. if ( HaveSequenceForActivity( weaponTranslation ) )
  5048. return weaponTranslation;
  5049. if ( bWeaponRequired )
  5050. {
  5051. // only complain about an activity once
  5052. static CUtlVector< Activity > sUniqueActivities;
  5053. if (!sUniqueActivities.Find( weaponTranslation))
  5054. {
  5055. // FIXME: warning
  5056. DevWarning( "%s missing activity \"%s\" needed by weapon\"%s\"\n",
  5057. GetClassname(), GetActivityName( weaponTranslation ), GetActiveWeapon()->GetClassname() );
  5058. sUniqueActivities.AddToTail( weaponTranslation );
  5059. }
  5060. }
  5061. if ( baseTranslation != weaponTranslation && HaveSequenceForActivity( baseTranslation ) )
  5062. return baseTranslation;
  5063. if ( idealWeaponActivity != baseTranslation && HaveSequenceForActivity( idealWeaponActivity ) )
  5064. return idealActivity;
  5065. if ( idealActivity != idealWeaponActivity && HaveSequenceForActivity( idealActivity ) )
  5066. return idealActivity;
  5067. Assert( !HaveSequenceForActivity( idealActivity ) );
  5068. if ( idealActivity == ACT_RUN )
  5069. {
  5070. idealActivity = ACT_WALK;
  5071. }
  5072. else if ( idealActivity == ACT_WALK )
  5073. {
  5074. idealActivity = ACT_RUN;
  5075. }
  5076. return idealActivity;
  5077. }
  5078. //-----------------------------------------------------------------------------
  5079. // Purpose:
  5080. // Input : NewActivity -
  5081. // iSequence -
  5082. // translatedActivity -
  5083. // weaponActivity -
  5084. //-----------------------------------------------------------------------------
  5085. void CAI_BaseNPC::ResolveActivityToSequence(Activity NewActivity, int &iSequence, Activity &translatedActivity, Activity &weaponActivity)
  5086. {
  5087. AI_PROFILE_SCOPE( CAI_BaseNPC_ResolveActivityToSequence );
  5088. iSequence = ACTIVITY_NOT_AVAILABLE;
  5089. translatedActivity = TranslateActivity( NewActivity, &weaponActivity );
  5090. if ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE )
  5091. {
  5092. iSequence = GetScriptCustomMoveSequence();
  5093. }
  5094. else
  5095. {
  5096. iSequence = SelectWeightedSequence( translatedActivity );
  5097. if ( iSequence == ACTIVITY_NOT_AVAILABLE )
  5098. {
  5099. static CAI_BaseNPC *pLastWarn;
  5100. static Activity lastWarnActivity;
  5101. static float timeLastWarn;
  5102. if ( ( pLastWarn != this && lastWarnActivity != translatedActivity ) || gpGlobals->curtime - timeLastWarn > 5.0 )
  5103. {
  5104. DevWarning( "%s:%s:%s has no sequence for act:%s\n", GetClassname(), GetDebugName(), STRING( GetModelName() ), ActivityList_NameForIndex(translatedActivity) );
  5105. pLastWarn = this;
  5106. lastWarnActivity = translatedActivity;
  5107. timeLastWarn = gpGlobals->curtime;
  5108. }
  5109. if ( translatedActivity == ACT_RUN )
  5110. {
  5111. translatedActivity = ACT_WALK;
  5112. iSequence = SelectWeightedSequence( translatedActivity );
  5113. }
  5114. }
  5115. }
  5116. if ( iSequence == ACT_INVALID )
  5117. {
  5118. // Abject failure. Use sequence zero.
  5119. iSequence = 0;
  5120. }
  5121. }
  5122. //-----------------------------------------------------------------------------
  5123. // Purpose:
  5124. // Input : NewActivity -
  5125. // iSequence -
  5126. // translatedActivity -
  5127. // weaponActivity -
  5128. //-----------------------------------------------------------------------------
  5129. extern ConVar ai_sequence_debug;
  5130. void CAI_BaseNPC::SetActivityAndSequence(Activity NewActivity, int iSequence, Activity translatedActivity, Activity weaponActivity)
  5131. {
  5132. m_translatedActivity = translatedActivity;
  5133. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5134. {
  5135. DevMsg("SetActivityAndSequence : %s: %s:%s -> %s:%s / %s:%s\n", GetClassname(),
  5136. GetActivityName(GetActivity()), GetSequenceName(GetSequence()),
  5137. GetActivityName(NewActivity), GetSequenceName(iSequence),
  5138. GetActivityName(translatedActivity), GetActivityName(weaponActivity) );
  5139. }
  5140. // Set to the desired anim, or default anim if the desired is not present
  5141. if ( iSequence > ACTIVITY_NOT_AVAILABLE )
  5142. {
  5143. if ( GetSequence() != iSequence || !SequenceLoops() )
  5144. {
  5145. //
  5146. // Don't reset frame between movement phased animations
  5147. if (!IsActivityMovementPhased( m_Activity ) ||
  5148. !IsActivityMovementPhased( NewActivity ))
  5149. {
  5150. SetCycle( 0 );
  5151. }
  5152. }
  5153. ResetSequence( iSequence );
  5154. Weapon_SetActivity( weaponActivity, SequenceDuration( iSequence ) );
  5155. }
  5156. else
  5157. {
  5158. // Not available try to get default anim
  5159. ResetSequence( 0 );
  5160. }
  5161. // Set the view position based on the current activity
  5162. SetViewOffset( EyeOffset(m_translatedActivity) );
  5163. if (m_Activity != NewActivity)
  5164. {
  5165. OnChangeActivity(NewActivity);
  5166. }
  5167. // NOTE: We DO NOT write the translated activity here.
  5168. // This is to abstract the activity translation from the AI code.
  5169. // As far as the code is concerned, a translation is merely a new set of sequences
  5170. // that should be regarded as the activity in question.
  5171. // Go ahead and set this so it doesn't keep trying when the anim is not present
  5172. m_Activity = NewActivity;
  5173. // this cannot be called until m_Activity stores NewActivity!
  5174. GetMotor()->RecalculateYawSpeed();
  5175. }
  5176. //-----------------------------------------------------------------------------
  5177. // Purpose: Sets the activity to the desired activity immediately, skipping any
  5178. // transition sequences.
  5179. // Input : NewActivity -
  5180. //-----------------------------------------------------------------------------
  5181. void CAI_BaseNPC::SetActivity( Activity NewActivity )
  5182. {
  5183. // If I'm already doing the NewActivity I can bail.
  5184. // FIXME: Should this be based on the current translated activity and ideal translated activity (calculated below)?
  5185. // The old code only cared about the logical activity, not translated.
  5186. if (m_Activity == NewActivity)
  5187. {
  5188. return;
  5189. }
  5190. // Don't do this if I'm playing a transition, unless it's ACT_RESET.
  5191. if ( NewActivity != ACT_RESET && m_Activity == ACT_TRANSITION && m_IdealActivity != ACT_DO_NOT_DISTURB )
  5192. {
  5193. return;
  5194. }
  5195. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5196. {
  5197. DevMsg("SetActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity));
  5198. }
  5199. if ( !GetModelPtr() )
  5200. return;
  5201. // In case someone calls this with something other than the ideal activity.
  5202. m_IdealActivity = NewActivity;
  5203. // Resolve to ideals and apply directly, skipping transitions.
  5204. ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5205. //DevMsg("%s: SLAM %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
  5206. SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5207. }
  5208. //-----------------------------------------------------------------------------
  5209. // Purpose: Sets the activity that we would like to transition toward.
  5210. // Input : NewActivity -
  5211. //-----------------------------------------------------------------------------
  5212. void CAI_BaseNPC::SetIdealActivity( Activity NewActivity )
  5213. {
  5214. // ignore if it's an ACT_TRANSITION, it means somewhere we're setting IdealActivity with a bogus intermediate value
  5215. if (NewActivity == ACT_TRANSITION)
  5216. {
  5217. Assert( 0 );
  5218. return;
  5219. }
  5220. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5221. {
  5222. DevMsg("SetIdealActivity : %s: %s -> %s\n", GetClassname(), GetActivityName(GetActivity()), GetActivityName(NewActivity));
  5223. }
  5224. if (NewActivity == ACT_RESET)
  5225. {
  5226. // They probably meant to call SetActivity(ACT_RESET)... we'll fix it for them.
  5227. SetActivity(ACT_RESET);
  5228. return;
  5229. }
  5230. m_IdealActivity = NewActivity;
  5231. if( NewActivity == ACT_DO_NOT_DISTURB )
  5232. {
  5233. // Don't resolve anything! Leave it the way the user has it right now.
  5234. return;
  5235. }
  5236. if ( !GetModelPtr() )
  5237. return;
  5238. // Perform translation in case we need to change sequences within a single activity,
  5239. // such as between a standing idle and a crouching idle.
  5240. ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5241. }
  5242. //-----------------------------------------------------------------------------
  5243. // Purpose: Moves toward the ideal activity through any transition sequences.
  5244. //-----------------------------------------------------------------------------
  5245. void CAI_BaseNPC::AdvanceToIdealActivity(void)
  5246. {
  5247. // If there is a transition sequence between the current sequence and the ideal sequence...
  5248. int nNextSequence = FindTransitionSequence(GetSequence(), m_nIdealSequence, NULL);
  5249. if (nNextSequence != -1)
  5250. {
  5251. // We found a transition sequence or possibly went straight to
  5252. // the ideal sequence.
  5253. if (nNextSequence != m_nIdealSequence)
  5254. {
  5255. // DevMsg("%s: TRANSITION %s -> %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(nNextSequence), GetSequenceName(m_nIdealSequence));
  5256. Activity eWeaponActivity = ACT_TRANSITION;
  5257. Activity eTranslatedActivity = ACT_TRANSITION;
  5258. // Figure out if the transition sequence has an associated activity that
  5259. // we can use for our weapon. Do activity translation also.
  5260. Activity eTransitionActivity = GetSequenceActivity(nNextSequence);
  5261. if (eTransitionActivity != ACT_INVALID)
  5262. {
  5263. int nDiscard;
  5264. ResolveActivityToSequence(eTransitionActivity, nDiscard, eTranslatedActivity, eWeaponActivity);
  5265. }
  5266. // Set activity and sequence to the transition stuff. Set the activity to ACT_TRANSITION
  5267. // so we know we're in a transition.
  5268. SetActivityAndSequence(ACT_TRANSITION, nNextSequence, eTranslatedActivity, eWeaponActivity);
  5269. }
  5270. else
  5271. {
  5272. //DevMsg("%s: IDEAL %s -> %s\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
  5273. // Set activity and sequence to the ideal stuff that was set up in MaintainActivity.
  5274. SetActivityAndSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5275. }
  5276. }
  5277. // Else go straight there to the ideal activity.
  5278. else
  5279. {
  5280. //DevMsg("%s: Unable to get from sequence %s to %s!\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence));
  5281. SetActivity(m_IdealActivity);
  5282. }
  5283. }
  5284. //-----------------------------------------------------------------------------
  5285. // Purpose: Tries to achieve our ideal animation state, playing any transition
  5286. // sequences that we need to play to get there.
  5287. //-----------------------------------------------------------------------------
  5288. void CAI_BaseNPC::MaintainActivity(void)
  5289. {
  5290. AI_PROFILE_SCOPE( CAI_BaseNPC_MaintainActivity );
  5291. if ( m_lifeState == LIFE_DEAD )
  5292. {
  5293. // Don't maintain activities if we're daid.
  5294. // Blame Speyrer
  5295. return;
  5296. }
  5297. if ((GetState() == NPC_STATE_SCRIPT))
  5298. {
  5299. // HACK: finish any transitions we might be playing before we yield control to the script
  5300. if (GetActivity() != ACT_TRANSITION)
  5301. {
  5302. // Our animation state is being controlled by a script.
  5303. return;
  5304. }
  5305. }
  5306. if( m_IdealActivity == ACT_DO_NOT_DISTURB || !GetModelPtr() )
  5307. {
  5308. return;
  5309. }
  5310. // We may have work to do if we aren't playing our ideal activity OR if we
  5311. // aren't playing our ideal sequence.
  5312. if ((GetActivity() != m_IdealActivity) || (GetSequence() != m_nIdealSequence))
  5313. {
  5314. if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
  5315. {
  5316. DevMsg("MaintainActivity %s : %s:%s -> %s:%s\n", GetClassname(),
  5317. GetActivityName(GetActivity()), GetSequenceName(GetSequence()),
  5318. GetActivityName(m_IdealActivity), GetSequenceName(m_nIdealSequence));
  5319. }
  5320. bool bAdvance = false;
  5321. // If we're in a transition activity, see if we are done with the transition.
  5322. if (GetActivity() == ACT_TRANSITION)
  5323. {
  5324. // If the current sequence is finished, try to go to the next one
  5325. // closer to our ideal sequence.
  5326. if (IsSequenceFinished())
  5327. {
  5328. bAdvance = true;
  5329. }
  5330. // Else a transition sequence is in progress, do nothing.
  5331. }
  5332. // Else get a specific sequence for the activity and try to transition to that.
  5333. else
  5334. {
  5335. // Save off a target sequence and translated activities to apply when we finish
  5336. // playing all the transitions and finally arrive at our ideal activity.
  5337. ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity);
  5338. bAdvance = true;
  5339. }
  5340. if (bAdvance)
  5341. {
  5342. // Try to go to the next sequence closer to our ideal sequence.
  5343. AdvanceToIdealActivity();
  5344. }
  5345. }
  5346. }
  5347. //-----------------------------------------------------------------------------
  5348. // Purpose: Returns true if our ideal activity has finished playing.
  5349. //-----------------------------------------------------------------------------
  5350. bool CAI_BaseNPC::IsActivityFinished( void )
  5351. {
  5352. return (IsSequenceFinished() && (GetSequence() == m_nIdealSequence));
  5353. }
  5354. //-----------------------------------------------------------------------------
  5355. // Purpose: Checks to see if the activity is one of the standard phase-matched movement activities
  5356. // Input : activity
  5357. //-----------------------------------------------------------------------------
  5358. bool CAI_BaseNPC::IsActivityMovementPhased( Activity activity )
  5359. {
  5360. switch( activity )
  5361. {
  5362. case ACT_WALK:
  5363. case ACT_WALK_AIM:
  5364. case ACT_WALK_CROUCH:
  5365. case ACT_WALK_CROUCH_AIM:
  5366. case ACT_RUN:
  5367. case ACT_RUN_AIM:
  5368. case ACT_RUN_CROUCH:
  5369. case ACT_RUN_CROUCH_AIM:
  5370. case ACT_RUN_PROTECTED:
  5371. return true;
  5372. }
  5373. return false;
  5374. }
  5375. //-----------------------------------------------------------------------------
  5376. // Purpose:
  5377. //-----------------------------------------------------------------------------
  5378. void CAI_BaseNPC::OnChangeActivity( Activity eNewActivity )
  5379. {
  5380. if ( eNewActivity == ACT_RUN ||
  5381. eNewActivity == ACT_RUN_AIM ||
  5382. eNewActivity == ACT_WALK )
  5383. {
  5384. Stand();
  5385. }
  5386. }
  5387. //=========================================================
  5388. // SetSequenceByName
  5389. //=========================================================
  5390. void CAI_BaseNPC::SetSequenceByName( const char *szSequence )
  5391. {
  5392. int iSequence = LookupSequence( szSequence );
  5393. if ( iSequence > ACTIVITY_NOT_AVAILABLE )
  5394. SetSequenceById( iSequence );
  5395. else
  5396. {
  5397. DevWarning( 2, "%s has no sequence %s to match request\n", GetClassname(), szSequence );
  5398. SetSequence( 0 ); // Set to the reset anim (if it's there)
  5399. }
  5400. }
  5401. //-----------------------------------------------------------------------------
  5402. void CAI_BaseNPC::SetSequenceById( int iSequence )
  5403. {
  5404. // Set to the desired anim, or default anim if the desired is not present
  5405. if ( iSequence > ACTIVITY_NOT_AVAILABLE )
  5406. {
  5407. if ( GetSequence() != iSequence || !SequenceLoops() )
  5408. {
  5409. SetCycle( 0 );
  5410. }
  5411. ResetSequence( iSequence ); // Set to the reset anim (if it's there)
  5412. GetMotor()->RecalculateYawSpeed();
  5413. }
  5414. else
  5415. {
  5416. // Not available try to get default anim
  5417. DevWarning( 2, "%s invalid sequence requested\n", GetClassname() );
  5418. SetSequence( 0 ); // Set to the reset anim (if it's there)
  5419. }
  5420. }
  5421. //-----------------------------------------------------------------------------
  5422. // Purpose: Returns the target entity
  5423. // Input :
  5424. // Output :
  5425. //-----------------------------------------------------------------------------
  5426. CBaseEntity *CAI_BaseNPC::GetNavTargetEntity(void)
  5427. {
  5428. if ( GetNavigator()->GetGoalType() == GOALTYPE_ENEMY )
  5429. return m_hEnemy;
  5430. else if ( GetNavigator()->GetGoalType() == GOALTYPE_TARGETENT )
  5431. return m_hTargetEnt;
  5432. return NULL;
  5433. }
  5434. //-----------------------------------------------------------------------------
  5435. // Purpose: returns zero if the caller can jump from
  5436. // vecStart to vecEnd ignoring collisions with pTarget
  5437. //
  5438. // if the throw fails, returns the distance
  5439. // that can be travelled before an obstacle is hit
  5440. //-----------------------------------------------------------------------------
  5441. #include "ai_initutils.h"
  5442. //#define _THROWDEBUG
  5443. float CAI_BaseNPC::ThrowLimit( const Vector &vecStart,
  5444. const Vector &vecEnd,
  5445. float fGravity,
  5446. float fArcSize,
  5447. const Vector &mins,
  5448. const Vector &maxs,
  5449. CBaseEntity *pTarget,
  5450. Vector *jumpVel,
  5451. CBaseEntity **pBlocker)
  5452. {
  5453. // Get my jump velocity
  5454. Vector rawJumpVel = CalcThrowVelocity(vecStart, vecEnd, fGravity, fArcSize);
  5455. *jumpVel = rawJumpVel;
  5456. Vector vecFrom = vecStart;
  5457. // Calculate the total time of the jump minus a tiny fraction
  5458. float jumpTime = (vecStart - vecEnd).Length2D()/rawJumpVel.Length2D();
  5459. float timeStep = jumpTime / 10.0;
  5460. Vector gravity = Vector(0,0,fGravity);
  5461. // this loop takes single steps to the goal.
  5462. for (float flTime = 0 ; flTime < jumpTime-0.1 ; flTime += timeStep )
  5463. {
  5464. // Calculate my position after the time step (average velocity over this time step)
  5465. Vector nextPos = vecFrom + (rawJumpVel - 0.5 * gravity * timeStep) * timeStep;
  5466. // If last time step make next position the target position
  5467. if ((flTime + timeStep) > jumpTime)
  5468. {
  5469. nextPos = vecEnd;
  5470. }
  5471. trace_t tr;
  5472. AI_TraceHull( vecFrom, nextPos, mins, maxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  5473. if (tr.startsolid || tr.fraction < 1.0)
  5474. {
  5475. CBaseEntity *pEntity = tr.m_pEnt;
  5476. // If we hit the target we are good to go!
  5477. if (pEntity == pTarget)
  5478. {
  5479. return 0;
  5480. }
  5481. #ifdef _THROWDEBUG
  5482. NDebugOverlay::Line( vecFrom, nextPos, 255, 0, 0, true, 1.0 );
  5483. #endif
  5484. // ----------------------------------------------------------
  5485. // If blocked by an npc remember
  5486. // ----------------------------------------------------------
  5487. *pBlocker = pEntity;
  5488. // Return distance sucessfully traveled before block encountered
  5489. return ((tr.endpos - vecStart).Length());
  5490. }
  5491. #ifdef _THROWDEBUG
  5492. else
  5493. {
  5494. NDebugOverlay::Line( vecFrom, nextPos, 255, 255, 255, true, 1.0 );
  5495. }
  5496. #endif
  5497. rawJumpVel = rawJumpVel - gravity * timeStep;
  5498. vecFrom = nextPos;
  5499. }
  5500. return 0;
  5501. }
  5502. //-----------------------------------------------------------------------------
  5503. // Purpose: Called to initialize or re-initialize the vphysics hull when the size
  5504. // of the NPC changes
  5505. //-----------------------------------------------------------------------------
  5506. void CAI_BaseNPC::SetupVPhysicsHull()
  5507. {
  5508. if ( GetMoveType() == MOVETYPE_VPHYSICS || GetMoveType() == MOVETYPE_NONE )
  5509. return;
  5510. if ( VPhysicsGetObject() )
  5511. {
  5512. // Disable collisions to get
  5513. VPhysicsGetObject()->EnableCollisions(false);
  5514. VPhysicsDestroyObject();
  5515. }
  5516. VPhysicsInitShadow( true, false );
  5517. IPhysicsObject *pPhysObj = VPhysicsGetObject();
  5518. if ( pPhysObj )
  5519. {
  5520. float mass = Studio_GetMass(GetModelPtr());
  5521. if ( mass > 0 )
  5522. {
  5523. pPhysObj->SetMass( mass );
  5524. }
  5525. #if _DEBUG
  5526. else
  5527. {
  5528. DevMsg("Warning: %s has no physical mass\n", STRING(GetModelName()));
  5529. }
  5530. #endif
  5531. IPhysicsShadowController *pController = pPhysObj->GetShadowController();
  5532. float avgsize = (WorldAlignSize().x + WorldAlignSize().y) * 0.5;
  5533. pController->SetTeleportDistance( avgsize * 0.5 );
  5534. m_bCheckContacts = true;
  5535. }
  5536. }
  5537. // Check for problematic physics objects resting on this NPC.
  5538. // They can screw up his navigation, so attach a controller to
  5539. // help separate the NPC & physics when you encounter these.
  5540. ConVar ai_auto_contact_solver( "ai_auto_contact_solver", "1" );
  5541. void CAI_BaseNPC::CheckPhysicsContacts()
  5542. {
  5543. if ( gpGlobals->frametime <= 0.0f || !ai_auto_contact_solver.GetBool() )
  5544. return;
  5545. m_bCheckContacts = false;
  5546. if ( GetMoveType() == MOVETYPE_STEP && VPhysicsGetObject())
  5547. {
  5548. IPhysicsObject *pPhysics = VPhysicsGetObject();
  5549. IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
  5550. CBaseEntity *pGroundEntity = GetGroundEntity();
  5551. float heightCheck = GetAbsOrigin().z + GetHullMaxs().z;
  5552. Vector npcVel;
  5553. pPhysics->GetVelocity( &npcVel, NULL );
  5554. CBaseEntity *pOtherEntity = NULL;
  5555. bool createSolver = false;
  5556. float solverTime = 0.0f;
  5557. while ( pSnapshot->IsValid() )
  5558. {
  5559. IPhysicsObject *pOther = pSnapshot->GetObject(1);
  5560. pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
  5561. if ( pOtherEntity && pGroundEntity != pOtherEntity )
  5562. {
  5563. float otherMass = PhysGetEntityMass(pOtherEntity);
  5564. if ( pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS && pOther->IsMoveable() &&
  5565. otherMass < VPHYSICS_LARGE_OBJECT_MASS && !pOtherEntity->GetServerVehicle() )
  5566. {
  5567. m_bCheckContacts = true;
  5568. Vector vel, point;
  5569. pOther->GetVelocity( &vel, NULL );
  5570. pSnapshot->GetContactPoint( point );
  5571. // compare the relative velocity
  5572. vel -= npcVel;
  5573. // slow moving object probably won't clear itself.
  5574. // Either set ignore, or disable collisions entirely
  5575. if ( vel.LengthSqr() < 5.0f*5.0f )
  5576. {
  5577. float topdist = fabs(point.z-heightCheck);
  5578. // 4 seconds to ignore this for nav
  5579. solverTime = 4.0f;
  5580. if ( topdist < 2.0f )
  5581. {
  5582. // Resting on my head so disable collisions for a bit
  5583. solverTime = 0.5f; // UNDONE: Tune
  5584. if ( pOther->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  5585. {
  5586. // player is being a monkey
  5587. solverTime = 0.25f;
  5588. }
  5589. //Msg("Dropping %s from %s\n", pOtherEntity->GetClassname(), GetClassname() );
  5590. Assert( !NPCPhysics_SolverExists(this, pOtherEntity) );
  5591. createSolver = true;
  5592. break;
  5593. }
  5594. }
  5595. }
  5596. }
  5597. pSnapshot->NextFrictionData();
  5598. }
  5599. pPhysics->DestroyFrictionSnapshot( pSnapshot );
  5600. if ( createSolver )
  5601. {
  5602. // turn collisions back on once we've been separated for enough time
  5603. NPCPhysics_CreateSolver( this, pOtherEntity, true, solverTime );
  5604. pPhysics->RecheckContactPoints();
  5605. }
  5606. }
  5607. }
  5608. void CAI_BaseNPC::StartTouch( CBaseEntity *pOther )
  5609. {
  5610. BaseClass::StartTouch(pOther);
  5611. if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
  5612. {
  5613. m_bCheckContacts = true;
  5614. }
  5615. }
  5616. //-----------------------------------------------------------------------------
  5617. // Purpose: To be called instead of UTIL_SetSize, so pathfinding hull
  5618. // and actual hull agree
  5619. // Input :
  5620. // Output :
  5621. //-----------------------------------------------------------------------------
  5622. void CAI_BaseNPC::SetHullSizeNormal( bool force )
  5623. {
  5624. if ( m_fIsUsingSmallHull || force )
  5625. {
  5626. // Find out what the height difference will be between the versions and adjust our bbox accordingly to keep us level
  5627. const float flScale = GetModelScale();
  5628. Vector vecMins = ( GetHullMins() * flScale );
  5629. Vector vecMaxs = ( GetHullMaxs() * flScale );
  5630. UTIL_SetSize( this, vecMins, vecMaxs );
  5631. m_fIsUsingSmallHull = false;
  5632. if ( VPhysicsGetObject() )
  5633. {
  5634. SetupVPhysicsHull();
  5635. }
  5636. }
  5637. }
  5638. //-----------------------------------------------------------------------------
  5639. // Purpose: To be called instead of UTIL_SetSize, so pathfinding hull
  5640. // and actual hull agree
  5641. // Input :
  5642. // Output :
  5643. //-----------------------------------------------------------------------------
  5644. bool CAI_BaseNPC::SetHullSizeSmall( bool force )
  5645. {
  5646. if ( !m_fIsUsingSmallHull || force )
  5647. {
  5648. UTIL_SetSize(this, NAI_Hull::SmallMins(GetHullType()),NAI_Hull::SmallMaxs(GetHullType()));
  5649. m_fIsUsingSmallHull = true;
  5650. if ( VPhysicsGetObject() )
  5651. {
  5652. SetupVPhysicsHull();
  5653. }
  5654. }
  5655. return true;
  5656. }
  5657. //-----------------------------------------------------------------------------
  5658. // Checks to see that the nav hull is valid for the NPC
  5659. //-----------------------------------------------------------------------------
  5660. bool CAI_BaseNPC::IsNavHullValid() const
  5661. {
  5662. Assert( GetSolid() != SOLID_BSP );
  5663. Vector hullMin = GetHullMins();
  5664. Vector hullMax = GetHullMaxs();
  5665. Vector vecMins, vecMaxs;
  5666. if ( GetSolid() == SOLID_BBOX )
  5667. {
  5668. vecMins = WorldAlignMins();
  5669. vecMaxs = WorldAlignMaxs();
  5670. }
  5671. else if ( GetSolid() == SOLID_VPHYSICS )
  5672. {
  5673. Assert( VPhysicsGetObject() );
  5674. const CPhysCollide *pPhysCollide = VPhysicsGetObject()->GetCollide();
  5675. physcollision->CollideGetAABB( &vecMins, &vecMaxs, pPhysCollide, GetAbsOrigin(), GetAbsAngles() );
  5676. vecMins -= GetAbsOrigin();
  5677. vecMaxs -= GetAbsOrigin();
  5678. }
  5679. else
  5680. {
  5681. vecMins = hullMin;
  5682. vecMaxs = hullMax;
  5683. }
  5684. if ( (hullMin.x > vecMins.x) || (hullMax.x < vecMaxs.x) ||
  5685. (hullMin.y > vecMins.y) || (hullMax.y < vecMaxs.y) ||
  5686. (hullMin.z > vecMins.z) || (hullMax.z < vecMaxs.z) )
  5687. {
  5688. return false;
  5689. }
  5690. return true;
  5691. }
  5692. //=========================================================
  5693. // NPCInit - after a npc is spawned, it needs to
  5694. // be dropped into the world, checked for mobility problems,
  5695. // and put on the proper path, if any. This function does
  5696. // all of those things after the npc spawns. Any
  5697. // initialization that should take place for all npcs
  5698. // goes here.
  5699. //=========================================================
  5700. void CAI_BaseNPC::NPCInit ( void )
  5701. {
  5702. if (!g_pGameRules->FAllowNPCs())
  5703. {
  5704. UTIL_Remove( this );
  5705. return;
  5706. }
  5707. if( IsWaitingToRappel() )
  5708. {
  5709. // If this guy's supposed to rappel, keep him from
  5710. // falling to the ground when he spawns.
  5711. AddFlag( FL_FLY );
  5712. }
  5713. #ifdef _DEBUG
  5714. // Make sure that the bounding box is appropriate for the hull size...
  5715. // FIXME: We can't test vphysics objects because NPCInit occurs before VPhysics is set up
  5716. if ( GetSolid() != SOLID_VPHYSICS && !IsSolidFlagSet(FSOLID_NOT_SOLID) )
  5717. {
  5718. if ( !IsNavHullValid() )
  5719. {
  5720. Warning("NPC Entity %s (%d) has a bounding box which extends outside its nav box!\n",
  5721. STRING(m_iClassname), entindex() );
  5722. }
  5723. }
  5724. #endif
  5725. // Set fields common to all npcs
  5726. AddFlag( FL_AIMTARGET | FL_NPC );
  5727. AddSolidFlags( FSOLID_NOT_STANDABLE );
  5728. m_flOriginalYaw = GetAbsAngles().y;
  5729. SetBlocksLOS( false );
  5730. SetGravity(1.0); // Don't change
  5731. m_takedamage = DAMAGE_YES;
  5732. GetMotor()->SetIdealYaw( GetLocalAngles().y );
  5733. m_iMaxHealth = m_iHealth;
  5734. m_lifeState = LIFE_ALIVE;
  5735. SetIdealState( NPC_STATE_IDLE );// Assume npc will be idle, until proven otherwise
  5736. SetIdealActivity( ACT_IDLE );
  5737. SetActivity( ACT_IDLE );
  5738. #ifdef HL1_DLL
  5739. SetDeathPose( ACT_INVALID );
  5740. #endif
  5741. ClearCommandGoal();
  5742. ClearSchedule( "Initializing NPC" );
  5743. GetNavigator()->ClearGoal();
  5744. InitBoneControllers( ); // FIX: should be done in Spawn
  5745. if ( GetModelPtr() )
  5746. {
  5747. ResetActivityIndexes();
  5748. ResetEventIndexes();
  5749. }
  5750. SetHintNode( NULL );
  5751. m_afMemory = MEMORY_CLEAR;
  5752. SetEnemy( NULL );
  5753. m_flDistTooFar = 1024.0;
  5754. SetDistLook( 2048.0 );
  5755. if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) )
  5756. {
  5757. m_flDistTooFar = 1e9f;
  5758. SetDistLook( 6000.0 );
  5759. }
  5760. // Clear conditions
  5761. m_Conditions.ClearAll();
  5762. // set eye position
  5763. SetDefaultEyeOffset();
  5764. // Only give weapon of allowed to have one
  5765. if (CapabilitiesGet() & bits_CAP_USE_WEAPONS)
  5766. { // Does this npc spawn with a weapon
  5767. if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0"))
  5768. {
  5769. CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(m_spawnEquipment) );
  5770. if ( pWeapon )
  5771. {
  5772. // If I have a name, make my weapon match it with "_weapon" appended
  5773. if ( GetEntityName() != NULL_STRING )
  5774. {
  5775. pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", STRING(GetEntityName()))) );
  5776. }
  5777. if ( GetEffects() & EF_NOSHADOW )
  5778. {
  5779. // BUGBUG: if this NPC drops this weapon it will forevermore have no shadow
  5780. pWeapon->AddEffects( EF_NOSHADOW );
  5781. }
  5782. Weapon_Equip( pWeapon );
  5783. }
  5784. }
  5785. }
  5786. // Robin: Removed this, since it stomps the weapon's settings, and it's stomped
  5787. // by OnUpdateShotRegulator() as soon as they finish firing the first time.
  5788. //GetShotRegulator()->SetParameters( 2, 6, 0.3f, 0.8f );
  5789. SetUse ( &CAI_BaseNPC::NPCUse );
  5790. // NOTE: Can't call NPC Init Think directly... logic changed about
  5791. // what time it is when worldspawn happens..
  5792. // We must put off the rest of our initialization
  5793. // until we're sure everything else has had a chance to spawn. Otherwise
  5794. // we may try to reference entities that haven't spawned yet.(sjb)
  5795. SetThink( &CAI_BaseNPC::NPCInitThink );
  5796. SetNextThink( gpGlobals->curtime + 0.01f );
  5797. ForceGatherConditions();
  5798. // HACKHACK: set up a pre idle animation
  5799. // NOTE: Must do this before CreateVPhysics() so bone followers have the correct initial positions.
  5800. if ( HasSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT ) )
  5801. {
  5802. const char *pStartSequence = CAI_ScriptedSequence::GetSpawnPreIdleSequenceForScript( this );
  5803. if ( pStartSequence )
  5804. {
  5805. SetSequence( LookupSequence( pStartSequence ) );
  5806. }
  5807. }
  5808. CreateVPhysics();
  5809. if ( HasSpawnFlags( SF_NPC_START_EFFICIENT ) )
  5810. {
  5811. SetEfficiency( AIE_EFFICIENT );
  5812. }
  5813. m_bFadeCorpse = ShouldFadeOnDeath();
  5814. m_GiveUpOnDeadEnemyTimer.Set( 0.75, 2.0 );
  5815. m_flTimeLastMovement = FLT_MAX;
  5816. m_flIgnoreDangerSoundsUntil = 0;
  5817. SetDeathPose( ACT_INVALID );
  5818. SetDeathPoseFrame( 0 );
  5819. m_EnemiesSerialNumber = -1;
  5820. }
  5821. //-----------------------------------------------------------------------------
  5822. bool CAI_BaseNPC::CreateVPhysics()
  5823. {
  5824. if ( IsAlive() && !VPhysicsGetObject() )
  5825. {
  5826. SetupVPhysicsHull();
  5827. }
  5828. return true;
  5829. }
  5830. //-----------------------------------------------------------------------------
  5831. // Set up the shot regulator based on the equipped weapon
  5832. //-----------------------------------------------------------------------------
  5833. void CAI_BaseNPC::OnUpdateShotRegulator( )
  5834. {
  5835. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  5836. if ( !pWeapon )
  5837. return;
  5838. // Default values
  5839. m_ShotRegulator.SetBurstInterval( pWeapon->GetFireRate(), pWeapon->GetFireRate() );
  5840. m_ShotRegulator.SetBurstShotCountRange( pWeapon->GetMinBurst(), pWeapon->GetMaxBurst() );
  5841. m_ShotRegulator.SetRestInterval( pWeapon->GetMinRestTime(), pWeapon->GetMaxRestTime() );
  5842. // Let the behavior have a whack at it.
  5843. if ( GetRunningBehavior() )
  5844. {
  5845. GetRunningBehavior()->OnUpdateShotRegulator();
  5846. }
  5847. }
  5848. //-----------------------------------------------------------------------------
  5849. // Set up the shot regulator based on the equipped weapon
  5850. //-----------------------------------------------------------------------------
  5851. void CAI_BaseNPC::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
  5852. {
  5853. BaseClass::OnChangeActiveWeapon( pOldWeapon, pNewWeapon );
  5854. // Shot regulator code
  5855. if ( pNewWeapon )
  5856. {
  5857. OnUpdateShotRegulator();
  5858. m_ShotRegulator.Reset( true );
  5859. }
  5860. }
  5861. //-----------------------------------------------------------------------------
  5862. // Purpose: Tests to see if NPC can holster their weapon (if animation exists to holster weapon)
  5863. // Output : true if holster weapon animation exists
  5864. //-----------------------------------------------------------------------------
  5865. bool CAI_BaseNPC::CanHolsterWeapon( void )
  5866. {
  5867. int seq = SelectWeightedSequence( ACT_DISARM );
  5868. return (seq >= 0);
  5869. }
  5870. //-----------------------------------------------------------------------------
  5871. // Purpose:
  5872. //-----------------------------------------------------------------------------
  5873. int CAI_BaseNPC::HolsterWeapon( void )
  5874. {
  5875. if ( IsWeaponHolstered() )
  5876. return -1;
  5877. int iHolsterGesture = FindGestureLayer( ACT_DISARM );
  5878. if ( iHolsterGesture != -1 )
  5879. return iHolsterGesture;
  5880. int iLayer = AddGesture( ACT_DISARM, true );
  5881. //iLayer = AddGesture( ACT_GESTURE_DISARM, true );
  5882. if (iLayer != -1)
  5883. {
  5884. // Prevent firing during the holster / unholster
  5885. float flDuration = GetLayerDuration( iLayer );
  5886. m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 );
  5887. if( m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED )
  5888. {
  5889. m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING_DESTROY;
  5890. }
  5891. else
  5892. {
  5893. m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING;
  5894. }
  5895. // Make sure we don't try to reload while we're holstering
  5896. ClearCondition(COND_LOW_PRIMARY_AMMO);
  5897. ClearCondition(COND_NO_PRIMARY_AMMO);
  5898. ClearCondition(COND_NO_SECONDARY_AMMO);
  5899. }
  5900. return iLayer;
  5901. }
  5902. //-----------------------------------------------------------------------------
  5903. // Purpose:
  5904. //-----------------------------------------------------------------------------
  5905. int CAI_BaseNPC::UnholsterWeapon( void )
  5906. {
  5907. if ( !IsWeaponHolstered() )
  5908. return -1;
  5909. int iHolsterGesture = FindGestureLayer( ACT_ARM );
  5910. if ( iHolsterGesture != -1 )
  5911. return iHolsterGesture;
  5912. // Deploy the first weapon you can find
  5913. for (int i = 0; i < WeaponCount(); i++)
  5914. {
  5915. if ( GetWeapon( i ))
  5916. {
  5917. SetActiveWeapon( GetWeapon(i) );
  5918. int iLayer = AddGesture( ACT_ARM, true );
  5919. //iLayer = AddGesture( ACT_GESTURE_ARM, true );
  5920. if (iLayer != -1)
  5921. {
  5922. // Prevent firing during the holster / unholster
  5923. float flDuration = GetLayerDuration( iLayer );
  5924. m_ShotRegulator.FireNoEarlierThan( gpGlobals->curtime + flDuration + 0.5 );
  5925. m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING;
  5926. }
  5927. // Refill the clip
  5928. if ( GetActiveWeapon()->UsesClipsForAmmo1() )
  5929. {
  5930. GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
  5931. }
  5932. // Make sure we don't try to reload while we're unholstering
  5933. ClearCondition(COND_LOW_PRIMARY_AMMO);
  5934. ClearCondition(COND_NO_PRIMARY_AMMO);
  5935. ClearCondition(COND_NO_SECONDARY_AMMO);
  5936. return iLayer;
  5937. }
  5938. }
  5939. return -1;
  5940. }
  5941. //-----------------------------------------------------------------------------
  5942. // Purpose:
  5943. //-----------------------------------------------------------------------------
  5944. void CAI_BaseNPC::InputHolsterWeapon( inputdata_t &inputdata )
  5945. {
  5946. m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED;
  5947. }
  5948. //-----------------------------------------------------------------------------
  5949. // Purpose:
  5950. //-----------------------------------------------------------------------------
  5951. void CAI_BaseNPC::InputHolsterAndDestroyWeapon( inputdata_t &inputdata )
  5952. {
  5953. m_iDesiredWeaponState = DESIREDWEAPONSTATE_HOLSTERED_DESTROYED;
  5954. }
  5955. //-----------------------------------------------------------------------------
  5956. // Purpose:
  5957. //-----------------------------------------------------------------------------
  5958. void CAI_BaseNPC::InputUnholsterWeapon( inputdata_t &inputdata )
  5959. {
  5960. m_iDesiredWeaponState = DESIREDWEAPONSTATE_UNHOLSTERED;
  5961. }
  5962. //-----------------------------------------------------------------------------
  5963. // Purpose:
  5964. //-----------------------------------------------------------------------------
  5965. bool CAI_BaseNPC::IsWeaponHolstered( void )
  5966. {
  5967. if( !GetActiveWeapon() )
  5968. return true;
  5969. if( GetActiveWeapon()->IsEffectActive(EF_NODRAW) )
  5970. return true;
  5971. return false;
  5972. }
  5973. //-----------------------------------------------------------------------------
  5974. // Purpose:
  5975. //-----------------------------------------------------------------------------
  5976. bool CAI_BaseNPC::IsWeaponStateChanging( void )
  5977. {
  5978. return ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING || m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY );
  5979. }
  5980. //-----------------------------------------------------------------------------
  5981. // Set up the shot regulator based on the equipped weapon
  5982. //-----------------------------------------------------------------------------
  5983. void CAI_BaseNPC::OnRangeAttack1()
  5984. {
  5985. SetLastAttackTime( gpGlobals->curtime );
  5986. // Houston, there is a problem!
  5987. AssertOnce( GetShotRegulator()->ShouldShoot() );
  5988. m_ShotRegulator.OnFiredWeapon();
  5989. if ( m_ShotRegulator.IsInRestInterval() )
  5990. {
  5991. OnUpdateShotRegulator();
  5992. }
  5993. SetNextAttack( m_ShotRegulator.NextShotTime() );
  5994. }
  5995. //-----------------------------------------------------------------------------
  5996. // Purpose: Initialze the relationship table from the keyvalues
  5997. // Input :
  5998. // Output :
  5999. //-----------------------------------------------------------------------------
  6000. void CAI_BaseNPC::InitRelationshipTable(void)
  6001. {
  6002. AddRelationship( STRING( m_RelationshipString ), NULL );
  6003. }
  6004. //-----------------------------------------------------------------------------
  6005. // Purpose:
  6006. //-----------------------------------------------------------------------------
  6007. void CAI_BaseNPC::AddRelationship( const char *pszRelationship, CBaseEntity *pActivator )
  6008. {
  6009. // Parse the keyvalue data
  6010. char parseString[1000];
  6011. Q_strncpy(parseString, pszRelationship, sizeof(parseString));
  6012. // Look for an entity string
  6013. char *entityString = strtok(parseString," ");
  6014. while (entityString)
  6015. {
  6016. // Get the disposition
  6017. char *dispositionString = strtok(NULL," ");
  6018. Disposition_t disposition = D_NU;
  6019. if ( dispositionString )
  6020. {
  6021. if (!stricmp(dispositionString,"D_HT"))
  6022. {
  6023. disposition = D_HT;
  6024. }
  6025. else if (!stricmp(dispositionString,"D_FR"))
  6026. {
  6027. disposition = D_FR;
  6028. }
  6029. else if (!stricmp(dispositionString,"D_LI"))
  6030. {
  6031. disposition = D_LI;
  6032. }
  6033. else if (!stricmp(dispositionString,"D_NU"))
  6034. {
  6035. disposition = D_NU;
  6036. }
  6037. else
  6038. {
  6039. disposition = D_NU;
  6040. Warning( "***ERROR***\nBad relationship type (%s) to unknown entity (%s)!\n", dispositionString,entityString );
  6041. Assert( 0 );
  6042. return;
  6043. }
  6044. }
  6045. else
  6046. {
  6047. Warning("Can't parse relationship info (%s) - Expecting 'name [D_HT, D_FR, D_LI, D_NU] [1-99]'\n", pszRelationship );
  6048. Assert(0);
  6049. return;
  6050. }
  6051. // Get the priority
  6052. char *priorityString = strtok(NULL," ");
  6053. int priority = ( priorityString ) ? atoi(priorityString) : DEF_RELATIONSHIP_PRIORITY;
  6054. bool bFoundEntity = false;
  6055. // Try to get pointer to an entity of this name
  6056. CBaseEntity *entity = gEntList.FindEntityByName( NULL, entityString );
  6057. while( entity )
  6058. {
  6059. // make sure you catch all entities of this name.
  6060. bFoundEntity = true;
  6061. AddEntityRelationship(entity, disposition, priority );
  6062. entity = gEntList.FindEntityByName( entity, entityString );
  6063. }
  6064. if( !bFoundEntity )
  6065. {
  6066. // Need special condition for player as we can only have one
  6067. if (!stricmp("player", entityString) || !stricmp("!player", entityString))
  6068. {
  6069. AddClassRelationship( CLASS_PLAYER, disposition, priority );
  6070. }
  6071. // Otherwise try to create one too see if a valid classname and get class type
  6072. else
  6073. {
  6074. // HACKHACK:
  6075. CBaseEntity *pEntity = CanCreateEntityClass( entityString ) ? CreateEntityByName( entityString ) : NULL;
  6076. if (pEntity)
  6077. {
  6078. AddClassRelationship( pEntity->Classify(), disposition, priority );
  6079. UTIL_RemoveImmediate(pEntity);
  6080. }
  6081. else
  6082. {
  6083. DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString );
  6084. }
  6085. }
  6086. }
  6087. // Check for another entity in the list
  6088. entityString = strtok(NULL," ");
  6089. }
  6090. }
  6091. //-----------------------------------------------------------------------------
  6092. //-----------------------------------------------------------------------------
  6093. void CAI_BaseNPC::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority )
  6094. {
  6095. #if 0
  6096. ForceGatherConditions();
  6097. #endif
  6098. BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority );
  6099. }
  6100. //-----------------------------------------------------------------------------
  6101. //-----------------------------------------------------------------------------
  6102. void CAI_BaseNPC::AddClassRelationship( Class_T nClass, Disposition_t nDisposition, int nPriority )
  6103. {
  6104. #if 0
  6105. ForceGatherConditions();
  6106. #endif
  6107. BaseClass::AddClassRelationship( nClass, nDisposition, nPriority );
  6108. }
  6109. //=========================================================
  6110. // NPCInitThink - Calls StartNPC. Startnpc is
  6111. // virtual, but this function cannot be
  6112. //=========================================================
  6113. void CAI_BaseNPC::NPCInitThink ( void )
  6114. {
  6115. // Initialize the relationship table
  6116. InitRelationshipTable();
  6117. StartNPC();
  6118. PostNPCInit();
  6119. if( GetSleepState() == AISS_AUTO_PVS )
  6120. {
  6121. // This code is a bit wonky, but it makes it easier for level designers to
  6122. // select this option in Hammer. So we set a sleep flag to indicate the choice,
  6123. // and then set the sleep state to awake (normal)
  6124. AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS );
  6125. SetSleepState( AISS_AWAKE );
  6126. }
  6127. if( GetSleepState() == AISS_AUTO_PVS_AFTER_PVS )
  6128. {
  6129. AddSleepFlags( AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS );
  6130. SetSleepState( AISS_AWAKE );
  6131. }
  6132. if ( GetSleepState() > AISS_AWAKE )
  6133. {
  6134. Sleep();
  6135. }
  6136. m_flLastRealThinkTime = gpGlobals->curtime;
  6137. }
  6138. //=========================================================
  6139. // StartNPC - final bit of initization before a npc
  6140. // is turned over to the AI.
  6141. //=========================================================
  6142. void CAI_BaseNPC::StartNPC( void )
  6143. {
  6144. // Raise npc off the floor one unit, then drop to floor
  6145. if ( (GetMoveType() != MOVETYPE_FLY) && (GetMoveType() != MOVETYPE_FLYGRAVITY) &&
  6146. !(CapabilitiesGet() & bits_CAP_MOVE_FLY) &&
  6147. !HasSpawnFlags( SF_NPC_FALL_TO_GROUND ) && !IsWaitingToRappel() && !GetMoveParent() )
  6148. {
  6149. Vector origin = GetLocalOrigin();
  6150. if (!GetMoveProbe()->FloorPoint( origin + Vector(0, 0, 0.1), MASK_NPCSOLID, 0, -2048, &origin ))
  6151. {
  6152. Warning( "NPC %s stuck in wall--level design error at (%.2f %.2f %.2f)\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  6153. if ( g_pDeveloper->GetInt() > 1 )
  6154. {
  6155. m_debugOverlays |= OVERLAY_BBOX_BIT;
  6156. }
  6157. }
  6158. SetLocalOrigin( origin );
  6159. }
  6160. else
  6161. {
  6162. SetGroundEntity( NULL );
  6163. }
  6164. if ( m_target != NULL_STRING )// this npc has a target
  6165. {
  6166. // Find the npc's initial target entity, stash it
  6167. SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) );
  6168. if ( !GetGoalEnt() )
  6169. {
  6170. Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target));
  6171. }
  6172. else
  6173. {
  6174. StartTargetHandling( GetGoalEnt() );
  6175. }
  6176. }
  6177. //SetState ( m_IdealNPCState );
  6178. //SetActivity ( m_IdealActivity );
  6179. InitSquad();
  6180. //---------------------------------
  6181. //
  6182. // Spread think times of simultaneously spawned NPCs so that they don't all happen at the same time
  6183. //
  6184. // Think distribution based on spawn order is:
  6185. //
  6186. // Tick offset Think time Spawn order
  6187. // 0 0 1
  6188. // 1 0.015 13
  6189. // 2 0.03 5
  6190. // 3 0.045 9
  6191. // 4 0.06 18
  6192. // 5 0.075 3
  6193. // 6 0.09 15
  6194. // 7 0.105 11
  6195. // 8 0.12 7
  6196. // 9 0.135 17
  6197. // 10 0.15 2
  6198. // 11 0.165 14
  6199. // 12 0.18 6
  6200. // 13 0.195 19
  6201. // 14 0.21 10
  6202. // 15 0.225 4
  6203. // 16 0.24 16
  6204. // 17 0.255 12
  6205. // 18 0.27 8
  6206. // 19 0.285 20
  6207. // If this NPC is spawning late in the game, just push through the rest of the initialization
  6208. // start thinking right now. Some spread is added to handle triggered spawns that bring
  6209. // a bunch of NPCs into the level
  6210. SetThink ( &CAI_BaseNPC::CallNPCThink );
  6211. if ( gm_flTimeLastSpawn != gpGlobals->curtime )
  6212. {
  6213. gm_nSpawnedThisFrame = 0;
  6214. gm_flTimeLastSpawn = gpGlobals->curtime;
  6215. }
  6216. static const float nextThinkTimes[20] =
  6217. {
  6218. .0, .150, .075, .225, .030, .180, .120, .270, .045, .210, .105, .255, .015, .165, .090, .240, .135, .060, .195, .285
  6219. };
  6220. SetNextThink( gpGlobals->curtime + nextThinkTimes[gm_nSpawnedThisFrame % 20] );
  6221. gm_nSpawnedThisFrame++;
  6222. //---------------------------------
  6223. m_ScriptArrivalActivity = AIN_DEF_ACTIVITY;
  6224. m_strScriptArrivalSequence = NULL_STRING;
  6225. if ( HasSpawnFlags(SF_NPC_WAIT_FOR_SCRIPT) )
  6226. {
  6227. SetState( NPC_STATE_IDLE );
  6228. m_Activity = m_IdealActivity;
  6229. m_nIdealSequence = GetSequence();
  6230. SetSchedule( SCHED_WAIT_FOR_SCRIPT );
  6231. }
  6232. }
  6233. //-----------------------------------------------------------------------------
  6234. void CAI_BaseNPC::StartTargetHandling( CBaseEntity *pTargetEnt )
  6235. {
  6236. // set the npc up to walk a path corner path.
  6237. // !!!BUGBUG - this is a minor bit of a hack.
  6238. // JAYJAY
  6239. // NPC will start turning towards his destination
  6240. bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY);
  6241. AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pTargetEnt->GetAbsOrigin(),
  6242. bIsFlying ? ACT_FLY : ACT_WALK,
  6243. AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
  6244. SetState( NPC_STATE_IDLE );
  6245. SetSchedule( SCHED_IDLE_WALK );
  6246. if ( !GetNavigator()->SetGoal( goal ) )
  6247. {
  6248. DevWarning( 2, "Can't Create Route!\n" );
  6249. }
  6250. }
  6251. //-----------------------------------------------------------------------------
  6252. // Purpose: Connect my memory to the squad's
  6253. //-----------------------------------------------------------------------------
  6254. bool CAI_BaseNPC::InitSquad( void )
  6255. {
  6256. // -------------------------------------------------------
  6257. // If I form squads add me to a squad
  6258. // -------------------------------------------------------
  6259. if (!m_pSquad && ( CapabilitiesGet() & bits_CAP_SQUAD ))
  6260. {
  6261. if ( !m_SquadName )
  6262. {
  6263. DevMsg(2, "Found %s that isn't in a squad\n",GetClassname());
  6264. }
  6265. else
  6266. {
  6267. m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName);
  6268. }
  6269. }
  6270. return ( m_pSquad != NULL );
  6271. }
  6272. //-----------------------------------------------------------------------------
  6273. // Purpose: Get the memory for this NPC
  6274. //-----------------------------------------------------------------------------
  6275. CAI_Enemies *CAI_BaseNPC::GetEnemies( void )
  6276. {
  6277. return m_pEnemies;
  6278. }
  6279. //-----------------------------------------------------------------------------
  6280. // Purpose: Remove this NPC's memory
  6281. //-----------------------------------------------------------------------------
  6282. void CAI_BaseNPC::RemoveMemory( void )
  6283. {
  6284. delete m_pEnemies;
  6285. }
  6286. //-----------------------------------------------------------------------------
  6287. // Purpose:
  6288. //-----------------------------------------------------------------------------
  6289. void CAI_BaseNPC::TaskComplete( bool fIgnoreSetFailedCondition )
  6290. {
  6291. EndTaskOverlay();
  6292. // Handy thing to use for debugging
  6293. //if (IsCurSchedule(SCHED_PUT_HERE) &&
  6294. // GetTask()->iTask == TASK_PUT_HERE)
  6295. //{
  6296. // int put_breakpoint_here = 5;
  6297. //}
  6298. if ( fIgnoreSetFailedCondition || !HasCondition(COND_TASK_FAILED) )
  6299. {
  6300. SetTaskStatus( TASKSTATUS_COMPLETE );
  6301. }
  6302. }
  6303. void CAI_BaseNPC::TaskMovementComplete( void )
  6304. {
  6305. switch( GetTaskStatus() )
  6306. {
  6307. case TASKSTATUS_NEW:
  6308. case TASKSTATUS_RUN_MOVE_AND_TASK:
  6309. SetTaskStatus( TASKSTATUS_RUN_TASK );
  6310. break;
  6311. case TASKSTATUS_RUN_MOVE:
  6312. TaskComplete();
  6313. break;
  6314. case TASKSTATUS_RUN_TASK:
  6315. // FIXME: find out how to safely restart movement
  6316. //Warning( "Movement completed twice!\n" );
  6317. //Assert( 0 );
  6318. break;
  6319. case TASKSTATUS_COMPLETE:
  6320. break;
  6321. }
  6322. // JAY: Put this back in.
  6323. // UNDONE: Figure out how much of the timestep was consumed by movement
  6324. // this frame and restart the movement/schedule engine if necessary
  6325. if ( m_scriptState != SCRIPT_CUSTOM_MOVE_TO_MARK )
  6326. {
  6327. SetIdealActivity( GetStoppedActivity() );
  6328. }
  6329. // Advance past the last node (in case there is some event at this node)
  6330. if ( GetNavigator()->IsGoalActive() )
  6331. {
  6332. GetNavigator()->AdvancePath();
  6333. }
  6334. // Now clear the path, it's done.
  6335. GetNavigator()->ClearGoal();
  6336. OnMovementComplete();
  6337. }
  6338. int CAI_BaseNPC::TaskIsRunning( void )
  6339. {
  6340. if ( GetTaskStatus() != TASKSTATUS_COMPLETE &&
  6341. GetTaskStatus() != TASKSTATUS_RUN_MOVE )
  6342. return 1;
  6343. return 0;
  6344. }
  6345. //-----------------------------------------------------------------------------
  6346. // Purpose:
  6347. // Input :
  6348. // Output :
  6349. //-----------------------------------------------------------------------------
  6350. void CAI_BaseNPC::TaskFail( AI_TaskFailureCode_t code )
  6351. {
  6352. EndTaskOverlay();
  6353. // Handy tool for debugging
  6354. //if (IsCurSchedule(SCHED_PUT_NAME_HERE))
  6355. //{
  6356. // int put_breakpoint_here = 5;
  6357. //}
  6358. // If in developer mode save the fail text for debug output
  6359. if (g_pDeveloper->GetInt())
  6360. {
  6361. m_failText = TaskFailureToString( code );
  6362. m_interuptSchedule = NULL;
  6363. m_failedSchedule = GetCurSchedule();
  6364. if (m_debugOverlays & OVERLAY_TASK_TEXT_BIT)
  6365. {
  6366. DevMsg(this, AIMF_IGNORE_SELECTED, " TaskFail -> %s\n", m_failText );
  6367. }
  6368. ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): TaskFail -> %s\n", GetDebugName(), entindex(), m_failText ) );
  6369. //AddTimedOverlay( fail_text, 5);
  6370. }
  6371. m_ScheduleState.taskFailureCode = code;
  6372. SetCondition(COND_TASK_FAILED);
  6373. Forget( bits_MEMORY_TURNING );
  6374. }
  6375. //------------------------------------------------------------------------------
  6376. // Purpose : Remember that this entity wasn't reachable
  6377. // Input :
  6378. // Output :
  6379. //------------------------------------------------------------------------------
  6380. void CAI_BaseNPC::RememberUnreachable(CBaseEntity *pEntity, float duration )
  6381. {
  6382. if ( pEntity == GetEnemy() )
  6383. {
  6384. ForceChooseNewEnemy();
  6385. }
  6386. const float NPC_UNREACHABLE_TIMEOUT = ( duration > 0.0 ) ? duration : 3;
  6387. // Only add to list if not already on it
  6388. for (int i=m_UnreachableEnts.Size()-1;i>=0;i--)
  6389. {
  6390. // If record already exists just update mark time
  6391. if (pEntity == m_UnreachableEnts[i].hUnreachableEnt)
  6392. {
  6393. m_UnreachableEnts[i].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT;
  6394. m_UnreachableEnts[i].vLocationWhenUnreachable = pEntity->GetAbsOrigin();
  6395. return;
  6396. }
  6397. }
  6398. // Add new unreachabe entity to list
  6399. int nNewIndex = m_UnreachableEnts.AddToTail();
  6400. m_UnreachableEnts[nNewIndex].hUnreachableEnt = pEntity;
  6401. m_UnreachableEnts[nNewIndex].fExpireTime = gpGlobals->curtime + NPC_UNREACHABLE_TIMEOUT;
  6402. m_UnreachableEnts[nNewIndex].vLocationWhenUnreachable = pEntity->GetAbsOrigin();
  6403. }
  6404. //------------------------------------------------------------------------------
  6405. // Purpose : Returns true is entity was remembered as unreachable.
  6406. // After a time delay reachability is checked
  6407. // Input :
  6408. // Output :
  6409. //------------------------------------------------------------------------------
  6410. bool CAI_BaseNPC::IsUnreachable(CBaseEntity *pEntity)
  6411. {
  6412. float UNREACHABLE_DIST_TOLERANCE_SQ = (120*120);
  6413. // Note that it's ok to remove elements while I'm iterating
  6414. // as long as I iterate backwards and remove them using FastRemove
  6415. for (int i=m_UnreachableEnts.Size()-1;i>=0;i--)
  6416. {
  6417. // Remove any dead elements
  6418. if (m_UnreachableEnts[i].hUnreachableEnt == NULL)
  6419. {
  6420. m_UnreachableEnts.FastRemove(i);
  6421. }
  6422. else if (pEntity == m_UnreachableEnts[i].hUnreachableEnt)
  6423. {
  6424. // Test for reachablility on any elements that have timed out
  6425. if ( gpGlobals->curtime > m_UnreachableEnts[i].fExpireTime ||
  6426. pEntity->GetAbsOrigin().DistToSqr(m_UnreachableEnts[i].vLocationWhenUnreachable) > UNREACHABLE_DIST_TOLERANCE_SQ)
  6427. {
  6428. m_UnreachableEnts.FastRemove(i);
  6429. return false;
  6430. }
  6431. return true;
  6432. }
  6433. }
  6434. return false;
  6435. }
  6436. bool CAI_BaseNPC::IsValidEnemy( CBaseEntity *pEnemy )
  6437. {
  6438. CAI_BaseNPC *pEnemyNPC = pEnemy->MyNPCPointer();
  6439. if ( pEnemyNPC && pEnemyNPC->CanBeAnEnemyOf( this ) == false )
  6440. return false;
  6441. // Test our enemy filter
  6442. if ( m_hEnemyFilter.Get()!= NULL && m_hEnemyFilter->PassesFilter( this, pEnemy ) == false )
  6443. return false;
  6444. return true;
  6445. }
  6446. bool CAI_BaseNPC::CanBeAnEnemyOf( CBaseEntity *pEnemy )
  6447. {
  6448. if ( GetSleepState() > AISS_WAITING_FOR_THREAT )
  6449. return false;
  6450. return true;
  6451. }
  6452. //-----------------------------------------------------------------------------
  6453. // Purpose: Picks best enemy from my list of enemies
  6454. // Prefers reachable enemies over enemies that are unreachable,
  6455. // regardless of priority. For enemies that are both reachable or
  6456. // unreachable picks by priority. If priority is the same, picks
  6457. // by distance.
  6458. // Input :
  6459. // Output :
  6460. //-----------------------------------------------------------------------------
  6461. CBaseEntity *CAI_BaseNPC::BestEnemy( void )
  6462. {
  6463. AI_PROFILE_SCOPE( CAI_BaseNPC_BestEnemy );
  6464. // TODO - may want to consider distance, attack types, back turned, etc.
  6465. CBaseEntity* pBestEnemy = NULL;
  6466. int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE;// so first visible entity will become the closest.
  6467. int iBestPriority = -1000;
  6468. bool bBestUnreachable = true; // Forces initial check
  6469. ThreeState_t fBestSeen = TRS_NONE;
  6470. ThreeState_t fBestVisible = TRS_NONE;
  6471. int iDistSq;
  6472. bool bUnreachable = false;
  6473. AIEnemiesIter_t iter;
  6474. DbgEnemyMsg( this, "BestEnemy() {\n" );
  6475. for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  6476. {
  6477. CBaseEntity *pEnemy = pEMemory->hEnemy;
  6478. if (!pEnemy || !pEnemy->IsAlive())
  6479. {
  6480. if ( pEnemy )
  6481. {
  6482. DbgEnemyMsg( this, " %s rejected: dead\n", pEnemy->GetDebugName() );
  6483. }
  6484. continue;
  6485. }
  6486. if ( (pEnemy->GetFlags() & FL_NOTARGET) )
  6487. {
  6488. DbgEnemyMsg( this, " %s rejected: no target\n", pEnemy->GetDebugName() );
  6489. continue;
  6490. }
  6491. if ( m_bIgnoreUnseenEnemies )
  6492. {
  6493. const float TIME_CONSIDER_ENEMY_UNSEEN = .4;
  6494. if ( pEMemory->timeLastSeen < gpGlobals->curtime - TIME_CONSIDER_ENEMY_UNSEEN )
  6495. {
  6496. DbgEnemyMsg( this, " %s rejected: not seen and set to ignore unseen enemies\n", pEnemy->GetDebugName() );
  6497. continue;
  6498. }
  6499. }
  6500. // UNDONE: Move relationship checks into IsValidEnemy?
  6501. Disposition_t relation = IRelationType( pEnemy );
  6502. if ( (relation != D_HT && relation != D_FR) )
  6503. {
  6504. DbgEnemyMsg( this, " %s rejected: no hate/fear\n", pEnemy->GetDebugName() );
  6505. continue;
  6506. }
  6507. if ( m_flAcceptableTimeSeenEnemy > 0.0 && pEMemory->timeLastSeen < m_flAcceptableTimeSeenEnemy )
  6508. {
  6509. DbgEnemyMsg( this, " %s rejected: old\n", pEnemy->GetDebugName() );
  6510. continue;
  6511. }
  6512. if ( pEMemory->timeValidEnemy > gpGlobals->curtime )
  6513. {
  6514. DbgEnemyMsg( this, " %s rejected: not yet valid\n", pEnemy->GetDebugName() );
  6515. continue;
  6516. }
  6517. // Skip enemies that have eluded me to prevent infinite loops
  6518. if ( pEMemory->bEludedMe )
  6519. {
  6520. DbgEnemyMsg( this, " %s rejected: eluded\n", pEnemy->GetDebugName() );
  6521. continue;
  6522. }
  6523. // Skip enemies I fear that I've never seen. (usually seen through an enemy finder)
  6524. if ( relation == D_FR && !pEMemory->bUnforgettable && pEMemory->timeFirstSeen == AI_INVALID_TIME )
  6525. {
  6526. DbgEnemyMsg( this, " %s rejected: feared, but never seen\n", pEnemy->GetDebugName() );
  6527. continue;
  6528. }
  6529. if ( !IsValidEnemy( pEnemy ) )
  6530. {
  6531. DbgEnemyMsg( this, " %s rejected: not valid\n", pEnemy->GetDebugName() );
  6532. continue;
  6533. }
  6534. // establish the reachability of this enemy
  6535. bUnreachable = IsUnreachable(pEnemy);
  6536. // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority
  6537. if (!bBestUnreachable && bUnreachable)
  6538. {
  6539. DbgEnemyMsg( this, " %s rejected: unreachable\n", pEnemy->GetDebugName() );
  6540. continue;
  6541. }
  6542. // If best is unreachable and current is reachable, always pick the current regardless of priority
  6543. if (bBestUnreachable && !bUnreachable)
  6544. {
  6545. DbgEnemyMsg( this, " %s accepted (1)\n", pEnemy->GetDebugName() );
  6546. if ( pBestEnemy )
  6547. {
  6548. DbgEnemyMsg( this, " (%s displaced)\n", pBestEnemy->GetDebugName() );
  6549. }
  6550. iBestPriority = IRelationPriority ( pEnemy );
  6551. iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  6552. pBestEnemy = pEnemy;
  6553. bBestUnreachable = bUnreachable;
  6554. fBestSeen = TRS_NONE;
  6555. fBestVisible = TRS_NONE;
  6556. }
  6557. // If both are unreachable or both are reachable, choose enemy based on priority and distance
  6558. else if ( IRelationPriority( pEnemy ) > iBestPriority )
  6559. {
  6560. DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() );
  6561. if ( pBestEnemy )
  6562. {
  6563. DbgEnemyMsg( this, " (%s displaced due to priority, %d > %d )\n", pBestEnemy->GetDebugName(), IRelationPriority( pEnemy ), iBestPriority );
  6564. }
  6565. // this entity is disliked MORE than the entity that we
  6566. // currently think is the best visible enemy. No need to do
  6567. // a distance check, just get mad at this one for now.
  6568. iBestPriority = IRelationPriority ( pEnemy );
  6569. iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  6570. pBestEnemy = pEnemy;
  6571. bBestUnreachable = bUnreachable;
  6572. fBestSeen = TRS_NONE;
  6573. fBestVisible = TRS_NONE;
  6574. }
  6575. else if ( IRelationPriority( pEnemy ) == iBestPriority )
  6576. {
  6577. // this entity is disliked just as much as the entity that
  6578. // we currently think is the best visible enemy, so we only
  6579. // get mad at it if it is closer.
  6580. iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  6581. bool bAcceptCurrent = false;
  6582. bool bCloser = ( ( iBestDistSq - iDistSq ) > EnemyDistTolerance() );
  6583. ThreeState_t fCurSeen = TRS_NONE;
  6584. ThreeState_t fCurVisible = TRS_NONE;
  6585. // The following code is constructed in such a verbose manner to
  6586. // ensure the expensive calls only occur if absolutely needed
  6587. // If current is farther, and best has previously been confirmed as seen or visible, move on
  6588. if ( !bCloser)
  6589. {
  6590. if ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE )
  6591. {
  6592. DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
  6593. continue;
  6594. }
  6595. }
  6596. // If current is closer, and best has previously been confirmed as not seen and not visible, take it
  6597. if ( bCloser)
  6598. {
  6599. if ( fBestSeen == TRS_FALSE && fBestVisible == TRS_FALSE )
  6600. {
  6601. bAcceptCurrent = true;
  6602. }
  6603. }
  6604. if ( !bAcceptCurrent )
  6605. {
  6606. // If current is closer, and seen, take it
  6607. if ( bCloser )
  6608. {
  6609. fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6610. bAcceptCurrent = ( fCurSeen == TRS_TRUE );
  6611. }
  6612. }
  6613. if ( !bAcceptCurrent )
  6614. {
  6615. // If current is farther, and best is seen, move on
  6616. if ( !bCloser )
  6617. {
  6618. if ( fBestSeen == TRS_NONE )
  6619. {
  6620. fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6621. }
  6622. if ( fBestSeen == TRS_TRUE )
  6623. {
  6624. DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
  6625. continue;
  6626. }
  6627. }
  6628. // At this point, need to start performing expensive tests
  6629. if ( bCloser && fBestVisible == TRS_NONE )
  6630. {
  6631. // Perform shortest FVisible
  6632. fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6633. bAcceptCurrent = ( fCurVisible == TRS_TRUE );
  6634. }
  6635. // Alas, must do the most expensive comparison
  6636. if ( !bAcceptCurrent )
  6637. {
  6638. if ( fBestSeen == TRS_NONE )
  6639. {
  6640. fBestSeen = ( GetSenses()->DidSeeEntity( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6641. }
  6642. if ( fBestVisible == TRS_NONE )
  6643. {
  6644. fBestVisible = ( ( EnemyDistance( pBestEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pBestEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6645. }
  6646. if ( fCurSeen == TRS_NONE )
  6647. {
  6648. fCurSeen = ( GetSenses()->DidSeeEntity( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6649. }
  6650. if ( fCurVisible == TRS_NONE )
  6651. {
  6652. fCurVisible = ( ( EnemyDistance( pEnemy ) < GetSenses()->GetDistLook() ) && FVisible( pEnemy ) ) ? TRS_TRUE : TRS_FALSE;
  6653. }
  6654. bool bBestSeenOrVisible = ( fBestSeen == TRS_TRUE || fBestVisible == TRS_TRUE );
  6655. bool bCurSeenOrVisible = ( fCurSeen == TRS_TRUE || fCurVisible == TRS_TRUE );
  6656. if ( !bCloser)
  6657. {
  6658. if ( bBestSeenOrVisible )
  6659. {
  6660. DbgEnemyMsg( this, " %s rejected: current is closer and seen\n", pEnemy->GetDebugName() );
  6661. continue;
  6662. }
  6663. else if ( !bCurSeenOrVisible )
  6664. {
  6665. DbgEnemyMsg( this, " %s rejected: current is closer and neither is seen\n", pEnemy->GetDebugName() );
  6666. continue;
  6667. }
  6668. }
  6669. else // Closer
  6670. {
  6671. if ( !bCurSeenOrVisible && bBestSeenOrVisible )
  6672. {
  6673. DbgEnemyMsg( this, " %s rejected: current is father but seen\n", pEnemy->GetDebugName() );
  6674. continue;
  6675. }
  6676. }
  6677. }
  6678. }
  6679. DbgEnemyMsg( this, " %s accepted\n", pEnemy->GetDebugName() );
  6680. if ( pBestEnemy )
  6681. {
  6682. DbgEnemyMsg( this, " (%s displaced due to distance/visibility)\n", pBestEnemy->GetDebugName() );
  6683. }
  6684. fBestSeen = fCurSeen;
  6685. fBestVisible = fCurVisible;
  6686. iBestDistSq = iDistSq;
  6687. iBestPriority = IRelationPriority ( pEnemy );
  6688. pBestEnemy = pEnemy;
  6689. bBestUnreachable = bUnreachable;
  6690. }
  6691. else
  6692. {
  6693. DbgEnemyMsg( this, " %s rejected: lower priority\n", pEnemy->GetDebugName() );
  6694. }
  6695. }
  6696. DbgEnemyMsg( this, "} == %s\n", pBestEnemy->GetDebugName() );
  6697. return pBestEnemy;
  6698. }
  6699. //-----------------------------------------------------------------------------
  6700. // Purpose: Given a node returns the appropriate reload activity
  6701. // Input :
  6702. // Output :
  6703. //-----------------------------------------------------------------------------
  6704. Activity CAI_BaseNPC::GetReloadActivity( CAI_Hint* pHint )
  6705. {
  6706. Activity nReloadActivity = ACT_RELOAD;
  6707. if (pHint && GetEnemy()!=NULL)
  6708. {
  6709. switch (pHint->HintType())
  6710. {
  6711. case HINT_TACTICAL_COVER_LOW:
  6712. case HINT_TACTICAL_COVER_MED:
  6713. {
  6714. if (SelectWeightedSequence( ACT_RELOAD_LOW ) != ACTIVITY_NOT_AVAILABLE)
  6715. {
  6716. Vector vEyePos = GetAbsOrigin() + EyeOffset(ACT_RELOAD_LOW);
  6717. // Check if this location will block the threat's line of sight to me
  6718. trace_t tr;
  6719. AI_TraceLOS( vEyePos, GetEnemy()->EyePosition(), this, &tr );
  6720. if (tr.fraction != 1.0)
  6721. {
  6722. nReloadActivity = ACT_RELOAD_LOW;
  6723. }
  6724. }
  6725. break;
  6726. }
  6727. }
  6728. }
  6729. return nReloadActivity;
  6730. }
  6731. //-----------------------------------------------------------------------------
  6732. // Purpose: Given a node returns the appropriate cover activity
  6733. // Input :
  6734. // Output :
  6735. //-----------------------------------------------------------------------------
  6736. Activity CAI_BaseNPC::GetCoverActivity( CAI_Hint *pHint )
  6737. {
  6738. Activity nCoverActivity = ACT_INVALID;
  6739. // ---------------------------------------------------------------
  6740. // Check if hint node specifies different cover type
  6741. // ---------------------------------------------------------------
  6742. if (pHint)
  6743. {
  6744. switch (pHint->HintType())
  6745. {
  6746. case HINT_TACTICAL_COVER_MED:
  6747. {
  6748. nCoverActivity = ACT_COVER_MED;
  6749. break;
  6750. }
  6751. case HINT_TACTICAL_COVER_LOW:
  6752. {
  6753. nCoverActivity = ACT_COVER_LOW;
  6754. break;
  6755. }
  6756. }
  6757. }
  6758. if ( nCoverActivity == ACT_INVALID )
  6759. nCoverActivity = ACT_COVER;
  6760. return nCoverActivity;
  6761. }
  6762. //=========================================================
  6763. // CalcIdealYaw - gets a yaw value for the caller that would
  6764. // face the supplied vector. Value is stuffed into the npc's
  6765. // ideal_yaw
  6766. //=========================================================
  6767. float CAI_BaseNPC::CalcIdealYaw( const Vector &vecTarget )
  6768. {
  6769. Vector vecProjection;
  6770. // strafing npc needs to face 90 degrees away from its goal
  6771. if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_LEFT )
  6772. {
  6773. vecProjection.x = -vecTarget.y;
  6774. vecProjection.y = vecTarget.x;
  6775. vecProjection.z = 0;
  6776. return UTIL_VecToYaw( vecProjection - GetLocalOrigin() );
  6777. }
  6778. else if ( GetNavigator()->GetMovementActivity() == ACT_STRAFE_RIGHT )
  6779. {
  6780. vecProjection.x = vecTarget.y;
  6781. vecProjection.y = vecTarget.x;
  6782. vecProjection.z = 0;
  6783. return UTIL_VecToYaw( vecProjection - GetLocalOrigin() );
  6784. }
  6785. else
  6786. {
  6787. return UTIL_VecToYaw ( vecTarget - GetLocalOrigin() );
  6788. }
  6789. }
  6790. //=========================================================
  6791. // SetEyePosition
  6792. //
  6793. // queries the npc's model for $eyeposition and copies
  6794. // that vector to the npc's m_vDefaultEyeOffset and m_vecViewOffset
  6795. //
  6796. //=========================================================
  6797. void CAI_BaseNPC::SetDefaultEyeOffset ( void )
  6798. {
  6799. if ( GetModelPtr() )
  6800. {
  6801. GetEyePosition( GetModelPtr(), m_vDefaultEyeOffset );
  6802. if ( m_vDefaultEyeOffset == vec3_origin )
  6803. {
  6804. if ( Classify() != CLASS_NONE )
  6805. {
  6806. DevMsg( "WARNING: %s(%s) has no eye offset in .qc!\n", GetClassname(), STRING(GetModelName()) );
  6807. }
  6808. VectorAdd( WorldAlignMins(), WorldAlignMaxs(), m_vDefaultEyeOffset );
  6809. m_vDefaultEyeOffset *= 0.75;
  6810. }
  6811. }
  6812. else
  6813. m_vDefaultEyeOffset = vec3_origin;
  6814. SetViewOffset( m_vDefaultEyeOffset );
  6815. }
  6816. //------------------------------------------------------------------------------
  6817. // Purpose : Returns eye offset for an NPC for the given activity
  6818. // Input :
  6819. // Output :
  6820. //------------------------------------------------------------------------------
  6821. Vector CAI_BaseNPC::EyeOffset( Activity nActivity )
  6822. {
  6823. if ( CapabilitiesGet() & bits_CAP_DUCK )
  6824. {
  6825. if ( IsCrouchedActivity( nActivity ) )
  6826. return GetCrouchEyeOffset();
  6827. }
  6828. // if the hint doesn't tell anything, assume current state
  6829. if ( IsCrouching() )
  6830. return GetCrouchEyeOffset();
  6831. return m_vDefaultEyeOffset * GetModelScale();
  6832. }
  6833. //-----------------------------------------------------------------------------
  6834. // Purpose:
  6835. // Output : Vector
  6836. //-----------------------------------------------------------------------------
  6837. Vector CAI_BaseNPC::EyePosition( void )
  6838. {
  6839. if ( IsCrouching() )
  6840. return GetAbsOrigin() + GetCrouchEyeOffset();
  6841. return BaseClass::EyePosition();
  6842. }
  6843. //------------------------------------------------------------------------------
  6844. // Purpose :
  6845. // Input :
  6846. // Output :
  6847. //------------------------------------------------------------------------------
  6848. void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent )
  6849. {
  6850. // UNDONE: Share this code into CBaseAnimating as appropriate?
  6851. switch( pEvent->event )
  6852. {
  6853. case SCRIPT_EVENT_DEAD:
  6854. if ( m_NPCState == NPC_STATE_SCRIPT )
  6855. {
  6856. m_lifeState = LIFE_DYING;
  6857. // Kill me now! (and fade out when CineCleanup() is called)
  6858. #if _DEBUG
  6859. DevMsg( 2, "Death event: %s\n", GetClassname() );
  6860. #endif
  6861. m_iHealth = 0;
  6862. }
  6863. #if _DEBUG
  6864. else
  6865. DevWarning( 2, "INVALID death event:%s\n", GetClassname() );
  6866. #endif
  6867. break;
  6868. case SCRIPT_EVENT_NOT_DEAD:
  6869. if ( m_NPCState == NPC_STATE_SCRIPT )
  6870. {
  6871. m_lifeState = LIFE_ALIVE;
  6872. // This is for life/death sequences where the player can determine whether a character is dead or alive after the script
  6873. m_iHealth = m_iMaxHealth;
  6874. }
  6875. break;
  6876. case SCRIPT_EVENT_SOUND: // Play a named wave file
  6877. {
  6878. EmitSound( pEvent->options );
  6879. }
  6880. break;
  6881. case SCRIPT_EVENT_SOUND_VOICE:
  6882. {
  6883. EmitSound( pEvent->options );
  6884. }
  6885. break;
  6886. case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time
  6887. if (random->RandomInt(0,2) == 0)
  6888. break;
  6889. // fall through...
  6890. case SCRIPT_EVENT_SENTENCE: // Play a named sentence group
  6891. SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, SNDLVL_TALKING, 0, 100 );
  6892. break;
  6893. case SCRIPT_EVENT_FIREEVENT:
  6894. {
  6895. //
  6896. // Fire a script event. The number of the script event to fire is in the options string.
  6897. //
  6898. if ( m_hCine != NULL )
  6899. {
  6900. m_hCine->FireScriptEvent( atoi( pEvent->options ) );
  6901. }
  6902. else
  6903. {
  6904. // FIXME: look so see if it's playing a vcd and fire those instead
  6905. // AssertOnce( 0 );
  6906. }
  6907. break;
  6908. }
  6909. case SCRIPT_EVENT_FIRE_INPUT:
  6910. {
  6911. variant_t emptyVariant;
  6912. this->AcceptInput( pEvent->options, this, this, emptyVariant, 0 );
  6913. break;
  6914. }
  6915. case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on
  6916. if ( m_hCine )
  6917. m_hCine->AllowInterrupt( false );
  6918. break;
  6919. case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now
  6920. if ( m_hCine )
  6921. m_hCine->AllowInterrupt( true );
  6922. break;
  6923. #if 0
  6924. case SCRIPT_EVENT_INAIR: // Don't engine->DropToFloor()
  6925. case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to
  6926. break;
  6927. #endif
  6928. case SCRIPT_EVENT_BODYGROUPON:
  6929. case SCRIPT_EVENT_BODYGROUPOFF:
  6930. case SCRIPT_EVENT_BODYGROUPTEMP:
  6931. DevMsg( "Bodygroup!\n" );
  6932. break;
  6933. case AE_NPC_ATTACK_BROADCAST:
  6934. break;
  6935. case NPC_EVENT_BODYDROP_HEAVY:
  6936. if ( GetFlags() & FL_ONGROUND )
  6937. {
  6938. EmitSound( "AI_BaseNPC.BodyDrop_Heavy" );
  6939. }
  6940. break;
  6941. case NPC_EVENT_BODYDROP_LIGHT:
  6942. if ( GetFlags() & FL_ONGROUND )
  6943. {
  6944. EmitSound( "AI_BaseNPC.BodyDrop_Light" );
  6945. }
  6946. break;
  6947. case NPC_EVENT_SWISHSOUND:
  6948. {
  6949. // NO NPC may use this anim event unless that npc's precache precaches this sound!!!
  6950. EmitSound( "AI_BaseNPC.SwishSound" );
  6951. break;
  6952. }
  6953. case NPC_EVENT_180TURN:
  6954. {
  6955. //DevMsg( "Turned!\n" );
  6956. SetIdealActivity( ACT_IDLE );
  6957. Forget( bits_MEMORY_TURNING );
  6958. SetBoneController( 0, GetLocalAngles().y );
  6959. IncrementInterpolationFrame();
  6960. break;
  6961. }
  6962. case NPC_EVENT_ITEM_PICKUP:
  6963. {
  6964. CBaseEntity *pPickup = NULL;
  6965. //
  6966. // Figure out what we're supposed to pick up.
  6967. //
  6968. if ( pEvent->options && strlen( pEvent->options ) > 0 )
  6969. {
  6970. // Pick up the weapon or item that was specified in the anim event.
  6971. pPickup = gEntList.FindEntityGenericNearest( pEvent->options, GetAbsOrigin(), 256, this );
  6972. }
  6973. else
  6974. {
  6975. // Pick up the weapon or item that was found earlier and cached in our target pointer.
  6976. pPickup = GetTarget();
  6977. }
  6978. // Make sure we found something to pick up.
  6979. if ( !pPickup )
  6980. {
  6981. TaskFail("Item no longer available!\n");
  6982. break;
  6983. }
  6984. // Make sure the item hasn't moved.
  6985. float flDist = ( pPickup->WorldSpaceCenter() - GetAbsOrigin() ).Length2D();
  6986. if ( flDist > ITEM_PICKUP_TOLERANCE )
  6987. {
  6988. TaskFail("Item has moved!\n");
  6989. break;
  6990. }
  6991. CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>( pPickup );
  6992. if ( pWeapon )
  6993. {
  6994. // Picking up a weapon.
  6995. CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
  6996. if ( pOwner )
  6997. {
  6998. TaskFail( "Weapon in use by someone else" );
  6999. }
  7000. else if ( !pWeapon )
  7001. {
  7002. TaskFail( "Weapon doesn't exist" );
  7003. }
  7004. else if (!Weapon_CanUse( pWeapon ))
  7005. {
  7006. TaskFail( "Can't use this weapon type" );
  7007. }
  7008. else
  7009. {
  7010. PickupWeapon( pWeapon );
  7011. TaskComplete();
  7012. break;
  7013. }
  7014. }
  7015. else
  7016. {
  7017. // Picking up an item.
  7018. PickupItem( pPickup );
  7019. TaskComplete();
  7020. }
  7021. break;
  7022. }
  7023. case NPC_EVENT_WEAPON_SET_SEQUENCE_NUMBER:
  7024. {
  7025. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7026. if ((pWeapon) && (pEvent->options))
  7027. {
  7028. int nSequence = atoi(pEvent->options);
  7029. if (nSequence != -1)
  7030. {
  7031. pWeapon->ResetSequence(nSequence);
  7032. }
  7033. }
  7034. break;
  7035. }
  7036. case NPC_EVENT_WEAPON_SET_SEQUENCE_NAME:
  7037. {
  7038. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7039. if ((pWeapon) && (pEvent->options))
  7040. {
  7041. int nSequence = pWeapon->LookupSequence(pEvent->options);
  7042. if (nSequence != -1)
  7043. {
  7044. pWeapon->ResetSequence(nSequence);
  7045. }
  7046. }
  7047. break;
  7048. }
  7049. case NPC_EVENT_WEAPON_SET_ACTIVITY:
  7050. {
  7051. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7052. if ((pWeapon) && (pEvent->options))
  7053. {
  7054. Activity act = (Activity)pWeapon->LookupActivity(pEvent->options);
  7055. if (act != ACT_INVALID)
  7056. {
  7057. // FIXME: where should the duration come from? normally it would come from the current sequence
  7058. Weapon_SetActivity(act, 0);
  7059. }
  7060. }
  7061. break;
  7062. }
  7063. case NPC_EVENT_WEAPON_DROP:
  7064. {
  7065. //
  7066. // Drop our active weapon (or throw it at the specified target entity).
  7067. //
  7068. CBaseEntity *pTarget = NULL;
  7069. if (pEvent->options)
  7070. {
  7071. pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this);
  7072. }
  7073. if (pTarget)
  7074. {
  7075. Vector vecTargetPos = pTarget->WorldSpaceCenter();
  7076. Weapon_Drop(GetActiveWeapon(), &vecTargetPos);
  7077. }
  7078. else
  7079. {
  7080. Weapon_Drop(GetActiveWeapon());
  7081. }
  7082. break;
  7083. }
  7084. case EVENT_WEAPON_RELOAD:
  7085. {
  7086. if ( GetActiveWeapon() )
  7087. {
  7088. GetActiveWeapon()->WeaponSound( RELOAD_NPC );
  7089. GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
  7090. ClearCondition(COND_LOW_PRIMARY_AMMO);
  7091. ClearCondition(COND_NO_PRIMARY_AMMO);
  7092. ClearCondition(COND_NO_SECONDARY_AMMO);
  7093. }
  7094. break;
  7095. }
  7096. case EVENT_WEAPON_RELOAD_SOUND:
  7097. {
  7098. if ( GetActiveWeapon() )
  7099. {
  7100. GetActiveWeapon()->WeaponSound( RELOAD_NPC );
  7101. }
  7102. break;
  7103. }
  7104. case EVENT_WEAPON_RELOAD_FILL_CLIP:
  7105. {
  7106. if ( GetActiveWeapon() )
  7107. {
  7108. GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
  7109. ClearCondition(COND_LOW_PRIMARY_AMMO);
  7110. ClearCondition(COND_NO_PRIMARY_AMMO);
  7111. ClearCondition(COND_NO_SECONDARY_AMMO);
  7112. }
  7113. break;
  7114. }
  7115. case NPC_EVENT_LEFTFOOT:
  7116. case NPC_EVENT_RIGHTFOOT:
  7117. // For right now, do nothing. All functionality for this lives in individual npcs.
  7118. break;
  7119. case NPC_EVENT_OPEN_DOOR:
  7120. {
  7121. CBasePropDoor *pDoor = (CBasePropDoor *)(CBaseEntity *)GetNavigator()->GetPath()->GetCurWaypoint()->GetEHandleData();
  7122. if (pDoor != NULL)
  7123. {
  7124. OpenPropDoorNow( pDoor );
  7125. }
  7126. break;
  7127. }
  7128. default:
  7129. if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_SERVER))
  7130. {
  7131. if (pEvent->event == AE_NPC_HOLSTER)
  7132. {
  7133. // Cache off the weapon.
  7134. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7135. Assert( pWeapon != NULL );
  7136. GetActiveWeapon()->Holster();
  7137. SetActiveWeapon( NULL );
  7138. //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.
  7139. GetNavigator()->SetArrivalSequence( ACT_INVALID );
  7140. if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY )
  7141. {
  7142. // Get rid of it!
  7143. UTIL_Remove( pWeapon );
  7144. }
  7145. if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE )
  7146. {
  7147. m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
  7148. m_Activity = ACT_RESET;
  7149. }
  7150. return;
  7151. }
  7152. else if (pEvent->event == AE_NPC_DRAW)
  7153. {
  7154. if (GetActiveWeapon())
  7155. {
  7156. GetActiveWeapon()->Deploy();
  7157. //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now.
  7158. GetNavigator()->SetArrivalSequence( ACT_INVALID );
  7159. if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE )
  7160. {
  7161. m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE;
  7162. m_Activity = ACT_RESET;
  7163. }
  7164. }
  7165. return;
  7166. }
  7167. else if ( pEvent->event == AE_NPC_BODYDROP_HEAVY )
  7168. {
  7169. if ( GetFlags() & FL_ONGROUND )
  7170. {
  7171. EmitSound( "AI_BaseNPC.BodyDrop_Heavy" );
  7172. }
  7173. return;
  7174. }
  7175. else if ( pEvent->event == AE_NPC_LEFTFOOT || pEvent->event == AE_NPC_RIGHTFOOT )
  7176. {
  7177. return;
  7178. }
  7179. else if ( pEvent->event == AE_NPC_RAGDOLL )
  7180. {
  7181. // Convert to ragdoll immediately
  7182. BecomeRagdollOnClient( vec3_origin );
  7183. return;
  7184. }
  7185. else if ( pEvent->event == AE_NPC_ADDGESTURE )
  7186. {
  7187. Activity act = ( Activity )LookupActivity( pEvent->options );
  7188. if (act != ACT_INVALID)
  7189. {
  7190. act = TranslateActivity( act );
  7191. if (act != ACT_INVALID)
  7192. {
  7193. AddGesture( act );
  7194. }
  7195. }
  7196. return;
  7197. }
  7198. else if ( pEvent->event == AE_NPC_RESTARTGESTURE )
  7199. {
  7200. Activity act = ( Activity )LookupActivity( pEvent->options );
  7201. if (act != ACT_INVALID)
  7202. {
  7203. act = TranslateActivity( act );
  7204. if (act != ACT_INVALID)
  7205. {
  7206. RestartGesture( act );
  7207. }
  7208. }
  7209. return;
  7210. }
  7211. else if ( pEvent->event == AE_NPC_WEAPON_DROP )
  7212. {
  7213. // Drop our active weapon (or throw it at the specified target entity).
  7214. CBaseEntity *pTarget = NULL;
  7215. if (pEvent->options)
  7216. {
  7217. pTarget = gEntList.FindEntityGeneric(NULL, pEvent->options, this);
  7218. }
  7219. if (pTarget)
  7220. {
  7221. Vector vecTargetPos = pTarget->WorldSpaceCenter();
  7222. Weapon_Drop(GetActiveWeapon(), &vecTargetPos);
  7223. }
  7224. else
  7225. {
  7226. Weapon_Drop(GetActiveWeapon());
  7227. }
  7228. return;
  7229. }
  7230. else if ( pEvent->event == AE_NPC_WEAPON_SET_ACTIVITY )
  7231. {
  7232. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  7233. if ((pWeapon) && (pEvent->options))
  7234. {
  7235. Activity act = (Activity)pWeapon->LookupActivity(pEvent->options);
  7236. if (act == ACT_INVALID)
  7237. {
  7238. // Try and translate it
  7239. act = Weapon_TranslateActivity( (Activity)CAI_BaseNPC::GetActivityID(pEvent->options), NULL );
  7240. }
  7241. if (act != ACT_INVALID)
  7242. {
  7243. // FIXME: where should the duration come from? normally it would come from the current sequence
  7244. Weapon_SetActivity(act, 0);
  7245. }
  7246. }
  7247. return;
  7248. }
  7249. else if ( pEvent->event == AE_NPC_SET_INTERACTION_CANTDIE )
  7250. {
  7251. SetInteractionCantDie( (atoi(pEvent->options) != 0) );
  7252. return;
  7253. }
  7254. else if ( pEvent->event == AE_NPC_HURT_INTERACTION_PARTNER )
  7255. {
  7256. // If we're currently interacting with an enemy, hurt them/me
  7257. if ( m_hInteractionPartner )
  7258. {
  7259. CAI_BaseNPC *pTarget = NULL;
  7260. CAI_BaseNPC *pAttacker = NULL;
  7261. if ( pEvent->options )
  7262. {
  7263. char szEventOptions[128];
  7264. Q_strncpy( szEventOptions, pEvent->options, sizeof(szEventOptions) );
  7265. char *pszParam = strtok( szEventOptions, " " );
  7266. if ( pszParam )
  7267. {
  7268. if ( !Q_strncmp( pszParam, "ME", 2 ) )
  7269. {
  7270. pTarget = this;
  7271. pAttacker = m_hInteractionPartner;
  7272. }
  7273. else if ( !Q_strncmp( pszParam, "THEM", 4 ) )
  7274. {
  7275. pAttacker = this;
  7276. pTarget = m_hInteractionPartner;
  7277. }
  7278. pszParam = strtok(NULL," ");
  7279. if ( pAttacker && pTarget && pszParam )
  7280. {
  7281. int iDamage = atoi( pszParam );
  7282. if ( iDamage )
  7283. {
  7284. // We've got a target, and damage. Now hurt them.
  7285. CTakeDamageInfo info;
  7286. info.SetDamage( iDamage );
  7287. info.SetAttacker( pAttacker );
  7288. info.SetInflictor( pAttacker );
  7289. info.SetDamageType( DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE );
  7290. pTarget->TakeDamage( info );
  7291. return;
  7292. }
  7293. }
  7294. }
  7295. }
  7296. // Bad data. Explain how to use this anim event.
  7297. const char *pName = EventList_NameForIndex( pEvent->event );
  7298. DevWarning( 1, "Bad %s format. Should be: { AE_NPC_HURT_INTERACTION_PARTNER <frame number> \"<ME/THEM> <Amount of damage done>\" }\n", pName );
  7299. return;
  7300. }
  7301. DevWarning( "%s received AE_NPC_HURT_INTERACTION_PARTNER anim event, but it's not interacting with anything.\n", GetDebugName() );
  7302. return;
  7303. }
  7304. }
  7305. // FIXME: why doesn't this code pass unhandled events down to its parent?
  7306. // Came from my weapon?
  7307. //Adrian I'll clean this up once the old event system is phased out.
  7308. if ( pEvent->pSource != this || ( pEvent->type & AE_TYPE_NEWEVENTSYSTEM && pEvent->type & AE_TYPE_WEAPON ) || (pEvent->event >= EVENT_WEAPON && pEvent->event <= EVENT_WEAPON_LAST) )
  7309. {
  7310. Weapon_HandleAnimEvent( pEvent );
  7311. }
  7312. else
  7313. {
  7314. BaseClass::HandleAnimEvent( pEvent );
  7315. }
  7316. break;
  7317. }
  7318. }
  7319. //-----------------------------------------------------------------------------
  7320. // Purpose: Override base class to add display of routes
  7321. // Input :
  7322. // Output : Current text offset from the top
  7323. //-----------------------------------------------------------------------------
  7324. void CAI_BaseNPC::DrawDebugGeometryOverlays(void)
  7325. {
  7326. // Handy for debug
  7327. //NDebugOverlay::Cross3D(EyePosition(),Vector(-2,-2,-2),Vector(2,2,2),0,255,0,true);
  7328. // ------------------------------
  7329. // Remove me if requested
  7330. // ------------------------------
  7331. if (m_debugOverlays & OVERLAY_NPC_ZAP_BIT)
  7332. {
  7333. VacateStrategySlot();
  7334. Weapon_Drop( GetActiveWeapon() );
  7335. m_iHealth = 0;
  7336. SetThink( &CAI_BaseNPC::SUB_Remove );
  7337. }
  7338. // ------------------------------
  7339. // properly kill an NPC.
  7340. // ------------------------------
  7341. if (m_debugOverlays & OVERLAY_NPC_KILL_BIT)
  7342. {
  7343. CTakeDamageInfo info;
  7344. info.SetDamage( m_iHealth );
  7345. info.SetAttacker( this );
  7346. info.SetInflictor( ( AI_IsSinglePlayer() ) ? (CBaseEntity *)AI_GetSinglePlayer() : (CBaseEntity *)this );
  7347. info.SetDamageType( DMG_GENERIC );
  7348. m_debugOverlays &= ~OVERLAY_NPC_KILL_BIT;
  7349. TakeDamage( info );
  7350. return;
  7351. }
  7352. // ------------------------------
  7353. // Draw route if requested
  7354. // ------------------------------
  7355. if ((m_debugOverlays & OVERLAY_NPC_ROUTE_BIT))
  7356. {
  7357. GetNavigator()->DrawDebugRouteOverlay();
  7358. if ( IsMoving() )
  7359. {
  7360. float yaw = GetMotor()->GetIdealYaw();
  7361. Vector vecYaw = UTIL_YawToVector(yaw);
  7362. NDebugOverlay::Line(WorldSpaceCenter(),WorldSpaceCenter() + vecYaw * GetHullWidth() * .5,255,255,255,true,0.0);
  7363. }
  7364. }
  7365. if (!(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) && (IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN)))
  7366. {
  7367. NDebugOverlay::Box(m_vecLastPosition, Vector(-5,-5,-5),Vector(5,5,5), 255, 0, 255, 0, 0);
  7368. NDebugOverlay::HorzArrow( GetAbsOrigin(), m_vecLastPosition, 16, 255, 0, 255, 64, true, 0 );
  7369. }
  7370. // ------------------------------
  7371. // Draw red box around if selected
  7372. // ------------------------------
  7373. if ((m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) && !ai_no_select_box.GetBool())
  7374. {
  7375. NDebugOverlay::EntityBounds(this, 255, 0, 0, 20, 0);
  7376. }
  7377. // ------------------------------
  7378. // Draw nearest node if selected
  7379. // ------------------------------
  7380. if ((m_debugOverlays & OVERLAY_NPC_NEAREST_BIT))
  7381. {
  7382. int iNodeID = GetPathfinder()->NearestNodeToNPC();
  7383. if (iNodeID != NO_NODE)
  7384. {
  7385. NDebugOverlay::Box(GetNavigator()->GetNetwork()->AccessNodes()[iNodeID]->GetPosition(GetHullType()), Vector(-10,-10,-10),Vector(10,10,10), 255, 255, 255, 0, 0);
  7386. }
  7387. }
  7388. // ------------------------------
  7389. // Draw viewcone if selected
  7390. // ------------------------------
  7391. if ((m_debugOverlays & OVERLAY_NPC_VIEWCONE_BIT))
  7392. {
  7393. float flViewRange = acos(m_flFieldOfView);
  7394. Vector vEyeDir = EyeDirection2D( );
  7395. Vector vLeftDir, vRightDir;
  7396. float fSin, fCos;
  7397. SinCos( flViewRange, &fSin, &fCos );
  7398. vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
  7399. vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
  7400. vLeftDir.z = vEyeDir.z;
  7401. fSin = sin(-flViewRange);
  7402. fCos = cos(-flViewRange);
  7403. vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
  7404. vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
  7405. vRightDir.z = vEyeDir.z;
  7406. // Visualize it
  7407. NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vLeftDir * 200 ), 64, 255, 0, 0, 50, false, 0 );
  7408. NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vRightDir * 200 ), 64, 255, 0, 0, 50, false, 0 );
  7409. NDebugOverlay::VertArrow( EyePosition(), EyePosition() + ( vEyeDir * 100 ), 8, 0, 255, 0, 50, false, 0 );
  7410. NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, 128, 0 );
  7411. }
  7412. // ----------------------------------------------
  7413. // Draw the relationships for this NPC to others
  7414. // ----------------------------------------------
  7415. if ( m_debugOverlays & OVERLAY_NPC_RELATION_BIT )
  7416. {
  7417. // Show the relationships to entities around us
  7418. int r = 0;
  7419. int g = 0;
  7420. int b = 0;
  7421. int nRelationship;
  7422. CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  7423. // Rate all NPCs
  7424. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  7425. {
  7426. if ( ppAIs[i] == NULL || ppAIs[i] == this )
  7427. continue;
  7428. // Get our relation to the target
  7429. nRelationship = IRelationType( ppAIs[i] );
  7430. // Get the color for the arrow
  7431. UTIL_GetDebugColorForRelationship( nRelationship, r, g, b );
  7432. // Draw an arrow
  7433. NDebugOverlay::HorzArrow( GetAbsOrigin(), ppAIs[i]->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f );
  7434. }
  7435. // Also include all players
  7436. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  7437. {
  7438. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  7439. if ( pPlayer == NULL )
  7440. continue;
  7441. // Get our relation to the target
  7442. nRelationship = IRelationType( pPlayer );
  7443. // Get the color for the arrow
  7444. UTIL_GetDebugColorForRelationship( nRelationship, r, g, b );
  7445. // Draw an arrow
  7446. NDebugOverlay::HorzArrow( GetAbsOrigin(), pPlayer->GetAbsOrigin(), 16, r, g, b, 64, true, 0.0f );
  7447. }
  7448. }
  7449. // ------------------------------
  7450. // Draw enemies if selected
  7451. // ------------------------------
  7452. if ((m_debugOverlays & OVERLAY_NPC_ENEMIES_BIT))
  7453. {
  7454. AIEnemiesIter_t iter;
  7455. for( AI_EnemyInfo_t *eMemory = GetEnemies()->GetFirst(&iter); eMemory != NULL; eMemory = GetEnemies()->GetNext(&iter) )
  7456. {
  7457. if (eMemory->hEnemy)
  7458. {
  7459. CBaseCombatCharacter *npcEnemy = (eMemory->hEnemy)->MyCombatCharacterPointer();
  7460. if (npcEnemy)
  7461. {
  7462. float r,g,b;
  7463. char debugText[255];
  7464. debugText[0] = NULL;
  7465. if (npcEnemy == GetEnemy())
  7466. {
  7467. Q_strncat(debugText,"Current Enemy", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7468. }
  7469. else if (npcEnemy == GetTarget())
  7470. {
  7471. Q_strncat(debugText,"Current Target", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7472. }
  7473. else
  7474. {
  7475. Q_strncat(debugText,"Other Memory", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7476. }
  7477. if (IsUnreachable(npcEnemy))
  7478. {
  7479. Q_strncat(debugText," (Unreachable)", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7480. }
  7481. if (eMemory->bEludedMe)
  7482. {
  7483. Q_strncat(debugText," (Eluded)", sizeof( debugText ), COPY_ALL_CHARACTERS );
  7484. }
  7485. // Unreachable enemy drawn in green
  7486. if (IsUnreachable(npcEnemy))
  7487. {
  7488. r = 0;
  7489. g = 255;
  7490. b = 0;
  7491. }
  7492. // Eluded enemy drawn in blue
  7493. else if (eMemory->bEludedMe)
  7494. {
  7495. r = 0;
  7496. g = 0;
  7497. b = 255;
  7498. }
  7499. // Current enemy drawn in red
  7500. else if (npcEnemy == GetEnemy())
  7501. {
  7502. r = 255;
  7503. g = 0;
  7504. b = 0;
  7505. }
  7506. // Current traget drawn in magenta
  7507. else if (npcEnemy == GetTarget())
  7508. {
  7509. r = 255;
  7510. g = 0;
  7511. b = 255;
  7512. }
  7513. // All other enemies drawn in pink
  7514. else
  7515. {
  7516. r = 255;
  7517. g = 100;
  7518. b = 100;
  7519. }
  7520. Vector drawPos = eMemory->vLastKnownLocation;
  7521. NDebugOverlay::Text( drawPos, debugText, false, 0.0 );
  7522. // If has a line on the player draw cross slightly in front so player can see
  7523. if (npcEnemy->IsPlayer() &&
  7524. (eMemory->vLastKnownLocation - npcEnemy->GetAbsOrigin()).Length()<10 )
  7525. {
  7526. Vector vEnemyFacing = npcEnemy->BodyDirection2D( );
  7527. Vector eyePos = npcEnemy->EyePosition() + vEnemyFacing*10.0;
  7528. Vector upVec = Vector(0,0,2);
  7529. Vector sideVec;
  7530. CrossProduct( vEnemyFacing, upVec, sideVec);
  7531. NDebugOverlay::Line(eyePos+sideVec+upVec, eyePos-sideVec-upVec, r,g,b, false,0);
  7532. NDebugOverlay::Line(eyePos+sideVec-upVec, eyePos-sideVec+upVec, r,g,b, false,0);
  7533. NDebugOverlay::Text( eyePos, debugText, false, 0.0 );
  7534. }
  7535. else
  7536. {
  7537. NDebugOverlay::Cross3D(drawPos,NAI_Hull::Mins(npcEnemy->GetHullType()),NAI_Hull::Maxs(npcEnemy->GetHullType()),r,g,b,false,0);
  7538. }
  7539. }
  7540. }
  7541. }
  7542. }
  7543. // ----------------------------------------------
  7544. // Draw line to target and enemy entity if exist
  7545. // ----------------------------------------------
  7546. if ((m_debugOverlays & OVERLAY_NPC_FOCUS_BIT))
  7547. {
  7548. if (GetEnemy() != NULL)
  7549. {
  7550. NDebugOverlay::Line(EyePosition(),GetEnemy()->EyePosition(),255,0,0,true,0.0);
  7551. }
  7552. if (GetTarget() != NULL)
  7553. {
  7554. NDebugOverlay::Line(EyePosition(),GetTarget()->EyePosition(),0,0,255,true,0.0);
  7555. }
  7556. }
  7557. GetPathfinder()->DrawDebugGeometryOverlays(m_debugOverlays);
  7558. CBaseEntity::DrawDebugGeometryOverlays();
  7559. }
  7560. //-----------------------------------------------------------------------------
  7561. // Purpose: Draw any debug text overlays
  7562. // Input :
  7563. // Output : Current text offset from the top
  7564. //-----------------------------------------------------------------------------
  7565. int CAI_BaseNPC::DrawDebugTextOverlays(void)
  7566. {
  7567. int text_offset = 0;
  7568. // ---------------------
  7569. // Print Baseclass text
  7570. // ---------------------
  7571. text_offset = BaseClass::DrawDebugTextOverlays();
  7572. if (m_debugOverlays & OVERLAY_NPC_SQUAD_BIT)
  7573. {
  7574. // Print health
  7575. char tempstr[512];
  7576. Q_snprintf(tempstr,sizeof(tempstr),"Health: %i",m_iHealth.Get());
  7577. EntityText(text_offset,tempstr,0);
  7578. text_offset++;
  7579. // Print squad name
  7580. Q_strncpy(tempstr,"Squad: ",sizeof(tempstr));
  7581. if (m_pSquad)
  7582. {
  7583. Q_strncat(tempstr,m_pSquad->GetName(),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7584. if( m_pSquad->GetLeader() == this )
  7585. {
  7586. Q_strncat(tempstr," (LEADER)",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7587. }
  7588. Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7589. }
  7590. else
  7591. {
  7592. Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7593. }
  7594. EntityText(text_offset,tempstr,0);
  7595. text_offset++;
  7596. // Print enemy name
  7597. Q_strncpy(tempstr,"Enemy: ",sizeof(tempstr));
  7598. if (GetEnemy())
  7599. {
  7600. if (GetEnemy()->GetEntityName() != NULL_STRING)
  7601. {
  7602. Q_strncat(tempstr,STRING(GetEnemy()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7603. Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7604. }
  7605. else
  7606. {
  7607. Q_strncat(tempstr,STRING(GetEnemy()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7608. Q_strncat(tempstr,"\n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7609. }
  7610. }
  7611. else
  7612. {
  7613. Q_strncat(tempstr," - \n",sizeof(tempstr), COPY_ALL_CHARACTERS);
  7614. }
  7615. EntityText(text_offset,tempstr,0);
  7616. text_offset++;
  7617. // Print slot
  7618. Q_snprintf(tempstr,sizeof(tempstr),"Slot: %s (%d)\n",
  7619. SquadSlotName(m_iMySquadSlot), m_iMySquadSlot);
  7620. EntityText(text_offset,tempstr,0);
  7621. text_offset++;
  7622. }
  7623. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  7624. {
  7625. char tempstr[512];
  7626. // --------------
  7627. // Print Health
  7628. // --------------
  7629. Q_snprintf(tempstr,sizeof(tempstr),"Health: %i (DACC:%1.2f)",m_iHealth.Get(), GetDamageAccumulator() );
  7630. EntityText(text_offset,tempstr,0);
  7631. text_offset++;
  7632. // --------------
  7633. // Print State
  7634. // --------------
  7635. static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" };
  7636. if ( (int)m_NPCState < ARRAYSIZE(pStateNames) )
  7637. {
  7638. Q_snprintf(tempstr,sizeof(tempstr),"Stat: %s, ", pStateNames[m_NPCState] );
  7639. EntityText(text_offset,tempstr,0);
  7640. text_offset++;
  7641. }
  7642. // -----------------
  7643. // Start Scripting?
  7644. // -----------------
  7645. if( IsInAScript() )
  7646. {
  7647. Q_snprintf(tempstr,sizeof(tempstr),"STARTSCRIPTING" );
  7648. EntityText(text_offset,tempstr,0);
  7649. text_offset++;
  7650. }
  7651. // -----------------
  7652. // Hint Group?
  7653. // -----------------
  7654. if( GetHintGroup() != NULL_STRING )
  7655. {
  7656. Q_snprintf(tempstr,sizeof(tempstr),"Hint Group: %s", STRING(GetHintGroup()) );
  7657. EntityText(text_offset,tempstr,0);
  7658. text_offset++;
  7659. }
  7660. // -----------------
  7661. // Print MotionType
  7662. // -----------------
  7663. int navTypeIndex = (int)GetNavType() + 1;
  7664. static const char *pMoveNames[] = { "None", "Ground", "Jump", "Fly", "Climb" };
  7665. Assert( navTypeIndex >= 0 && navTypeIndex < ARRAYSIZE(pMoveNames) );
  7666. if ( navTypeIndex < ARRAYSIZE(pMoveNames) )
  7667. {
  7668. Q_snprintf(tempstr,sizeof(tempstr),"Move: %s, ", pMoveNames[navTypeIndex] );
  7669. EntityText(text_offset,tempstr,0);
  7670. text_offset++;
  7671. }
  7672. // --------------
  7673. // Print Schedule
  7674. // --------------
  7675. if ( GetCurSchedule() )
  7676. {
  7677. CAI_BehaviorBase *pBehavior = GetRunningBehavior();
  7678. if ( pBehavior )
  7679. {
  7680. Q_snprintf(tempstr,sizeof(tempstr),"Behv: %s, ", pBehavior->GetName() );
  7681. EntityText(text_offset,tempstr,0);
  7682. text_offset++;
  7683. }
  7684. const char *pName = NULL;
  7685. pName = GetCurSchedule()->GetName();
  7686. if ( !pName )
  7687. {
  7688. pName = "Unknown";
  7689. }
  7690. Q_snprintf(tempstr,sizeof(tempstr),"Schd: %s, ", pName );
  7691. EntityText(text_offset,tempstr,0);
  7692. text_offset++;
  7693. if (m_debugOverlays & OVERLAY_NPC_TASK_BIT)
  7694. {
  7695. for (int i = 0 ; i < GetCurSchedule()->NumTasks(); i++)
  7696. {
  7697. Q_snprintf(tempstr,sizeof(tempstr),"%s%s%s%s",
  7698. ((i==0) ? "Task:":" "),
  7699. ((i==GetScheduleCurTaskIndex()) ? "->" :" "),
  7700. TaskName(GetCurSchedule()->GetTaskList()[i].iTask),
  7701. ((i==GetScheduleCurTaskIndex()) ? "<-" :""));
  7702. EntityText(text_offset,tempstr,0);
  7703. text_offset++;
  7704. }
  7705. }
  7706. else
  7707. {
  7708. const Task_t *pTask = GetTask();
  7709. if ( pTask )
  7710. {
  7711. Q_snprintf(tempstr,sizeof(tempstr),"Task: %s (#%d), ", TaskName(pTask->iTask), GetScheduleCurTaskIndex() );
  7712. }
  7713. else
  7714. {
  7715. Q_strncpy(tempstr,"Task: None",sizeof(tempstr));
  7716. }
  7717. EntityText(text_offset,tempstr,0);
  7718. text_offset++;
  7719. }
  7720. }
  7721. // --------------
  7722. // Print Acitivity
  7723. // --------------
  7724. if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID && m_Activity != ACT_RESET)
  7725. {
  7726. Activity iActivity = TranslateActivity( m_Activity );
  7727. Activity iIdealActivity = Weapon_TranslateActivity( m_IdealActivity );
  7728. iIdealActivity = NPC_TranslateActivity( iIdealActivity );
  7729. const char *pszActivity = GetActivityName( iActivity );
  7730. const char *pszIdealActivity = GetActivityName( iIdealActivity );
  7731. const char *pszRootActivity = GetActivityName( m_Activity );
  7732. Q_snprintf(tempstr,sizeof(tempstr),"Actv: %s (%s) [%s]\n", pszActivity, pszIdealActivity, pszRootActivity );
  7733. }
  7734. else if (m_Activity == ACT_RESET)
  7735. {
  7736. Q_strncpy(tempstr,"Actv: RESET",sizeof(tempstr) );
  7737. }
  7738. else
  7739. {
  7740. Q_strncpy(tempstr,"Actv: INVALID", sizeof(tempstr) );
  7741. }
  7742. EntityText(text_offset,tempstr,0);
  7743. text_offset++;
  7744. //
  7745. // Print all the current conditions.
  7746. //
  7747. if (m_debugOverlays & OVERLAY_NPC_CONDITIONS_BIT)
  7748. {
  7749. bool bHasConditions = false;
  7750. for (int i = 0; i < MAX_CONDITIONS; i++)
  7751. {
  7752. if (m_Conditions.IsBitSet(i))
  7753. {
  7754. Q_snprintf(tempstr, sizeof(tempstr), "Cond: %s\n", ConditionName(AI_RemapToGlobal(i)));
  7755. EntityText(text_offset, tempstr, 0);
  7756. text_offset++;
  7757. bHasConditions = true;
  7758. }
  7759. }
  7760. if (!bHasConditions)
  7761. {
  7762. Q_snprintf(tempstr,sizeof(tempstr),"(no conditions)");
  7763. EntityText(text_offset,tempstr,0);
  7764. text_offset++;
  7765. }
  7766. }
  7767. if ( GetFlags() & FL_FLY )
  7768. {
  7769. EntityText(text_offset,"HAS FL_FLY",0);
  7770. text_offset++;
  7771. }
  7772. // --------------
  7773. // Print Interrupte
  7774. // --------------
  7775. if (m_interuptSchedule)
  7776. {
  7777. const char *pName = NULL;
  7778. pName = m_interuptSchedule->GetName();
  7779. if ( !pName )
  7780. {
  7781. pName = "Unknown";
  7782. }
  7783. Q_snprintf(tempstr,sizeof(tempstr),"Intr: %s (%s)\n", pName, m_interruptText );
  7784. EntityText(text_offset,tempstr,0);
  7785. text_offset++;
  7786. }
  7787. // --------------
  7788. // Print Failure
  7789. // --------------
  7790. if (m_failedSchedule)
  7791. {
  7792. const char *pName = NULL;
  7793. pName = m_failedSchedule->GetName();
  7794. if ( !pName )
  7795. {
  7796. pName = "Unknown";
  7797. }
  7798. Q_snprintf(tempstr,sizeof(tempstr),"Fail: %s (%s)\n", pName,m_failText );
  7799. EntityText(text_offset,tempstr,0);
  7800. text_offset++;
  7801. }
  7802. // -------------------------------
  7803. // Print any important condtions
  7804. // -------------------------------
  7805. if (HasCondition(COND_ENEMY_TOO_FAR))
  7806. {
  7807. EntityText(text_offset,"Enemy too far to attack",0);
  7808. text_offset++;
  7809. }
  7810. if ( GetAbsVelocity() != vec3_origin || GetLocalAngularVelocity() != vec3_angle )
  7811. {
  7812. char tmp[512];
  7813. Q_snprintf( tmp, sizeof(tmp), "Vel %.1f %.1f %.1f Ang: %.1f %.1f %.1f\n",
  7814. GetAbsVelocity().x, GetAbsVelocity().y, GetAbsVelocity().z,
  7815. GetLocalAngularVelocity().x, GetLocalAngularVelocity().y, GetLocalAngularVelocity().z );
  7816. EntityText(text_offset,tmp,0);
  7817. text_offset++;
  7818. }
  7819. // -------------------------------
  7820. // Print shot accuracy
  7821. // -------------------------------
  7822. if ( m_LastShootAccuracy != -1 && ai_shot_stats.GetBool() )
  7823. {
  7824. CFmtStr msg;
  7825. EntityText(text_offset,msg.sprintf("Cur Accuracy: %.1f", m_LastShootAccuracy),0);
  7826. text_offset++;
  7827. if ( m_TotalShots )
  7828. {
  7829. EntityText(text_offset,msg.sprintf("Act Accuracy: %.1f", ((float)m_TotalHits/(float)m_TotalShots)*100.0),0);
  7830. text_offset++;
  7831. }
  7832. if ( GetActiveWeapon() && GetEnemy() )
  7833. {
  7834. Vector curSpread = GetAttackSpread(GetActiveWeapon(), GetEnemy());
  7835. float curCone = RAD2DEG(asin(curSpread.x)) * 2;
  7836. float bias = GetSpreadBias( GetActiveWeapon(), GetEnemy());
  7837. EntityText(text_offset,msg.sprintf("Cone %.1f, Bias %.2f", curCone, bias),0);
  7838. text_offset++;
  7839. }
  7840. }
  7841. if ( GetGoalEnt() && GetNavigator()->GetGoalType() == GOALTYPE_PATHCORNER )
  7842. {
  7843. Q_strncpy(tempstr,"Pathcorner/goal ent: ",sizeof(tempstr));
  7844. if (GetGoalEnt()->GetEntityName() != NULL_STRING)
  7845. {
  7846. Q_strncat(tempstr,STRING(GetGoalEnt()->GetEntityName()),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7847. }
  7848. else
  7849. {
  7850. Q_strncat(tempstr,STRING(GetGoalEnt()->m_iClassname),sizeof(tempstr), COPY_ALL_CHARACTERS);
  7851. }
  7852. EntityText(text_offset, tempstr, 0);
  7853. text_offset++;
  7854. }
  7855. if ( VPhysicsGetObject() )
  7856. {
  7857. vphysics_objectstress_t stressOut;
  7858. CalculateObjectStress( VPhysicsGetObject(), this, &stressOut );
  7859. Q_snprintf(tempstr, sizeof(tempstr),"Stress: %.2f", stressOut.receivedStress );
  7860. EntityText(text_offset, tempstr, 0);
  7861. text_offset++;
  7862. }
  7863. if ( m_pSquad )
  7864. {
  7865. if( m_pSquad->IsLeader(this) )
  7866. {
  7867. Q_snprintf(tempstr, sizeof(tempstr),"**Squad Leader**" );
  7868. EntityText(text_offset, tempstr, 0);
  7869. text_offset++;
  7870. }
  7871. Q_snprintf(tempstr, sizeof(tempstr), "SquadSlot:%s", GetSquadSlotDebugName( GetMyStrategySlot() ) );
  7872. EntityText(text_offset, tempstr, 0);
  7873. text_offset++;
  7874. }
  7875. }
  7876. return text_offset;
  7877. }
  7878. //-----------------------------------------------------------------------------
  7879. // Purpose: Displays information in the console about the state of this npc.
  7880. //-----------------------------------------------------------------------------
  7881. void CAI_BaseNPC::ReportAIState( void )
  7882. {
  7883. static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" };
  7884. DevMsg( "%s: ", GetClassname() );
  7885. if ( (int)m_NPCState < ARRAYSIZE(pStateNames) )
  7886. DevMsg( "State: %s, ", pStateNames[m_NPCState] );
  7887. if( m_Activity != ACT_INVALID && m_IdealActivity != ACT_INVALID )
  7888. {
  7889. const char *pszActivity = GetActivityName(m_Activity);
  7890. const char *pszIdealActivity = GetActivityName(m_IdealActivity);
  7891. DevMsg( "Activity: %s - Ideal Activity: %s\n", pszActivity, pszIdealActivity );
  7892. }
  7893. if ( GetCurSchedule() )
  7894. {
  7895. const char *pName = NULL;
  7896. pName = GetCurSchedule()->GetName();
  7897. if ( !pName )
  7898. pName = "Unknown";
  7899. DevMsg( "Schedule %s, ", pName );
  7900. const Task_t *pTask = GetTask();
  7901. if ( pTask )
  7902. DevMsg( "Task %d (#%d), ", pTask->iTask, GetScheduleCurTaskIndex() );
  7903. }
  7904. else
  7905. DevMsg( "No Schedule, " );
  7906. if ( GetEnemy() != NULL )
  7907. {
  7908. g_pEffects->Sparks( GetEnemy()->GetAbsOrigin() + Vector( 0, 0, 64 ) );
  7909. DevMsg( "\nEnemy is %s", GetEnemy()->GetClassname() );
  7910. }
  7911. else
  7912. DevMsg( "No enemy " );
  7913. if ( IsMoving() )
  7914. {
  7915. DevMsg( " Moving " );
  7916. if ( m_flMoveWaitFinished > gpGlobals->curtime )
  7917. DevMsg( ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->curtime );
  7918. else if ( m_IdealActivity == GetStoppedActivity() )
  7919. DevMsg( ": In stopped anim. " );
  7920. }
  7921. DevMsg( "Leader." );
  7922. DevMsg( "\n" );
  7923. DevMsg( "Yaw speed:%3.1f,Health: %3d\n", GetMotor()->GetYawSpeed(), m_iHealth.Get() );
  7924. if ( GetGroundEntity() )
  7925. {
  7926. DevMsg( "Groundent:%s\n\n", GetGroundEntity()->GetClassname() );
  7927. }
  7928. else
  7929. {
  7930. DevMsg( "Groundent: NULL\n\n" );
  7931. }
  7932. }
  7933. //-----------------------------------------------------------------------------
  7934. ConVar ai_report_task_timings_on_limit( "ai_report_task_timings_on_limit", "0", FCVAR_ARCHIVE );
  7935. ConVar ai_think_limit_label( "ai_think_limit_label", "0", FCVAR_ARCHIVE );
  7936. void CAI_BaseNPC::ReportOverThinkLimit( float time )
  7937. {
  7938. DevMsg( "%s thinking for %.02fms!!! (%s); r%.2f (c%.2f, pst%.2f, ms%.2f), p-r%.2f, m%.2f\n",
  7939. GetDebugName(), time, GetCurSchedule()->GetName(),
  7940. g_AIRunTimer.GetDuration().GetMillisecondsF(),
  7941. g_AIConditionsTimer.GetDuration().GetMillisecondsF(),
  7942. g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF(),
  7943. g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF(),
  7944. g_AIPostRunTimer.GetDuration().GetMillisecondsF(),
  7945. g_AIMoveTimer.GetDuration().GetMillisecondsF() );
  7946. if (ai_think_limit_label.GetBool())
  7947. {
  7948. Vector tmp;
  7949. CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 1.0f ), &tmp );
  7950. tmp.z += 16;
  7951. float max = -1;
  7952. const char *pszMax = "unknown";
  7953. if ( g_AIConditionsTimer.GetDuration().GetMillisecondsF() > max )
  7954. {
  7955. max = g_AIConditionsTimer.GetDuration().GetMillisecondsF();
  7956. pszMax = "Conditions";
  7957. }
  7958. if ( g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF() > max )
  7959. {
  7960. max = g_AIPrescheduleThinkTimer.GetDuration().GetMillisecondsF();
  7961. pszMax = "Pre-think";
  7962. }
  7963. if ( g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF() > max )
  7964. {
  7965. max = g_AIMaintainScheduleTimer.GetDuration().GetMillisecondsF();
  7966. pszMax = "Schedule";
  7967. }
  7968. if ( g_AIPostRunTimer.GetDuration().GetMillisecondsF() > max )
  7969. {
  7970. max = g_AIPostRunTimer.GetDuration().GetMillisecondsF();
  7971. pszMax = "Post-run";
  7972. }
  7973. if ( g_AIMoveTimer.GetDuration().GetMillisecondsF() > max )
  7974. {
  7975. max = g_AIMoveTimer.GetDuration().GetMillisecondsF();
  7976. pszMax = "Move";
  7977. }
  7978. NDebugOverlay::Text( tmp, (char*)(const char *)CFmtStr( "Slow %.1f, %s %.1f ", time, pszMax, max ), false, 1 );
  7979. }
  7980. if ( ai_report_task_timings_on_limit.GetBool() )
  7981. DumpTaskTimings();
  7982. }
  7983. //-----------------------------------------------------------------------------
  7984. // Purpose: Returns whether or not this npc can play the scripted sequence or AI
  7985. // sequence that is trying to possess it. If DisregardState is set, the npc
  7986. // will be sucked into the script no matter what state it is in. ONLY
  7987. // Scripted AI ents should allow this.
  7988. // Input : fDisregardNPCState -
  7989. // interruptLevel -
  7990. // eMode - If the function returns true, eMode will be one of the following values:
  7991. // CAN_PLAY_NOW
  7992. // CAN_PLAY_ENQUEUED
  7993. // Output :
  7994. //-----------------------------------------------------------------------------
  7995. CanPlaySequence_t CAI_BaseNPC::CanPlaySequence( bool fDisregardNPCState, int interruptLevel )
  7996. {
  7997. CanPlaySequence_t eReturn = CAN_PLAY_NOW;
  7998. if ( m_hCine )
  7999. {
  8000. if ( !m_hCine->CanEnqueueAfter() )
  8001. {
  8002. // npc is already running one scripted sequence and has an important script to play next
  8003. return CANNOT_PLAY;
  8004. }
  8005. eReturn = CAN_PLAY_ENQUEUED;
  8006. }
  8007. if ( !IsAlive() )
  8008. {
  8009. // npc is dead!
  8010. return CANNOT_PLAY;
  8011. }
  8012. // An NPC in a vehicle cannot play a scripted sequence
  8013. if ( IsInAVehicle() )
  8014. return CANNOT_PLAY;
  8015. if ( fDisregardNPCState )
  8016. {
  8017. // ok to go, no matter what the npc state. (scripted AI)
  8018. // No clue as to how to proced if they're climbing or jumping
  8019. // Assert( GetNavType() != NAV_JUMP && GetNavType() != NAV_CLIMB );
  8020. return eReturn;
  8021. }
  8022. if ( m_NPCState == NPC_STATE_NONE || m_NPCState == NPC_STATE_IDLE || m_IdealNPCState == NPC_STATE_IDLE )
  8023. {
  8024. // ok to go, but only in these states
  8025. return eReturn;
  8026. }
  8027. if ( m_NPCState == NPC_STATE_ALERT && interruptLevel >= SS_INTERRUPT_BY_NAME )
  8028. {
  8029. return eReturn;
  8030. }
  8031. // unknown situation
  8032. return CANNOT_PLAY;
  8033. }
  8034. //-----------------------------------------------------------------------------
  8035. void CAI_BaseNPC::SetHintGroup( string_t newGroup, bool bHintGroupNavLimiting )
  8036. {
  8037. string_t oldGroup = m_strHintGroup;
  8038. m_strHintGroup = newGroup;
  8039. m_bHintGroupNavLimiting = bHintGroupNavLimiting;
  8040. if ( oldGroup != newGroup )
  8041. OnChangeHintGroup( oldGroup, newGroup );
  8042. }
  8043. //-----------------------------------------------------------------------------
  8044. // Purpose:
  8045. // Input :
  8046. // Output :
  8047. //-----------------------------------------------------------------------------
  8048. Vector CAI_BaseNPC::GetShootEnemyDir( const Vector &shootOrigin, bool bNoisy )
  8049. {
  8050. CBaseEntity *pEnemy = GetEnemy();
  8051. if ( pEnemy )
  8052. {
  8053. Vector vecEnemyLKP = GetEnemyLKP();
  8054. Vector vecEnemyOffset = pEnemy->BodyTarget( shootOrigin, bNoisy ) - pEnemy->GetAbsOrigin();
  8055. #ifdef PORTAL
  8056. // Translate the enemy's position across the portals if it's only seen in the portal view cone
  8057. if ( !FInViewCone( vecEnemyLKP ) || !FVisible( vecEnemyLKP ) )
  8058. {
  8059. CProp_Portal *pPortal = FInViewConeThroughPortal( vecEnemyLKP );
  8060. if ( pPortal )
  8061. {
  8062. UTIL_Portal_VectorTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecEnemyOffset, vecEnemyOffset );
  8063. UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecEnemyLKP, vecEnemyLKP );
  8064. }
  8065. }
  8066. #endif
  8067. Vector retval = vecEnemyOffset + vecEnemyLKP - shootOrigin;
  8068. VectorNormalize( retval );
  8069. return retval;
  8070. }
  8071. else
  8072. {
  8073. Vector forward;
  8074. AngleVectors( GetLocalAngles(), &forward );
  8075. return forward;
  8076. }
  8077. }
  8078. //-----------------------------------------------------------------------------
  8079. // Simulates many times and reports statistical accuracy.
  8080. //-----------------------------------------------------------------------------
  8081. void CAI_BaseNPC::CollectShotStats( const Vector &vecShootOrigin, const Vector &vecShootDir )
  8082. {
  8083. #ifdef HL2_DLL
  8084. if( ai_shot_stats.GetBool() != 0 && GetEnemy()->IsPlayer() )
  8085. {
  8086. int iterations = ai_shot_stats_term.GetInt();
  8087. int iHits = 0;
  8088. Vector testDir = vecShootDir;
  8089. CShotManipulator manipulator( testDir );
  8090. for( int i = 0 ; i < iterations ; i++ )
  8091. {
  8092. // Apply appropriate accuracy.
  8093. manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) );
  8094. Vector shotDir = manipulator.GetResult();
  8095. Vector vecEnd = vecShootOrigin + shotDir * 8192;
  8096. trace_t tr;
  8097. AI_TraceLine( vecShootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  8098. if( tr.m_pEnt && tr.m_pEnt == GetEnemy() )
  8099. {
  8100. iHits++;
  8101. }
  8102. Vector vecProjectedPosition = GetActualShootPosition( vecShootOrigin );
  8103. Vector testDir = vecProjectedPosition - vecShootOrigin;
  8104. VectorNormalize( testDir );
  8105. manipulator.SetShootDir( testDir );
  8106. }
  8107. float flHitPercent = ((float)iHits / (float)iterations) * 100.0;
  8108. m_LastShootAccuracy = flHitPercent;
  8109. //DevMsg("Shots:%d Hits:%d Percentage:%.1f\n", iterations, iHits, flHitPercent);
  8110. }
  8111. else
  8112. {
  8113. m_LastShootAccuracy = -1;
  8114. }
  8115. #endif
  8116. }
  8117. #ifdef HL2_DLL
  8118. //-----------------------------------------------------------------------------
  8119. // Purpose: Return the actual position the NPC wants to fire at when it's trying
  8120. // to hit it's current enemy.
  8121. //-----------------------------------------------------------------------------
  8122. Vector CAI_BaseNPC::GetActualShootPosition( const Vector &shootOrigin )
  8123. {
  8124. // Project the target's location into the future.
  8125. Vector vecEnemyLKP = GetEnemyLKP();
  8126. Vector vecEnemyOffset = GetEnemy()->BodyTarget( shootOrigin ) - GetEnemy()->GetAbsOrigin();
  8127. Vector vecTargetPosition = vecEnemyOffset + vecEnemyLKP;
  8128. #ifdef PORTAL
  8129. // Check if it's also visible through portals
  8130. CProp_Portal *pPortal = FInViewConeThroughPortal( vecEnemyLKP );
  8131. if ( pPortal )
  8132. {
  8133. // Get the target's position through portals
  8134. Vector vecEnemyOffsetTransformed;
  8135. Vector vecEnemyLKPTransformed;
  8136. UTIL_Portal_VectorTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecEnemyOffset, vecEnemyOffsetTransformed );
  8137. UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecEnemyLKP, vecEnemyLKPTransformed );
  8138. Vector vecTargetPositionTransformed = vecEnemyOffsetTransformed + vecEnemyLKPTransformed;
  8139. // Get the distance to the target with and without portals
  8140. float fDistanceToEnemyThroughPortalSqr = GetAbsOrigin().DistToSqr( vecTargetPositionTransformed );
  8141. float fDistanceToEnemySqr = GetAbsOrigin().DistToSqr( vecTargetPosition );
  8142. if ( fDistanceToEnemyThroughPortalSqr < fDistanceToEnemySqr || !FInViewCone( vecEnemyLKP ) || !FVisible( vecEnemyLKP ) )
  8143. {
  8144. // We're better off shooting through the portals
  8145. vecTargetPosition = vecTargetPositionTransformed;
  8146. }
  8147. }
  8148. #endif
  8149. // lead for some fraction of a second.
  8150. return (vecTargetPosition + ( GetEnemy()->GetSmoothedVelocity() * ai_lead_time.GetFloat() ));
  8151. }
  8152. //-----------------------------------------------------------------------------
  8153. // Purpose:
  8154. //-----------------------------------------------------------------------------
  8155. float CAI_BaseNPC::GetSpreadBias( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
  8156. {
  8157. float bias = BaseClass::GetSpreadBias( pWeapon, pTarget );
  8158. AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget );
  8159. if ( ai_shot_bias.GetFloat() != 1.0 )
  8160. bias = ai_shot_bias.GetFloat();
  8161. if ( pEnemyInfo )
  8162. {
  8163. float timeToFocus = ai_spread_pattern_focus_time.GetFloat();
  8164. if ( timeToFocus > 0.0 )
  8165. {
  8166. float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy;
  8167. if ( timeSinceValidEnemy < 0.0f )
  8168. {
  8169. timeSinceValidEnemy = 0.0f;
  8170. }
  8171. float timeSinceReacquire = gpGlobals->curtime - pEnemyInfo->timeLastReacquired;
  8172. if ( timeSinceValidEnemy < timeToFocus )
  8173. {
  8174. float scale = timeSinceValidEnemy / timeToFocus;
  8175. Assert( scale >= 0.0 && scale <= 1.0 );
  8176. bias *= scale;
  8177. }
  8178. else if ( timeSinceReacquire < timeToFocus ) // handled seperately as might be tuning seperately
  8179. {
  8180. float scale = timeSinceReacquire / timeToFocus;
  8181. Assert( scale >= 0.0 && scale <= 1.0 );
  8182. bias *= scale;
  8183. }
  8184. }
  8185. }
  8186. return bias;
  8187. }
  8188. //-----------------------------------------------------------------------------
  8189. Vector CAI_BaseNPC::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
  8190. {
  8191. Vector baseResult = BaseClass::GetAttackSpread( pWeapon, pTarget );
  8192. AI_EnemyInfo_t *pEnemyInfo = m_pEnemies->Find( pTarget );
  8193. if ( pEnemyInfo )
  8194. {
  8195. float timeToFocus = ai_spread_cone_focus_time.GetFloat();
  8196. if ( timeToFocus > 0.0 )
  8197. {
  8198. float timeSinceValidEnemy = gpGlobals->curtime - pEnemyInfo->timeValidEnemy;
  8199. if ( timeSinceValidEnemy < 0 )
  8200. timeSinceValidEnemy = 0;
  8201. if ( timeSinceValidEnemy < timeToFocus )
  8202. {
  8203. float coneMultiplier = ai_spread_defocused_cone_multiplier.GetFloat();
  8204. if ( coneMultiplier > 1.0 )
  8205. {
  8206. float scale = 1.0 - timeSinceValidEnemy / timeToFocus;
  8207. Assert( scale >= 0.0 && scale <= 1.0 );
  8208. float multiplier = ( (coneMultiplier - 1.0) * scale ) + 1.0;
  8209. baseResult *= multiplier;
  8210. }
  8211. }
  8212. }
  8213. }
  8214. return baseResult;
  8215. }
  8216. //-----------------------------------------------------------------------------
  8217. // Similar to calling GetShootEnemyDir, but returns the exact trajectory to
  8218. // fire the bullet along, after calculating for target speed, location,
  8219. // concealment, etc.
  8220. //
  8221. // Ultimately, this code results in the shooter aiming his weapon somewhere ahead of
  8222. // a moving target. Since bullet traces are instant, aiming ahead of a target
  8223. // will result in more misses than hits. This is designed to provide targets with
  8224. // a bonus when moving perpendicular to the shooter's line of sight.
  8225. //
  8226. // Do not confuse this with leading a target in an effort to more easily hit it.
  8227. //
  8228. // This code PENALIZES a target for moving directly along the shooter's line of sight.
  8229. //
  8230. // This code REWARDS a target for moving perpendicular to the shooter's line of sight.
  8231. //-----------------------------------------------------------------------------
  8232. Vector CAI_BaseNPC::GetActualShootTrajectory( const Vector &shootOrigin )
  8233. {
  8234. if( !GetEnemy() )
  8235. return GetShootEnemyDir( shootOrigin );
  8236. // If we're above water shooting at a player underwater, bias some of the shots forward of
  8237. // the player so that they see the cool bubble trails in the water ahead of them.
  8238. if (GetEnemy()->IsPlayer() && (GetWaterLevel() != 3) && (GetEnemy()->GetWaterLevel() == 3))
  8239. {
  8240. #if 1
  8241. if (random->RandomInt(0, 4) < 3)
  8242. {
  8243. Vector vecEnemyForward;
  8244. GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL );
  8245. vecEnemyForward.z = 0;
  8246. // Lead up to a second ahead of them unless they are moving backwards.
  8247. Vector vecEnemyVelocity = GetEnemy()->GetSmoothedVelocity();
  8248. VectorNormalize( vecEnemyVelocity );
  8249. float flVelocityScale = vecEnemyForward.Dot( vecEnemyVelocity );
  8250. if ( flVelocityScale < 0.0f )
  8251. {
  8252. flVelocityScale = 0.0f;
  8253. }
  8254. Vector vecAimPos = GetEnemy()->EyePosition() + ( 48.0f * vecEnemyForward ) + (flVelocityScale * GetEnemy()->GetSmoothedVelocity() );
  8255. //NDebugOverlay::Cross3D(vecAimPos, Vector(-16,-16,-16), Vector(16,16,16), 255, 255, 0, true, 1.0f );
  8256. //vecAimPos.z = UTIL_WaterLevel( vecAimPos, vecAimPos.z, vecAimPos.z + 400.0f );
  8257. //NDebugOverlay::Cross3D(vecAimPos, Vector(-32,-32,-32), Vector(32,32,32), 255, 0, 0, true, 1.0f );
  8258. Vector vecShotDir = vecAimPos - shootOrigin;
  8259. VectorNormalize( vecShotDir );
  8260. return vecShotDir;
  8261. }
  8262. #else
  8263. if (random->RandomInt(0, 4) < 3)
  8264. {
  8265. // Aim at a point a few feet in front of the player's eyes
  8266. Vector vecEnemyForward;
  8267. GetEnemy()->GetVectors( &vecEnemyForward, NULL, NULL );
  8268. Vector vecAimPos = GetEnemy()->EyePosition() + (120.0f * vecEnemyForward );
  8269. Vector vecShotDir = vecAimPos - shootOrigin;
  8270. VectorNormalize( vecShotDir );
  8271. CShotManipulator manipulator( vecShotDir );
  8272. manipulator.ApplySpread( VECTOR_CONE_10DEGREES, 1 );
  8273. vecShotDir = manipulator.GetResult();
  8274. return vecShotDir;
  8275. }
  8276. #endif
  8277. }
  8278. Vector vecProjectedPosition = GetActualShootPosition( shootOrigin );
  8279. Vector shotDir = vecProjectedPosition - shootOrigin;
  8280. VectorNormalize( shotDir );
  8281. CollectShotStats( shootOrigin, shotDir );
  8282. // NOW we have a shoot direction. Where a 100% accurate bullet should go.
  8283. // Modify it by weapon proficiency.
  8284. // construct a manipulator
  8285. CShotManipulator manipulator( shotDir );
  8286. // Apply appropriate accuracy.
  8287. bool bUsePerfectAccuracy = false;
  8288. if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
  8289. {
  8290. CNPC_Bullseye *pBullseye = dynamic_cast<CNPC_Bullseye*>(GetEnemy());
  8291. if ( pBullseye && pBullseye->UsePerfectAccuracy() )
  8292. {
  8293. bUsePerfectAccuracy = true;
  8294. }
  8295. }
  8296. if ( !bUsePerfectAccuracy )
  8297. {
  8298. manipulator.ApplySpread( GetAttackSpread( GetActiveWeapon(), GetEnemy() ), GetSpreadBias( GetActiveWeapon(), GetEnemy() ) );
  8299. shotDir = manipulator.GetResult();
  8300. }
  8301. // Look for an opportunity to make misses hit interesting things.
  8302. CBaseCombatCharacter *pEnemy;
  8303. pEnemy = GetEnemy()->MyCombatCharacterPointer();
  8304. if( pEnemy && pEnemy->ShouldShootMissTarget( this ) )
  8305. {
  8306. Vector vecEnd = shootOrigin + shotDir * 8192;
  8307. trace_t tr;
  8308. AI_TraceLine(shootOrigin, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  8309. if( tr.fraction != 1.0 && tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO )
  8310. {
  8311. // Hit something we can harm. Just shoot it.
  8312. return manipulator.GetResult();
  8313. }
  8314. // Find something interesting around the enemy to shoot instead of just missing.
  8315. CBaseEntity *pMissTarget = pEnemy->FindMissTarget();
  8316. if( pMissTarget )
  8317. {
  8318. shotDir = pMissTarget->WorldSpaceCenter() - shootOrigin;
  8319. VectorNormalize( shotDir );
  8320. }
  8321. }
  8322. return shotDir;
  8323. }
  8324. #endif // HL2_DLL
  8325. //-----------------------------------------------------------------------------
  8326. Vector CAI_BaseNPC::BodyTarget( const Vector &posSrc, bool bNoisy )
  8327. {
  8328. Vector low = WorldSpaceCenter() - ( WorldSpaceCenter() - GetAbsOrigin() ) * .25;
  8329. Vector high = EyePosition();
  8330. Vector delta = high - low;
  8331. Vector result;
  8332. if ( bNoisy )
  8333. {
  8334. // bell curve
  8335. float rand1 = random->RandomFloat( 0.0, 0.5 );
  8336. float rand2 = random->RandomFloat( 0.0, 0.5 );
  8337. result = low + delta * rand1 + delta * rand2;
  8338. }
  8339. else
  8340. result = low + delta * 0.5;
  8341. return result;
  8342. }
  8343. //-----------------------------------------------------------------------------
  8344. bool CAI_BaseNPC::ShouldMoveAndShoot()
  8345. {
  8346. return ( ( CapabilitiesGet() & bits_CAP_MOVE_SHOOT ) != 0 );
  8347. }
  8348. //=========================================================
  8349. // FacingIdeal - tells us if a npc is facing its ideal
  8350. // yaw. Created this function because many spots in the
  8351. // code were checking the yawdiff against this magic
  8352. // number. Nicer to have it in one place if we're gonna
  8353. // be stuck with it.
  8354. //=========================================================
  8355. bool CAI_BaseNPC::FacingIdeal( void )
  8356. {
  8357. if ( fabs( GetMotor()->DeltaIdealYaw() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!!
  8358. {
  8359. return true;
  8360. }
  8361. return false;
  8362. }
  8363. //---------------------------------
  8364. void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp )
  8365. {
  8366. GetMotor()->AddFacingTarget( pTarget, flImportance, flDuration, flRamp );
  8367. }
  8368. void CAI_BaseNPC::AddFacingTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
  8369. {
  8370. GetMotor()->AddFacingTarget( vecPosition, flImportance, flDuration, flRamp );
  8371. }
  8372. void CAI_BaseNPC::AddFacingTarget( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
  8373. {
  8374. GetMotor()->AddFacingTarget( pTarget, vecPosition, flImportance, flDuration, flRamp );
  8375. }
  8376. float CAI_BaseNPC::GetFacingDirection( Vector &vecDir )
  8377. {
  8378. return (GetMotor()->GetFacingDirection( vecDir ));
  8379. }
  8380. //---------------------------------
  8381. int CAI_BaseNPC::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener )
  8382. {
  8383. int sentenceIndex = -1;
  8384. if ( pszSentence && IsAlive() )
  8385. {
  8386. if ( pszSentence[0] == '!' )
  8387. {
  8388. sentenceIndex = SENTENCEG_Lookup( pszSentence );
  8389. CPASAttenuationFilter filter( this, soundlevel );
  8390. CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, volume, soundlevel, 0, PITCH_NORM );
  8391. }
  8392. else
  8393. {
  8394. sentenceIndex = SENTENCEG_PlayRndSz( edict(), pszSentence, volume, soundlevel, 0, PITCH_NORM );
  8395. }
  8396. }
  8397. return sentenceIndex;
  8398. }
  8399. int CAI_BaseNPC::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
  8400. {
  8401. return PlaySentence( pszSentence, delay, volume, soundlevel, pListener );
  8402. }
  8403. //-----------------------------------------------------------------------------
  8404. // Purpose:
  8405. // Input :
  8406. // Output :
  8407. //-----------------------------------------------------------------------------
  8408. CBaseEntity *CAI_BaseNPC::FindNamedEntity( const char *name, IEntityFindFilter *pFilter )
  8409. {
  8410. if ( !stricmp( name, "!player" ))
  8411. {
  8412. return ( CBaseEntity * )AI_GetSinglePlayer();
  8413. }
  8414. else if ( !stricmp( name, "!enemy" ) )
  8415. {
  8416. if (GetEnemy() != NULL)
  8417. return GetEnemy();
  8418. }
  8419. else if ( !stricmp( name, "!self" ) || !stricmp( name, "!target1" ) )
  8420. {
  8421. return this;
  8422. }
  8423. else if ( !stricmp( name, "!nearestfriend" ) || !stricmp( name, "!friend" ) )
  8424. {
  8425. // FIXME: look at CBaseEntity *CNPCSimpleTalker::FindNearestFriend(bool fPlayer)
  8426. // punt for now
  8427. return ( CBaseEntity * )AI_GetSinglePlayer();
  8428. }
  8429. else if (!stricmp( name, "self" ))
  8430. {
  8431. static int selfwarningcount = 0;
  8432. // fix the vcd, the reserved names have changed
  8433. if ( ++selfwarningcount < 5 )
  8434. {
  8435. DevMsg( "ERROR: \"self\" is no longer used, use \"!self\" in vcd instead!\n" );
  8436. }
  8437. return this;
  8438. }
  8439. else if ( !stricmp( name, "Player" ))
  8440. {
  8441. static int playerwarningcount = 0;
  8442. if ( ++playerwarningcount < 5 )
  8443. {
  8444. DevMsg( "ERROR: \"player\" is no longer used, use \"!player\" in vcd instead!\n" );
  8445. }
  8446. return ( CBaseEntity * )AI_GetSinglePlayer();
  8447. }
  8448. else
  8449. {
  8450. // search for up to 32 entities with the same name and choose one randomly
  8451. CBaseEntity *entityList[ FINDNAMEDENTITY_MAX_ENTITIES ];
  8452. CBaseEntity *entity;
  8453. int iCount;
  8454. entity = NULL;
  8455. for( iCount = 0; iCount < FINDNAMEDENTITY_MAX_ENTITIES; iCount++ )
  8456. {
  8457. entity = gEntList.FindEntityByName( entity, name, NULL, NULL, NULL, pFilter );
  8458. if ( !entity )
  8459. {
  8460. break;
  8461. }
  8462. entityList[ iCount ] = entity;
  8463. }
  8464. if ( iCount > 0 )
  8465. {
  8466. int index = RandomInt( 0, iCount - 1 );
  8467. entity = entityList[ index ];
  8468. return entity;
  8469. }
  8470. }
  8471. return NULL;
  8472. }
  8473. void CAI_BaseNPC::CorpseFallThink( void )
  8474. {
  8475. if ( GetFlags() & FL_ONGROUND )
  8476. {
  8477. SetThink ( NULL );
  8478. SetSequenceBox( );
  8479. }
  8480. else
  8481. {
  8482. SetNextThink( gpGlobals->curtime + 0.1f );
  8483. }
  8484. }
  8485. // Call after animation/pose is set up
  8486. void CAI_BaseNPC::NPCInitDead( void )
  8487. {
  8488. InitBoneControllers();
  8489. RemoveSolidFlags( FSOLID_NOT_SOLID );
  8490. // so he'll fall to ground
  8491. SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
  8492. SetCycle( 0 );
  8493. ResetSequenceInfo( );
  8494. m_flPlaybackRate = 0;
  8495. // Copy health
  8496. m_iMaxHealth = m_iHealth;
  8497. m_lifeState = LIFE_DEAD;
  8498. UTIL_SetSize(this, vec3_origin, vec3_origin );
  8499. // Setup health counters, etc.
  8500. SetThink( &CAI_BaseNPC::CorpseFallThink );
  8501. SetNextThink( gpGlobals->curtime + 0.5f );
  8502. }
  8503. //=========================================================
  8504. // BBoxIsFlat - check to see if the npc's bounding box
  8505. // is lying flat on a surface (traces from all four corners
  8506. // are same length.)
  8507. //=========================================================
  8508. bool CAI_BaseNPC::BBoxFlat ( void )
  8509. {
  8510. trace_t tr;
  8511. Vector vecPoint;
  8512. float flXSize, flYSize;
  8513. float flLength;
  8514. float flLength2;
  8515. flXSize = WorldAlignSize().x / 2;
  8516. flYSize = WorldAlignSize().y / 2;
  8517. vecPoint.x = GetAbsOrigin().x + flXSize;
  8518. vecPoint.y = GetAbsOrigin().y + flYSize;
  8519. vecPoint.z = GetAbsOrigin().z;
  8520. AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  8521. flLength = (vecPoint - tr.endpos).Length();
  8522. vecPoint.x = GetAbsOrigin().x - flXSize;
  8523. vecPoint.y = GetAbsOrigin().y - flYSize;
  8524. AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  8525. flLength2 = (vecPoint - tr.endpos).Length();
  8526. if ( flLength2 > flLength )
  8527. {
  8528. return false;
  8529. }
  8530. flLength = flLength2;
  8531. vecPoint.x = GetAbsOrigin().x - flXSize;
  8532. vecPoint.y = GetAbsOrigin().y + flYSize;
  8533. AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  8534. flLength2 = (vecPoint - tr.endpos).Length();
  8535. if ( flLength2 > flLength )
  8536. {
  8537. return false;
  8538. }
  8539. flLength = flLength2;
  8540. vecPoint.x = GetAbsOrigin().x + flXSize;
  8541. vecPoint.y = GetAbsOrigin().y - flYSize;
  8542. AI_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  8543. flLength2 = (vecPoint - tr.endpos).Length();
  8544. if ( flLength2 > flLength )
  8545. {
  8546. return false;
  8547. }
  8548. flLength = flLength2;
  8549. return true;
  8550. }
  8551. //-----------------------------------------------------------------------------
  8552. // Purpose:
  8553. // Input : *pEnemy -
  8554. // bSetCondNewEnemy -
  8555. //-----------------------------------------------------------------------------
  8556. void CAI_BaseNPC::SetEnemy( CBaseEntity *pEnemy, bool bSetCondNewEnemy )
  8557. {
  8558. if (m_hEnemy != pEnemy)
  8559. {
  8560. ClearAttackConditions( );
  8561. VacateStrategySlot();
  8562. m_GiveUpOnDeadEnemyTimer.Stop();
  8563. // If we've just found a new enemy, set the condition
  8564. if ( pEnemy && bSetCondNewEnemy )
  8565. {
  8566. SetCondition( COND_NEW_ENEMY );
  8567. }
  8568. }
  8569. // Assert( (pEnemy == NULL) || (m_NPCState == NPC_STATE_COMBAT) );
  8570. m_hEnemy = pEnemy;
  8571. m_flTimeEnemyAcquired = gpGlobals->curtime;
  8572. m_LastShootAccuracy = -1;
  8573. m_TotalShots = 0;
  8574. m_TotalHits = 0;
  8575. if ( !pEnemy )
  8576. ClearCondition( COND_NEW_ENEMY );
  8577. }
  8578. const Vector &CAI_BaseNPC::GetEnemyLKP() const
  8579. {
  8580. return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->LastKnownPosition( GetEnemy() );
  8581. }
  8582. float CAI_BaseNPC::GetEnemyLastTimeSeen() const
  8583. {
  8584. return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->LastTimeSeen( GetEnemy() );
  8585. }
  8586. void CAI_BaseNPC::MarkEnemyAsEluded()
  8587. {
  8588. GetEnemies()->MarkAsEluded( GetEnemy() );
  8589. }
  8590. void CAI_BaseNPC::ClearEnemyMemory()
  8591. {
  8592. GetEnemies()->ClearMemory( GetEnemy() );
  8593. }
  8594. bool CAI_BaseNPC::EnemyHasEludedMe() const
  8595. {
  8596. return (const_cast<CAI_BaseNPC *>(this))->GetEnemies()->HasEludedMe( GetEnemy() );
  8597. }
  8598. void CAI_BaseNPC::SetTarget( CBaseEntity *pTarget )
  8599. {
  8600. m_hTargetEnt = pTarget;
  8601. }
  8602. //=========================================================
  8603. // Choose Enemy - tries to find the best suitable enemy for the npc.
  8604. //=========================================================
  8605. bool CAI_BaseNPC::ShouldChooseNewEnemy()
  8606. {
  8607. CBaseEntity *pEnemy = GetEnemy();
  8608. if ( pEnemy )
  8609. {
  8610. if ( GetEnemies()->GetSerialNumber() != m_EnemiesSerialNumber )
  8611. {
  8612. return true;
  8613. }
  8614. m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber();
  8615. if ( EnemyHasEludedMe() || (IRelationType( pEnemy ) != D_HT && IRelationType( pEnemy ) != D_FR) || !IsValidEnemy( pEnemy ) )
  8616. {
  8617. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (1)\n" );
  8618. return true;
  8619. }
  8620. if ( HasCondition(COND_SEE_HATE) || HasCondition(COND_SEE_DISLIKE) || HasCondition(COND_SEE_NEMESIS) || HasCondition(COND_SEE_FEAR) )
  8621. {
  8622. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (2)\n" );
  8623. return true;
  8624. }
  8625. if ( !pEnemy->IsAlive() )
  8626. {
  8627. if ( m_GiveUpOnDeadEnemyTimer.IsRunning() )
  8628. {
  8629. if ( m_GiveUpOnDeadEnemyTimer.Expired() )
  8630. {
  8631. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (3)\n" );
  8632. return true;
  8633. }
  8634. }
  8635. else
  8636. m_GiveUpOnDeadEnemyTimer.Start();
  8637. }
  8638. AI_EnemyInfo_t *pInfo = GetEnemies()->Find( pEnemy );
  8639. if ( m_FailChooseEnemyTimer.Expired() )
  8640. {
  8641. m_FailChooseEnemyTimer.Set( 1.5 );
  8642. if ( HasCondition( COND_TASK_FAILED ) ||
  8643. ( pInfo && ( pInfo->timeAtFirstHand == AI_INVALID_TIME || gpGlobals->curtime - pInfo->timeLastSeen > 10 ) ) )
  8644. {
  8645. return true;
  8646. }
  8647. }
  8648. if ( pInfo && pInfo->timeValidEnemy < gpGlobals->curtime )
  8649. {
  8650. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> false\n" );
  8651. return false;
  8652. }
  8653. }
  8654. DbgEnemyMsg( this, "ShouldChooseNewEnemy() --> true (4)\n" );
  8655. m_EnemiesSerialNumber = GetEnemies()->GetSerialNumber();
  8656. return true;
  8657. }
  8658. //-------------------------------------
  8659. bool CAI_BaseNPC::ChooseEnemy( void )
  8660. {
  8661. AI_PROFILE_SCOPE(CAI_Enemies_ChooseEnemy);
  8662. DbgEnemyMsg( this, "ChooseEnemy() {\n" );
  8663. //---------------------------------
  8664. //
  8665. // Gather initial conditions
  8666. //
  8667. CBaseEntity *pInitialEnemy = GetEnemy();
  8668. CBaseEntity *pChosenEnemy = pInitialEnemy;
  8669. // Use memory bits in case enemy pointer altered outside this function, (e.g., ehandle goes NULL)
  8670. bool fHadEnemy = ( HasMemory( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER ) );
  8671. bool fEnemyWasPlayer = HasMemory( bits_MEMORY_HAD_PLAYER );
  8672. bool fEnemyWentNull = ( fHadEnemy && !pInitialEnemy );
  8673. bool fEnemyEluded = ( fEnemyWentNull || ( pInitialEnemy && GetEnemies()->HasEludedMe( pInitialEnemy ) ) );
  8674. //---------------------------------
  8675. //
  8676. // Establish suitability of choosing a new enemy
  8677. //
  8678. bool fHaveCondNewEnemy;
  8679. bool fHaveCondLostEnemy;
  8680. if ( !m_ScheduleState.bScheduleWasInterrupted && GetCurSchedule() && !FScheduleDone() )
  8681. {
  8682. Assert( InterruptFromCondition( COND_NEW_ENEMY ) == COND_NEW_ENEMY && InterruptFromCondition( COND_LOST_ENEMY ) == COND_LOST_ENEMY );
  8683. fHaveCondNewEnemy = GetCurSchedule()->HasInterrupt( COND_NEW_ENEMY );
  8684. fHaveCondLostEnemy = GetCurSchedule()->HasInterrupt( COND_LOST_ENEMY );
  8685. // See if they've been added as a custom interrupt
  8686. if ( !fHaveCondNewEnemy )
  8687. {
  8688. fHaveCondNewEnemy = IsCustomInterruptConditionSet( COND_NEW_ENEMY );
  8689. }
  8690. if ( !fHaveCondLostEnemy )
  8691. {
  8692. fHaveCondLostEnemy = IsCustomInterruptConditionSet( COND_LOST_ENEMY );
  8693. }
  8694. }
  8695. else
  8696. {
  8697. fHaveCondNewEnemy = true; // not having a schedule is the same as being interruptable by any condition
  8698. fHaveCondLostEnemy = true;
  8699. }
  8700. if ( !fEnemyWentNull )
  8701. {
  8702. if ( !fHaveCondNewEnemy && !( fHaveCondLostEnemy && fEnemyEluded ) )
  8703. {
  8704. // DO NOT mess with the npc's enemy pointer unless the schedule the npc is currently
  8705. // running will be interrupted by COND_NEW_ENEMY or COND_LOST_ENEMY. This will
  8706. // eliminate the problem of npcs getting a new enemy while they are in a schedule
  8707. // that doesn't care, and then not realizing it by the time they get to a schedule
  8708. // that does. I don't feel this is a good permanent fix.
  8709. m_bSkippedChooseEnemy = true;
  8710. DbgEnemyMsg( this, "Skipped enemy selection due to schedule restriction\n" );
  8711. DbgEnemyMsg( this, "}\n" );
  8712. return ( pChosenEnemy != NULL );
  8713. }
  8714. }
  8715. else if ( !fHaveCondNewEnemy && !fHaveCondLostEnemy && GetCurSchedule() )
  8716. {
  8717. DevMsg( 2, "WARNING: AI enemy went NULL but schedule (%s) is not interested\n", GetCurSchedule()->GetName() );
  8718. }
  8719. m_bSkippedChooseEnemy = false;
  8720. //---------------------------------
  8721. //
  8722. // Select a target
  8723. //
  8724. if ( ShouldChooseNewEnemy() )
  8725. {
  8726. pChosenEnemy = BestEnemy();
  8727. }
  8728. //---------------------------------
  8729. //
  8730. // React to result of selection
  8731. //
  8732. bool fChangingEnemy = ( pChosenEnemy != pInitialEnemy );
  8733. if ( fChangingEnemy || fEnemyWentNull )
  8734. {
  8735. DbgEnemyMsg( this, "Enemy changed from %s to %s\n", pInitialEnemy->GetDebugName(), pChosenEnemy->GetDebugName() );
  8736. Forget( bits_MEMORY_HAD_ENEMY | bits_MEMORY_HAD_PLAYER );
  8737. // Did our old enemy snuff it?
  8738. if ( pInitialEnemy && !pInitialEnemy->IsAlive() )
  8739. {
  8740. SetCondition( COND_ENEMY_DEAD );
  8741. }
  8742. SetEnemy( pChosenEnemy );
  8743. if ( fHadEnemy )
  8744. {
  8745. // Vacate any strategy slot on old enemy
  8746. VacateStrategySlot();
  8747. // Force output event for establishing LOS
  8748. Forget( bits_MEMORY_HAD_LOS );
  8749. // m_flLastAttackTime = 0;
  8750. }
  8751. if ( !pChosenEnemy )
  8752. {
  8753. // Don't break on enemies going null if they've been killed
  8754. if ( !HasCondition(COND_ENEMY_DEAD) )
  8755. {
  8756. SetCondition( COND_ENEMY_WENT_NULL );
  8757. }
  8758. if ( fEnemyEluded )
  8759. {
  8760. SetCondition( COND_LOST_ENEMY );
  8761. LostEnemySound();
  8762. }
  8763. if ( fEnemyWasPlayer )
  8764. {
  8765. m_OnLostPlayer.FireOutput( pInitialEnemy, this );
  8766. }
  8767. m_OnLostEnemy.FireOutput( pInitialEnemy, this);
  8768. }
  8769. else
  8770. {
  8771. Remember( ( pChosenEnemy->IsPlayer() ) ? bits_MEMORY_HAD_PLAYER : bits_MEMORY_HAD_ENEMY );
  8772. }
  8773. }
  8774. //---------------------------------
  8775. return ( pChosenEnemy != NULL );
  8776. }
  8777. //=========================================================
  8778. void CAI_BaseNPC::PickupWeapon( CBaseCombatWeapon *pWeapon )
  8779. {
  8780. pWeapon->OnPickedUp( this );
  8781. Weapon_Equip( pWeapon );
  8782. m_iszPendingWeapon = NULL_STRING;
  8783. }
  8784. //=========================================================
  8785. // DropItem - dead npc drops named item
  8786. //=========================================================
  8787. CBaseEntity *CAI_BaseNPC::DropItem ( const char *pszItemName, Vector vecPos, QAngle vecAng )
  8788. {
  8789. if ( !pszItemName )
  8790. {
  8791. DevMsg( "DropItem() - No item name!\n" );
  8792. return NULL;
  8793. }
  8794. CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, this );
  8795. if ( pItem )
  8796. {
  8797. if ( g_pGameRules->IsAllowedToSpawn( pItem ) == false )
  8798. {
  8799. UTIL_Remove( pItem );
  8800. return NULL;
  8801. }
  8802. IPhysicsObject *pPhys = pItem->VPhysicsGetObject();
  8803. if ( pPhys )
  8804. {
  8805. // Add an extra push in a random direction
  8806. Vector vel = RandomVector( -64.0f, 64.0f );
  8807. AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f );
  8808. vel[2] = 0.0f;
  8809. pPhys->AddVelocity( &vel, &angImp );
  8810. }
  8811. else
  8812. {
  8813. // do we want this behavior to be default?! (sjb)
  8814. pItem->ApplyAbsVelocityImpulse( GetAbsVelocity() );
  8815. pItem->ApplyLocalAngularVelocityImpulse( AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 ) );
  8816. }
  8817. return pItem;
  8818. }
  8819. else
  8820. {
  8821. DevMsg( "DropItem() - Didn't create!\n" );
  8822. return NULL;
  8823. }
  8824. }
  8825. bool CAI_BaseNPC::ShouldFadeOnDeath( void )
  8826. {
  8827. if ( g_RagdollLVManager.IsLowViolence() )
  8828. {
  8829. return true;
  8830. }
  8831. else
  8832. {
  8833. // if flagged to fade out
  8834. return HasSpawnFlags(SF_NPC_FADE_CORPSE);
  8835. }
  8836. }
  8837. //-----------------------------------------------------------------------------
  8838. // Purpose: Indicates whether or not this npc should play an idle sound now.
  8839. //
  8840. //
  8841. // Output : Returns true if yes, false if no.
  8842. //-----------------------------------------------------------------------------
  8843. bool CAI_BaseNPC::ShouldPlayIdleSound( void )
  8844. {
  8845. if ( ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT ) &&
  8846. random->RandomInt(0,99) == 0 && !HasSpawnFlags(SF_NPC_GAG) )
  8847. {
  8848. return true;
  8849. }
  8850. return false;
  8851. }
  8852. //-----------------------------------------------------------------------------
  8853. // Purpose: Make a sound that other AI's can hear, to broadcast our presence
  8854. // Input : volume (radius) of the sound.
  8855. // Output :
  8856. //-----------------------------------------------------------------------------
  8857. void CAI_BaseNPC::MakeAIFootstepSound( float volume, float duration )
  8858. {
  8859. CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), volume, duration, this, SOUNDENT_CHANNEL_NPC_FOOTSTEP );
  8860. }
  8861. //-----------------------------------------------------------------------------
  8862. // Purpose:
  8863. // Input :
  8864. // Output :
  8865. //-----------------------------------------------------------------------------
  8866. bool CAI_BaseNPC::FOkToMakeSound( int soundPriority )
  8867. {
  8868. // ask the squad to filter sounds if I'm in one
  8869. if ( m_pSquad )
  8870. {
  8871. if ( !m_pSquad->FOkToMakeSound( soundPriority ) )
  8872. return false;
  8873. }
  8874. else
  8875. {
  8876. // otherwise, check my own sound timer
  8877. // Am I making uninterruptable sound?
  8878. if (gpGlobals->curtime <= m_flSoundWaitTime)
  8879. {
  8880. if ( soundPriority <= m_nSoundPriority )
  8881. return false;
  8882. }
  8883. }
  8884. // no talking outside of combat if gagged.
  8885. if ( HasSpawnFlags(SF_NPC_GAG) && ( m_NPCState != NPC_STATE_COMBAT ) )
  8886. return false;
  8887. return true;
  8888. }
  8889. //-----------------------------------------------------------------------------
  8890. // Purpose:
  8891. // Input :
  8892. // Output :
  8893. //-----------------------------------------------------------------------------
  8894. void CAI_BaseNPC::JustMadeSound( int soundPriority, float flSoundLength )
  8895. {
  8896. m_flSoundWaitTime = gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0);
  8897. m_nSoundPriority = soundPriority;
  8898. if (m_pSquad)
  8899. {
  8900. m_pSquad->JustMadeSound( soundPriority, gpGlobals->curtime + flSoundLength + random->RandomFloat(1.5, 2.0) );
  8901. }
  8902. }
  8903. Activity CAI_BaseNPC::GetStoppedActivity( void )
  8904. {
  8905. if (GetNavigator()->IsGoalActive())
  8906. {
  8907. Activity activity = GetNavigator()->GetArrivalActivity();
  8908. if (activity > ACT_RESET)
  8909. {
  8910. return activity;
  8911. }
  8912. }
  8913. return ACT_IDLE;
  8914. }
  8915. //=========================================================
  8916. //=========================================================
  8917. void CAI_BaseNPC::OnScheduleChange ( void )
  8918. {
  8919. EndTaskOverlay();
  8920. m_pNavigator->OnScheduleChange();
  8921. m_flMoveWaitFinished = 0;
  8922. VacateStrategySlot();
  8923. // If I still have have a route, clear it
  8924. // FIXME: Routes should only be cleared inside of tasks (kenb)
  8925. GetNavigator()->ClearGoal();
  8926. UnlockBestSound();
  8927. // If I locked a hint node clear it
  8928. if ( HasMemory(bits_MEMORY_LOCKED_HINT) && GetHintNode() != NULL)
  8929. {
  8930. float hintDelay = GetHintDelay(GetHintNode()->HintType());
  8931. GetHintNode()->Unlock(hintDelay);
  8932. SetHintNode( NULL );
  8933. }
  8934. }
  8935. CBaseCombatCharacter* CAI_BaseNPC::GetEnemyCombatCharacterPointer()
  8936. {
  8937. if ( GetEnemy() == NULL )
  8938. return NULL;
  8939. return GetEnemy()->MyCombatCharacterPointer();
  8940. }
  8941. // Global Savedata for npc
  8942. //
  8943. // This should be an exact copy of the var's in the header. Fields
  8944. // that aren't save/restored are commented out
  8945. BEGIN_DATADESC( CAI_BaseNPC )
  8946. // m_pSchedule (reacquired on restore)
  8947. DEFINE_EMBEDDED( m_ScheduleState ),
  8948. DEFINE_FIELD( m_IdealSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules
  8949. DEFINE_FIELD( m_failSchedule, FIELD_INTEGER ), // handled specially but left in for "virtual" schedules
  8950. DEFINE_FIELD( m_bUsingStandardThinkTime, FIELD_BOOLEAN ),
  8951. DEFINE_FIELD( m_flLastRealThinkTime, FIELD_TIME ),
  8952. // m_iFrameBlocked (not saved)
  8953. // m_bInChoreo (not saved)
  8954. // m_bDoPostRestoreRefindPath (not saved)
  8955. // gm_flTimeLastSpawn (static)
  8956. // gm_nSpawnedThisFrame (static)
  8957. // m_Conditions (custom save)
  8958. // m_CustomInterruptConditions (custom save)
  8959. // m_ConditionsPreIgnore (custom save)
  8960. // m_InverseIgnoreConditions (custom save)
  8961. // m_poseAim_Pitch (not saved; recomputed on restore)
  8962. // m_poseAim_Yaw (not saved; recomputed on restore)
  8963. // m_poseMove_Yaw (not saved; recomputed on restore)
  8964. DEFINE_FIELD( m_flTimePingEffect, FIELD_TIME ),
  8965. DEFINE_FIELD( m_bForceConditionsGather, FIELD_BOOLEAN ),
  8966. DEFINE_FIELD( m_bConditionsGathered, FIELD_BOOLEAN ),
  8967. DEFINE_FIELD( m_bSkippedChooseEnemy, FIELD_BOOLEAN ),
  8968. DEFINE_FIELD( m_NPCState, FIELD_INTEGER ),
  8969. DEFINE_FIELD( m_IdealNPCState, FIELD_INTEGER ),
  8970. DEFINE_FIELD( m_flLastStateChangeTime, FIELD_TIME ),
  8971. DEFINE_FIELD( m_Efficiency, FIELD_INTEGER ),
  8972. DEFINE_FIELD( m_MoveEfficiency, FIELD_INTEGER ),
  8973. DEFINE_FIELD( m_flNextDecisionTime, FIELD_TIME ),
  8974. DEFINE_KEYFIELD( m_SleepState, FIELD_INTEGER, "sleepstate" ),
  8975. DEFINE_FIELD( m_SleepFlags, FIELD_INTEGER ),
  8976. DEFINE_KEYFIELD( m_flWakeRadius, FIELD_FLOAT, "wakeradius" ),
  8977. DEFINE_KEYFIELD( m_bWakeSquad, FIELD_BOOLEAN, "wakesquad" ),
  8978. DEFINE_FIELD( m_nWakeTick, FIELD_TICK ),
  8979. DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ),
  8980. DEFINE_CUSTOM_FIELD( m_translatedActivity, ActivityDataOps() ),
  8981. DEFINE_CUSTOM_FIELD( m_IdealActivity, ActivityDataOps() ),
  8982. DEFINE_CUSTOM_FIELD( m_IdealTranslatedActivity, ActivityDataOps() ),
  8983. DEFINE_CUSTOM_FIELD( m_IdealWeaponActivity, ActivityDataOps() ),
  8984. DEFINE_FIELD( m_nIdealSequence, FIELD_INTEGER ),
  8985. DEFINE_EMBEDDEDBYREF( m_pSenses ),
  8986. DEFINE_EMBEDDEDBYREF( m_pLockedBestSound ),
  8987. DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ),
  8988. DEFINE_FIELD( m_flTimeEnemyAcquired, FIELD_TIME ),
  8989. DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ),
  8990. DEFINE_EMBEDDED( m_GiveUpOnDeadEnemyTimer ),
  8991. DEFINE_EMBEDDED( m_FailChooseEnemyTimer ),
  8992. DEFINE_FIELD( m_EnemiesSerialNumber, FIELD_INTEGER ),
  8993. DEFINE_FIELD( m_flAcceptableTimeSeenEnemy, FIELD_TIME ),
  8994. DEFINE_EMBEDDED( m_UpdateEnemyPosTimer ),
  8995. // m_flTimeAnyUpdateEnemyPos (static)
  8996. DEFINE_FIELD( m_vecCommandGoal, FIELD_VECTOR ),
  8997. DEFINE_EMBEDDED( m_CommandMoveMonitor ),
  8998. DEFINE_FIELD( m_flSoundWaitTime, FIELD_TIME ),
  8999. DEFINE_FIELD( m_nSoundPriority, FIELD_INTEGER ),
  9000. DEFINE_FIELD( m_flIgnoreDangerSoundsUntil, FIELD_TIME ),
  9001. DEFINE_FIELD( m_afCapability, FIELD_INTEGER ),
  9002. DEFINE_FIELD( m_flMoveWaitFinished, FIELD_TIME ),
  9003. DEFINE_FIELD( m_hOpeningDoor, FIELD_EHANDLE ),
  9004. DEFINE_EMBEDDEDBYREF( m_pNavigator ),
  9005. DEFINE_EMBEDDEDBYREF( m_pLocalNavigator ),
  9006. DEFINE_EMBEDDEDBYREF( m_pPathfinder ),
  9007. DEFINE_EMBEDDEDBYREF( m_pMoveProbe ),
  9008. DEFINE_EMBEDDEDBYREF( m_pMotor ),
  9009. DEFINE_UTLVECTOR(m_UnreachableEnts, FIELD_EMBEDDED),
  9010. DEFINE_FIELD( m_hInteractionPartner, FIELD_EHANDLE ),
  9011. DEFINE_FIELD( m_hLastInteractionTestTarget, FIELD_EHANDLE ),
  9012. DEFINE_FIELD( m_hForcedInteractionPartner, FIELD_EHANDLE ),
  9013. DEFINE_FIELD( m_flForcedInteractionTimeout, FIELD_TIME ),
  9014. DEFINE_FIELD( m_vecForcedWorldPosition, FIELD_POSITION_VECTOR ),
  9015. DEFINE_FIELD( m_bCannotDieDuringInteraction, FIELD_BOOLEAN ),
  9016. DEFINE_FIELD( m_iInteractionState, FIELD_INTEGER ),
  9017. DEFINE_FIELD( m_iInteractionPlaying, FIELD_INTEGER ),
  9018. DEFINE_UTLVECTOR(m_ScriptedInteractions,FIELD_EMBEDDED),
  9019. DEFINE_FIELD( m_flInteractionYaw, FIELD_FLOAT ),
  9020. DEFINE_EMBEDDED( m_CheckOnGroundTimer ),
  9021. DEFINE_FIELD( m_vDefaultEyeOffset, FIELD_VECTOR ),
  9022. DEFINE_FIELD( m_flNextEyeLookTime, FIELD_TIME ),
  9023. DEFINE_FIELD( m_flEyeIntegRate, FIELD_FLOAT ),
  9024. DEFINE_FIELD( m_vEyeLookTarget, FIELD_POSITION_VECTOR ),
  9025. DEFINE_FIELD( m_vCurEyeTarget, FIELD_POSITION_VECTOR ),
  9026. DEFINE_FIELD( m_hEyeLookTarget, FIELD_EHANDLE ),
  9027. DEFINE_FIELD( m_flHeadYaw, FIELD_FLOAT ),
  9028. DEFINE_FIELD( m_flHeadPitch, FIELD_FLOAT ),
  9029. DEFINE_FIELD( m_flOriginalYaw, FIELD_FLOAT ),
  9030. DEFINE_FIELD( m_bInAScript, FIELD_BOOLEAN ),
  9031. DEFINE_FIELD( m_scriptState, FIELD_INTEGER ),
  9032. DEFINE_FIELD( m_hCine, FIELD_EHANDLE ),
  9033. DEFINE_CUSTOM_FIELD( m_ScriptArrivalActivity, ActivityDataOps() ),
  9034. DEFINE_FIELD( m_strScriptArrivalSequence, FIELD_STRING ),
  9035. DEFINE_FIELD( m_flSceneTime, FIELD_TIME ),
  9036. DEFINE_FIELD( m_iszSceneCustomMoveSeq, FIELD_STRING ),
  9037. // m_pEnemies Saved specially in ai_saverestore.cpp
  9038. DEFINE_FIELD( m_afMemory, FIELD_INTEGER ),
  9039. DEFINE_FIELD( m_hEnemyOccluder, FIELD_EHANDLE ),
  9040. DEFINE_FIELD( m_flSumDamage, FIELD_FLOAT ),
  9041. DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ),
  9042. DEFINE_FIELD( m_flLastPlayerDamageTime, FIELD_TIME ),
  9043. DEFINE_FIELD( m_flLastSawPlayerTime, FIELD_TIME ),
  9044. DEFINE_FIELD( m_flLastAttackTime, FIELD_TIME ),
  9045. DEFINE_FIELD( m_flLastEnemyTime, FIELD_TIME ),
  9046. DEFINE_FIELD( m_flNextWeaponSearchTime, FIELD_TIME ),
  9047. DEFINE_FIELD( m_iszPendingWeapon, FIELD_STRING ),
  9048. DEFINE_KEYFIELD( m_bIgnoreUnseenEnemies, FIELD_BOOLEAN , "ignoreunseenenemies"),
  9049. DEFINE_EMBEDDED( m_ShotRegulator ),
  9050. DEFINE_FIELD( m_iDesiredWeaponState, FIELD_INTEGER ),
  9051. // m_pSquad Saved specially in ai_saverestore.cpp
  9052. DEFINE_KEYFIELD(m_SquadName, FIELD_STRING, "squadname" ),
  9053. DEFINE_FIELD( m_iMySquadSlot, FIELD_INTEGER ),
  9054. DEFINE_KEYFIELD( m_strHintGroup, FIELD_STRING, "hintgroup" ),
  9055. DEFINE_KEYFIELD( m_bHintGroupNavLimiting, FIELD_BOOLEAN, "hintlimiting" ),
  9056. DEFINE_EMBEDDEDBYREF( m_pTacticalServices ),
  9057. DEFINE_FIELD( m_flWaitFinished, FIELD_TIME ),
  9058. DEFINE_FIELD( m_flNextFlinchTime, FIELD_TIME ),
  9059. DEFINE_FIELD( m_flNextDodgeTime, FIELD_TIME ),
  9060. DEFINE_EMBEDDED( m_MoveAndShootOverlay ),
  9061. DEFINE_FIELD( m_vecLastPosition, FIELD_POSITION_VECTOR ),
  9062. DEFINE_FIELD( m_vSavePosition, FIELD_POSITION_VECTOR ),
  9063. DEFINE_FIELD( m_vInterruptSavePosition, FIELD_POSITION_VECTOR ),
  9064. DEFINE_FIELD( m_pHintNode, FIELD_EHANDLE),
  9065. DEFINE_FIELD( m_cAmmoLoaded, FIELD_INTEGER ),
  9066. DEFINE_FIELD( m_flDistTooFar, FIELD_FLOAT ),
  9067. DEFINE_FIELD( m_hGoalEnt, FIELD_EHANDLE ),
  9068. DEFINE_FIELD( m_flTimeLastMovement, FIELD_TIME ),
  9069. DEFINE_KEYFIELD(m_spawnEquipment, FIELD_STRING, "additionalequipment" ),
  9070. DEFINE_FIELD( m_fNoDamageDecal, FIELD_BOOLEAN ),
  9071. DEFINE_FIELD( m_hStoredPathTarget, FIELD_EHANDLE ),
  9072. DEFINE_FIELD( m_vecStoredPathGoal, FIELD_POSITION_VECTOR ),
  9073. DEFINE_FIELD( m_nStoredPathType, FIELD_INTEGER ),
  9074. DEFINE_FIELD( m_fStoredPathFlags, FIELD_INTEGER ),
  9075. DEFINE_FIELD( m_bDidDeathCleanup, FIELD_BOOLEAN ),
  9076. DEFINE_FIELD( m_bCrouchDesired, FIELD_BOOLEAN ),
  9077. DEFINE_FIELD( m_bForceCrouch, FIELD_BOOLEAN ),
  9078. DEFINE_FIELD( m_bIsCrouching, FIELD_BOOLEAN ),
  9079. DEFINE_FIELD( m_bPerformAvoidance, FIELD_BOOLEAN ),
  9080. DEFINE_FIELD( m_bIsMoving, FIELD_BOOLEAN ),
  9081. DEFINE_FIELD( m_bFadeCorpse, FIELD_BOOLEAN ),
  9082. DEFINE_FIELD( m_iDeathPose, FIELD_INTEGER ),
  9083. DEFINE_FIELD( m_iDeathFrame, FIELD_INTEGER ),
  9084. DEFINE_FIELD( m_bCheckContacts, FIELD_BOOLEAN ),
  9085. DEFINE_FIELD( m_bSpeedModActive, FIELD_BOOLEAN ),
  9086. DEFINE_FIELD( m_iSpeedModRadius, FIELD_INTEGER ),
  9087. DEFINE_FIELD( m_iSpeedModSpeed, FIELD_INTEGER ),
  9088. DEFINE_FIELD( m_hEnemyFilter, FIELD_EHANDLE ),
  9089. DEFINE_KEYFIELD( m_iszEnemyFilterName, FIELD_STRING, "enemyfilter" ),
  9090. DEFINE_FIELD( m_bImportanRagdoll, FIELD_BOOLEAN ),
  9091. DEFINE_FIELD( m_bPlayerAvoidState, FIELD_BOOLEAN ),
  9092. // Satisfy classcheck
  9093. // DEFINE_FIELD( m_ScheduleHistory, CUtlVector < AIScheduleChoice_t > ),
  9094. // m_fIsUsingSmallHull TODO -- This needs more consideration than simple save/load
  9095. // m_failText DEBUG
  9096. // m_interruptText DEBUG
  9097. // m_failedSchedule DEBUG
  9098. // m_interuptSchedule DEBUG
  9099. // m_nDebugCurIndex DEBUG
  9100. // m_LastShootAccuracy DEBUG
  9101. // m_RecentShotAccuracy DEBUG
  9102. // m_TotalShots DEBUG
  9103. // m_TotalHits DEBUG
  9104. // m_bSelected DEBUG
  9105. // m_TimeLastShotMark DEBUG
  9106. // m_bDeferredNavigation
  9107. // Outputs
  9108. DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
  9109. DEFINE_OUTPUT( m_OnDeath, "OnDeath" ),
  9110. DEFINE_OUTPUT( m_OnHalfHealth, "OnHalfHealth" ),
  9111. DEFINE_OUTPUT( m_OnFoundEnemy, "OnFoundEnemy" ),
  9112. DEFINE_OUTPUT( m_OnLostEnemyLOS, "OnLostEnemyLOS" ),
  9113. DEFINE_OUTPUT( m_OnLostEnemy, "OnLostEnemy" ),
  9114. DEFINE_OUTPUT( m_OnFoundPlayer, "OnFoundPlayer" ),
  9115. DEFINE_OUTPUT( m_OnLostPlayerLOS, "OnLostPlayerLOS" ),
  9116. DEFINE_OUTPUT( m_OnLostPlayer, "OnLostPlayer" ),
  9117. DEFINE_OUTPUT( m_OnHearWorld, "OnHearWorld" ),
  9118. DEFINE_OUTPUT( m_OnHearPlayer, "OnHearPlayer" ),
  9119. DEFINE_OUTPUT( m_OnHearCombat, "OnHearCombat" ),
  9120. DEFINE_OUTPUT( m_OnDamagedByPlayer, "OnDamagedByPlayer" ),
  9121. DEFINE_OUTPUT( m_OnDamagedByPlayerSquad, "OnDamagedByPlayerSquad" ),
  9122. DEFINE_OUTPUT( m_OnDenyCommanderUse, "OnDenyCommanderUse" ),
  9123. DEFINE_OUTPUT( m_OnRappelTouchdown, "OnRappelTouchdown" ),
  9124. DEFINE_OUTPUT( m_OnWake, "OnWake" ),
  9125. DEFINE_OUTPUT( m_OnSleep, "OnSleep" ),
  9126. DEFINE_OUTPUT( m_OnForcedInteractionStarted, "OnForcedInteractionStarted" ),
  9127. DEFINE_OUTPUT( m_OnForcedInteractionAborted, "OnForcedInteractionAborted" ),
  9128. DEFINE_OUTPUT( m_OnForcedInteractionFinished, "OnForcedInteractionFinished" ),
  9129. // Inputs
  9130. DEFINE_INPUTFUNC( FIELD_STRING, "SetRelationship", InputSetRelationship ),
  9131. DEFINE_INPUTFUNC( FIELD_STRING, "SetEnemyFilter", InputSetEnemyFilter ),
  9132. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
  9133. DEFINE_INPUTFUNC( FIELD_VOID, "BeginRappel", InputBeginRappel ),
  9134. DEFINE_INPUTFUNC( FIELD_STRING, "SetSquad", InputSetSquad ),
  9135. DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ),
  9136. DEFINE_INPUTFUNC( FIELD_STRING, "ForgetEntity", InputForgetEntity ),
  9137. DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreDangerSounds", InputIgnoreDangerSounds ),
  9138. DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
  9139. DEFINE_INPUTFUNC( FIELD_VOID, "StartScripting", InputStartScripting ),
  9140. DEFINE_INPUTFUNC( FIELD_VOID, "StopScripting", InputStopScripting ),
  9141. DEFINE_INPUTFUNC( FIELD_VOID, "GagEnable", InputGagEnable ),
  9142. DEFINE_INPUTFUNC( FIELD_VOID, "GagDisable", InputGagDisable ),
  9143. DEFINE_INPUTFUNC( FIELD_VOID, "InsideTransition", InputInsideTransition ),
  9144. DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
  9145. DEFINE_INPUTFUNC( FIELD_VOID, "ActivateSpeedModifier", InputActivateSpeedModifier ),
  9146. DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpeedModifier", InputDisableSpeedModifier ),
  9147. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModRadius", InputSetSpeedModifierRadius ),
  9148. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModSpeed", InputSetSpeedModifierSpeed ),
  9149. DEFINE_INPUTFUNC( FIELD_VOID, "HolsterWeapon", InputHolsterWeapon ),
  9150. DEFINE_INPUTFUNC( FIELD_VOID, "HolsterAndDestroyWeapon", InputHolsterAndDestroyWeapon ),
  9151. DEFINE_INPUTFUNC( FIELD_VOID, "UnholsterWeapon", InputUnholsterWeapon ),
  9152. DEFINE_INPUTFUNC( FIELD_STRING, "ForceInteractionWithNPC", InputForceInteractionWithNPC ),
  9153. DEFINE_INPUTFUNC( FIELD_STRING, "UpdateEnemyMemory", InputUpdateEnemyMemory ),
  9154. // Function pointers
  9155. DEFINE_USEFUNC( NPCUse ),
  9156. DEFINE_THINKFUNC( CallNPCThink ),
  9157. DEFINE_THINKFUNC( CorpseFallThink ),
  9158. DEFINE_THINKFUNC( NPCInitThink ),
  9159. END_DATADESC()
  9160. BEGIN_SIMPLE_DATADESC( AIScheduleState_t )
  9161. DEFINE_FIELD( iCurTask, FIELD_INTEGER ),
  9162. DEFINE_FIELD( fTaskStatus, FIELD_INTEGER ),
  9163. DEFINE_FIELD( timeStarted, FIELD_TIME ),
  9164. DEFINE_FIELD( timeCurTaskStarted, FIELD_TIME ),
  9165. DEFINE_FIELD( taskFailureCode, FIELD_INTEGER ),
  9166. DEFINE_FIELD( iTaskInterrupt, FIELD_INTEGER ),
  9167. DEFINE_FIELD( bTaskRanAutomovement, FIELD_BOOLEAN ),
  9168. DEFINE_FIELD( bTaskUpdatedYaw, FIELD_BOOLEAN ),
  9169. DEFINE_FIELD( bScheduleWasInterrupted, FIELD_BOOLEAN ),
  9170. END_DATADESC()
  9171. IMPLEMENT_SERVERCLASS_ST( CAI_BaseNPC, DT_AI_BaseNPC )
  9172. SendPropInt( SENDINFO( m_lifeState ), 3, SPROP_UNSIGNED ),
  9173. SendPropBool( SENDINFO( m_bPerformAvoidance ) ),
  9174. SendPropBool( SENDINFO( m_bIsMoving ) ),
  9175. SendPropBool( SENDINFO( m_bFadeCorpse ) ),
  9176. SendPropInt( SENDINFO( m_iDeathPose ), ANIMATION_SEQUENCE_BITS ),
  9177. SendPropInt( SENDINFO( m_iDeathFrame ), 5 ),
  9178. SendPropBool( SENDINFO( m_bSpeedModActive ) ),
  9179. SendPropInt( SENDINFO( m_iSpeedModRadius ) ),
  9180. SendPropInt( SENDINFO( m_iSpeedModSpeed ) ),
  9181. SendPropBool( SENDINFO( m_bImportanRagdoll ) ),
  9182. SendPropFloat( SENDINFO( m_flTimePingEffect ) ),
  9183. END_SEND_TABLE()
  9184. //-------------------------------------
  9185. BEGIN_SIMPLE_DATADESC( UnreachableEnt_t )
  9186. DEFINE_FIELD( hUnreachableEnt, FIELD_EHANDLE ),
  9187. DEFINE_FIELD( fExpireTime, FIELD_TIME ),
  9188. DEFINE_FIELD( vLocationWhenUnreachable, FIELD_POSITION_VECTOR ),
  9189. END_DATADESC()
  9190. //-------------------------------------
  9191. BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_Phases_t )
  9192. DEFINE_FIELD( iszSequence, FIELD_STRING ),
  9193. DEFINE_FIELD( iActivity, FIELD_INTEGER ),
  9194. END_DATADESC()
  9195. //-------------------------------------
  9196. BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t )
  9197. DEFINE_FIELD( iszInteractionName, FIELD_STRING ),
  9198. DEFINE_FIELD( iFlags, FIELD_INTEGER ),
  9199. DEFINE_FIELD( iTriggerMethod, FIELD_INTEGER ),
  9200. DEFINE_FIELD( iLoopBreakTriggerMethod, FIELD_INTEGER ),
  9201. DEFINE_FIELD( vecRelativeOrigin, FIELD_VECTOR ),
  9202. DEFINE_FIELD( angRelativeAngles, FIELD_VECTOR ),
  9203. DEFINE_FIELD( vecRelativeVelocity, FIELD_VECTOR ),
  9204. DEFINE_FIELD( flDelay, FIELD_FLOAT ),
  9205. DEFINE_FIELD( flDistSqr, FIELD_FLOAT ),
  9206. DEFINE_FIELD( iszMyWeapon, FIELD_STRING ),
  9207. DEFINE_FIELD( iszTheirWeapon, FIELD_STRING ),
  9208. DEFINE_EMBEDDED_ARRAY( sPhases, SNPCINT_NUM_PHASES ),
  9209. DEFINE_FIELD( matDesiredLocalToWorld, FIELD_VMATRIX ),
  9210. DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ),
  9211. DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ),
  9212. END_DATADESC()
  9213. //-------------------------------------
  9214. void CAI_BaseNPC::PostConstructor( const char *szClassname )
  9215. {
  9216. BaseClass::PostConstructor( szClassname );
  9217. CreateComponents();
  9218. }
  9219. //-----------------------------------------------------------------------------
  9220. // Purpose:
  9221. //-----------------------------------------------------------------------------
  9222. void CAI_BaseNPC::Activate( void )
  9223. {
  9224. BaseClass::Activate();
  9225. if ( GetModelPtr() )
  9226. {
  9227. ParseScriptedNPCInteractions();
  9228. }
  9229. // Get a handle to my enemy filter entity if there is one.
  9230. if ( m_iszEnemyFilterName != NULL_STRING )
  9231. {
  9232. CBaseEntity *pFilter = gEntList.FindEntityByName( NULL, m_iszEnemyFilterName );
  9233. if ( pFilter != NULL )
  9234. {
  9235. m_hEnemyFilter = dynamic_cast<CBaseFilter*>(pFilter);
  9236. }
  9237. }
  9238. #ifdef AI_MONITOR_FOR_OSCILLATION
  9239. m_ScheduleHistory.RemoveAll();
  9240. #endif//AI_MONITOR_FOR_OSCILLATION
  9241. }
  9242. void CAI_BaseNPC::Precache( void )
  9243. {
  9244. gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls
  9245. if ( m_spawnEquipment != NULL_STRING && strcmp(STRING(m_spawnEquipment), "0") )
  9246. {
  9247. UTIL_PrecacheOther( STRING(m_spawnEquipment) );
  9248. }
  9249. // Make sure schedules are loaded for this NPC type
  9250. if (!LoadedSchedules())
  9251. {
  9252. DevMsg("ERROR: Rejecting spawn of %s as error in NPC's schedules.\n",GetDebugName());
  9253. UTIL_Remove(this);
  9254. return;
  9255. }
  9256. PrecacheScriptSound( "AI_BaseNPC.SwishSound" );
  9257. PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Heavy" );
  9258. PrecacheScriptSound( "AI_BaseNPC.BodyDrop_Light" );
  9259. PrecacheScriptSound( "AI_BaseNPC.SentenceStop" );
  9260. BaseClass::Precache();
  9261. }
  9262. //-----------------------------------------------------------------------------
  9263. const short AI_EXTENDED_SAVE_HEADER_VERSION = 5;
  9264. const short AI_EXTENDED_SAVE_HEADER_RESET_VERSION = 3;
  9265. const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS = 2;
  9266. const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP = 3;
  9267. const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE = 4;
  9268. const short AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE = 5;
  9269. struct AIExtendedSaveHeader_t
  9270. {
  9271. AIExtendedSaveHeader_t()
  9272. : version(AI_EXTENDED_SAVE_HEADER_VERSION),
  9273. flags(0),
  9274. scheduleCrc(0)
  9275. {
  9276. szSchedule[0] = 0;
  9277. szIdealSchedule[0] = 0;
  9278. szFailSchedule[0] = 0;
  9279. szSequence[0] = 0;
  9280. }
  9281. short version;
  9282. unsigned flags;
  9283. char szSchedule[128];
  9284. CRC32_t scheduleCrc;
  9285. char szIdealSchedule[128];
  9286. char szFailSchedule[128];
  9287. char szSequence[128];
  9288. DECLARE_SIMPLE_DATADESC();
  9289. };
  9290. enum AIExtendedSaveHeaderFlags_t
  9291. {
  9292. AIESH_HAD_ENEMY = 0x01,
  9293. AIESH_HAD_TARGET = 0x02,
  9294. AIESH_HAD_NAVGOAL = 0x04,
  9295. };
  9296. //-------------------------------------
  9297. BEGIN_SIMPLE_DATADESC( AIExtendedSaveHeader_t )
  9298. DEFINE_FIELD( version, FIELD_SHORT ),
  9299. DEFINE_FIELD( flags, FIELD_INTEGER ),
  9300. DEFINE_AUTO_ARRAY( szSchedule, FIELD_CHARACTER ),
  9301. DEFINE_FIELD( scheduleCrc, FIELD_INTEGER ),
  9302. DEFINE_AUTO_ARRAY( szIdealSchedule, FIELD_CHARACTER ),
  9303. DEFINE_AUTO_ARRAY( szFailSchedule, FIELD_CHARACTER ),
  9304. DEFINE_AUTO_ARRAY( szSequence, FIELD_CHARACTER ),
  9305. END_DATADESC()
  9306. //-------------------------------------
  9307. int CAI_BaseNPC::Save( ISave &save )
  9308. {
  9309. AIExtendedSaveHeader_t saveHeader;
  9310. if ( GetEnemy() )
  9311. saveHeader.flags |= AIESH_HAD_ENEMY;
  9312. if ( GetTarget() )
  9313. saveHeader.flags |= AIESH_HAD_TARGET;
  9314. if ( GetNavigator()->IsGoalActive() )
  9315. saveHeader.flags |= AIESH_HAD_NAVGOAL;
  9316. if ( m_pSchedule )
  9317. {
  9318. const char *pszSchedule = m_pSchedule->GetName();
  9319. Assert( Q_strlen( pszSchedule ) < sizeof( saveHeader.szSchedule ) - 1 );
  9320. Q_strncpy( saveHeader.szSchedule, pszSchedule, sizeof( saveHeader.szSchedule ) );
  9321. CRC32_Init( &saveHeader.scheduleCrc );
  9322. CRC32_ProcessBuffer( &saveHeader.scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) );
  9323. CRC32_Final( &saveHeader.scheduleCrc );
  9324. }
  9325. else
  9326. {
  9327. saveHeader.szSchedule[0] = 0;
  9328. saveHeader.scheduleCrc = 0;
  9329. }
  9330. int idealSchedule = GetGlobalScheduleId( m_IdealSchedule );
  9331. if ( idealSchedule != -1 && idealSchedule != AI_RemapToGlobal( SCHED_NONE ) && idealSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) )
  9332. {
  9333. CAI_Schedule *pIdealSchedule = GetSchedule( m_IdealSchedule );
  9334. if ( pIdealSchedule )
  9335. {
  9336. const char *pszIdealSchedule = pIdealSchedule->GetName();
  9337. Assert( Q_strlen( pszIdealSchedule ) < sizeof( saveHeader.szIdealSchedule ) - 1 );
  9338. Q_strncpy( saveHeader.szIdealSchedule, pszIdealSchedule, sizeof( saveHeader.szIdealSchedule ) );
  9339. }
  9340. }
  9341. int failSchedule = GetGlobalScheduleId( m_failSchedule );
  9342. if ( failSchedule != -1 && failSchedule != AI_RemapToGlobal( SCHED_NONE ) && failSchedule != AI_RemapToGlobal( SCHED_AISCRIPT ) )
  9343. {
  9344. CAI_Schedule *pFailSchedule = GetSchedule( m_failSchedule );
  9345. if ( pFailSchedule )
  9346. {
  9347. const char *pszFailSchedule = pFailSchedule->GetName();
  9348. Assert( Q_strlen( pszFailSchedule ) < sizeof( saveHeader.szFailSchedule ) - 1 );
  9349. Q_strncpy( saveHeader.szFailSchedule, pszFailSchedule, sizeof( saveHeader.szFailSchedule ) );
  9350. }
  9351. }
  9352. if ( GetSequence() != ACT_INVALID && GetModelPtr() )
  9353. {
  9354. const char *pszSequenceName = GetSequenceName( GetSequence() );
  9355. if ( pszSequenceName && *pszSequenceName )
  9356. {
  9357. Assert( Q_strlen( pszSequenceName ) < sizeof( saveHeader.szSequence ) - 1 );
  9358. Q_strncpy( saveHeader.szSequence, pszSequenceName, sizeof(saveHeader.szSequence) );
  9359. }
  9360. }
  9361. save.WriteAll( &saveHeader );
  9362. save.StartBlock();
  9363. SaveConditions( save, m_Conditions );
  9364. SaveConditions( save, m_CustomInterruptConditions );
  9365. SaveConditions( save, m_ConditionsPreIgnore );
  9366. CAI_ScheduleBits ignoreConditions;
  9367. m_InverseIgnoreConditions.Not( &ignoreConditions );
  9368. SaveConditions( save, ignoreConditions );
  9369. save.EndBlock();
  9370. save.StartBlock();
  9371. GetNavigator()->Save( save );
  9372. save.EndBlock();
  9373. return BaseClass::Save(save);
  9374. }
  9375. //-------------------------------------
  9376. void CAI_BaseNPC::DiscardScheduleState()
  9377. {
  9378. // We don't save/restore routes yet
  9379. GetNavigator()->ClearGoal();
  9380. // We don't save/restore schedules yet
  9381. ClearSchedule( "Restoring NPC" );
  9382. // Reset animation
  9383. m_Activity = ACT_RESET;
  9384. // If we don't have an enemy, clear conditions like see enemy, etc.
  9385. if ( GetEnemy() == NULL )
  9386. {
  9387. m_Conditions.ClearAll();
  9388. }
  9389. // went across a transition and lost my m_hCine
  9390. bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL );
  9391. if ( bLostScript )
  9392. {
  9393. // UNDONE: Do something better here?
  9394. // for now, just go back to idle and let the AI figure out what to do.
  9395. SetState( NPC_STATE_IDLE );
  9396. SetIdealState( NPC_STATE_IDLE );
  9397. DevMsg(1, "Scripted Sequence stripped on level transition for %s\n", GetDebugName() );
  9398. }
  9399. }
  9400. //-------------------------------------
  9401. void CAI_BaseNPC::OnRestore()
  9402. {
  9403. gm_iszPlayerSquad = AllocPooledString( PLAYER_SQUADNAME ); // cache for fast IsPlayerSquad calls
  9404. if ( m_bDoPostRestoreRefindPath && CAI_NetworkManager::NetworksLoaded() )
  9405. {
  9406. CAI_DynamicLink::InitDynamicLinks();
  9407. if ( !GetNavigator()->RefindPathToGoal( false ) )
  9408. DiscardScheduleState();
  9409. }
  9410. else
  9411. {
  9412. GetNavigator()->ClearGoal();
  9413. }
  9414. BaseClass::OnRestore();
  9415. m_bCheckContacts = true;
  9416. }
  9417. //-------------------------------------
  9418. int CAI_BaseNPC::Restore( IRestore &restore )
  9419. {
  9420. AIExtendedSaveHeader_t saveHeader;
  9421. restore.ReadAll( &saveHeader );
  9422. if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_CONDITIONS )
  9423. {
  9424. restore.StartBlock();
  9425. RestoreConditions( restore, &m_Conditions );
  9426. RestoreConditions( restore, &m_CustomInterruptConditions );
  9427. RestoreConditions( restore, &m_ConditionsPreIgnore );
  9428. CAI_ScheduleBits ignoreConditions;
  9429. RestoreConditions( restore, &ignoreConditions );
  9430. ignoreConditions.Not( &m_InverseIgnoreConditions );
  9431. restore.EndBlock();
  9432. }
  9433. if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_NAVIGATOR_SAVE )
  9434. {
  9435. restore.StartBlock();
  9436. GetNavigator()->Restore( restore );
  9437. restore.EndBlock();
  9438. }
  9439. // do a normal restore
  9440. int status = BaseClass::Restore(restore);
  9441. if ( !status )
  9442. return 0;
  9443. // Do schedule fix-up
  9444. if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SCHEDULE_ID_FIXUP )
  9445. {
  9446. if ( saveHeader.szIdealSchedule[0] )
  9447. {
  9448. CAI_Schedule *pIdealSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szIdealSchedule );
  9449. m_IdealSchedule = ( pIdealSchedule ) ? pIdealSchedule->GetId() : SCHED_NONE;
  9450. }
  9451. if ( saveHeader.szFailSchedule[0] )
  9452. {
  9453. CAI_Schedule *pFailSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szFailSchedule );
  9454. m_failSchedule = ( pFailSchedule ) ? pFailSchedule->GetId() : SCHED_NONE;
  9455. }
  9456. }
  9457. bool bLostSequence = false;
  9458. if ( saveHeader.version >= AI_EXTENDED_SAVE_HEADER_FIRST_VERSION_WITH_SEQUENCE && saveHeader.szSequence[0] && GetModelPtr() )
  9459. {
  9460. SetSequence( LookupSequence( saveHeader.szSequence ) );
  9461. if ( GetSequence() == ACT_INVALID )
  9462. {
  9463. DevMsg( this, AIMF_IGNORE_SELECTED, "Discarding missing sequence %s on load.\n", saveHeader.szSequence );
  9464. SetSequence( 0 );
  9465. bLostSequence = true;
  9466. }
  9467. Assert( IsValidSequence( GetSequence() ) );
  9468. }
  9469. bool bLostScript = ( m_NPCState == NPC_STATE_SCRIPT && m_hCine == NULL );
  9470. bool bDiscardScheduleState = ( bLostScript ||
  9471. bLostSequence ||
  9472. saveHeader.szSchedule[0] == 0 ||
  9473. saveHeader.version < AI_EXTENDED_SAVE_HEADER_RESET_VERSION ||
  9474. ( (saveHeader.flags & AIESH_HAD_ENEMY) && !GetEnemy() ) ||
  9475. ( (saveHeader.flags & AIESH_HAD_TARGET) && !GetTarget() ) );
  9476. if ( m_ScheduleState.taskFailureCode >= NUM_FAIL_CODES )
  9477. m_ScheduleState.taskFailureCode = FAIL_NO_TARGET; // must have been a string, gotta punt
  9478. if ( !bDiscardScheduleState )
  9479. {
  9480. m_pSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szSchedule );
  9481. if ( m_pSchedule )
  9482. {
  9483. CRC32_t scheduleCrc;
  9484. CRC32_Init( &scheduleCrc );
  9485. CRC32_ProcessBuffer( &scheduleCrc, (void *)m_pSchedule->GetTaskList(), m_pSchedule->NumTasks() * sizeof(Task_t) );
  9486. CRC32_Final( &scheduleCrc );
  9487. if ( scheduleCrc != saveHeader.scheduleCrc )
  9488. {
  9489. m_pSchedule = NULL;
  9490. }
  9491. }
  9492. }
  9493. if ( !m_pSchedule )
  9494. bDiscardScheduleState = true;
  9495. if ( !bDiscardScheduleState )
  9496. m_bDoPostRestoreRefindPath = ( ( saveHeader.flags & AIESH_HAD_NAVGOAL) != 0 );
  9497. else
  9498. {
  9499. m_bDoPostRestoreRefindPath = false;
  9500. DiscardScheduleState();
  9501. }
  9502. return status;
  9503. }
  9504. //-------------------------------------
  9505. void CAI_BaseNPC::SaveConditions( ISave &save, const CAI_ScheduleBits &conditions )
  9506. {
  9507. for (int i = 0; i < MAX_CONDITIONS; i++)
  9508. {
  9509. if (conditions.IsBitSet(i))
  9510. {
  9511. const char *pszConditionName = ConditionName(AI_RemapToGlobal(i));
  9512. if ( !pszConditionName )
  9513. break;
  9514. save.WriteString( pszConditionName );
  9515. }
  9516. }
  9517. save.WriteString( "" );
  9518. }
  9519. //-------------------------------------
  9520. void CAI_BaseNPC::RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions )
  9521. {
  9522. pConditions->ClearAll();
  9523. char szCondition[256];
  9524. for (;;)
  9525. {
  9526. restore.ReadString( szCondition, sizeof(szCondition), 0 );
  9527. if ( !szCondition[0] )
  9528. break;
  9529. int iCondition = GetSchedulingSymbols()->ConditionSymbolToId( szCondition );
  9530. if ( iCondition != -1 )
  9531. pConditions->Set( AI_RemapFromGlobal( iCondition ) );
  9532. }
  9533. }
  9534. //-----------------------------------------------------------------------------
  9535. //-----------------------------------------------------------------------------
  9536. bool CAI_BaseNPC::KeyValue( const char *szKeyName, const char *szValue )
  9537. {
  9538. bool bResult = BaseClass::KeyValue( szKeyName, szValue );
  9539. if( !bResult )
  9540. {
  9541. // Defer unhandled Keys to behaviors
  9542. CAI_BehaviorBase **ppBehaviors = AccessBehaviors();
  9543. for ( int i = 0; i < NumBehaviors(); i++ )
  9544. {
  9545. if( ppBehaviors[ i ]->KeyValue( szKeyName, szValue ) )
  9546. {
  9547. return true;
  9548. }
  9549. }
  9550. }
  9551. return bResult;
  9552. }
  9553. //-----------------------------------------------------------------------------
  9554. // Purpose: Debug function to make this NPC freeze in place (or unfreeze).
  9555. //-----------------------------------------------------------------------------
  9556. void CAI_BaseNPC::ToggleFreeze(void)
  9557. {
  9558. if (!IsCurSchedule(SCHED_NPC_FREEZE))
  9559. {
  9560. // Freeze them.
  9561. SetCondition(COND_NPC_FREEZE);
  9562. SetMoveType(MOVETYPE_NONE);
  9563. SetGravity(0);
  9564. SetLocalAngularVelocity(vec3_angle);
  9565. SetAbsVelocity( vec3_origin );
  9566. }
  9567. else
  9568. {
  9569. // Unfreeze them.
  9570. SetCondition(COND_NPC_UNFREEZE);
  9571. m_Activity = ACT_RESET;
  9572. // BUGBUG: this might not be the correct movetype!
  9573. SetMoveType( MOVETYPE_STEP );
  9574. // Doesn't restore gravity to the original value, but who cares?
  9575. SetGravity(1);
  9576. }
  9577. }
  9578. //-----------------------------------------------------------------------------
  9579. // Purpose: Written by subclasses macro to load schedules
  9580. // Input :
  9581. // Output :
  9582. //-----------------------------------------------------------------------------
  9583. bool CAI_BaseNPC::LoadSchedules(void)
  9584. {
  9585. return true;
  9586. }
  9587. //-----------------------------------------------------------------------------
  9588. bool CAI_BaseNPC::LoadedSchedules(void)
  9589. {
  9590. return true;
  9591. }
  9592. //-----------------------------------------------------------------------------
  9593. // Purpose: Constructor
  9594. // Input :
  9595. // Output :
  9596. //-----------------------------------------------------------------------------
  9597. CAI_BaseNPC::CAI_BaseNPC(void)
  9598. : m_UnreachableEnts( 0, 4 ),
  9599. m_bDeferredNavigation( false )
  9600. {
  9601. m_pMotor = NULL;
  9602. m_pMoveProbe = NULL;
  9603. m_pNavigator = NULL;
  9604. m_pSenses = NULL;
  9605. m_pPathfinder = NULL;
  9606. m_pLocalNavigator = NULL;
  9607. m_pSchedule = NULL;
  9608. m_IdealSchedule = SCHED_NONE;
  9609. #ifdef _DEBUG
  9610. // necessary since in debug, we initialize vectors to NAN for debugging
  9611. m_vecLastPosition.Init();
  9612. m_vSavePosition.Init();
  9613. m_vEyeLookTarget.Init();
  9614. m_vCurEyeTarget.Init();
  9615. m_vDefaultEyeOffset.Init();
  9616. #endif
  9617. m_bDidDeathCleanup = false;
  9618. m_afCapability = 0; // Make sure this is cleared in the base class
  9619. SetHullType(HULL_HUMAN); // Give human hull by default, subclasses should override
  9620. m_iMySquadSlot = SQUAD_SLOT_NONE;
  9621. m_flSumDamage = 0;
  9622. m_flLastDamageTime = 0;
  9623. m_flLastAttackTime = 0;
  9624. m_flSoundWaitTime = 0;
  9625. m_flNextEyeLookTime = 0;
  9626. m_flHeadYaw = 0;
  9627. m_flHeadPitch = 0;
  9628. m_spawnEquipment = NULL_STRING;
  9629. m_pEnemies = new CAI_Enemies;
  9630. m_bIgnoreUnseenEnemies = false;
  9631. m_flEyeIntegRate = 0.95;
  9632. SetTarget( NULL );
  9633. m_pSquad = NULL;
  9634. m_flMoveWaitFinished = 0;
  9635. m_fIsUsingSmallHull = true;
  9636. m_bHintGroupNavLimiting = false;
  9637. m_fNoDamageDecal = false;
  9638. SetInAScript( false );
  9639. m_pLockedBestSound = new CSound;
  9640. m_pLockedBestSound->m_iType = SOUND_NONE;
  9641. // ----------------------------
  9642. // Debugging fields
  9643. // ----------------------------
  9644. m_interruptText = NULL;
  9645. m_failText = NULL;
  9646. m_failedSchedule = NULL;
  9647. m_interuptSchedule = NULL;
  9648. m_nDebugPauseIndex = 0;
  9649. g_AI_Manager.AddAI( this );
  9650. if ( g_AI_Manager.NumAIs() == 1 )
  9651. {
  9652. m_AnyUpdateEnemyPosTimer.Force();
  9653. gm_flTimeLastSpawn = -1;
  9654. gm_nSpawnedThisFrame = 0;
  9655. gm_iNextThinkRebalanceTick = 0;
  9656. }
  9657. m_iFrameBlocked = -1;
  9658. m_bInChoreo = true; // assume so until call to UpdateEfficiency()
  9659. SetCollisionGroup( COLLISION_GROUP_NPC );
  9660. }
  9661. //-----------------------------------------------------------------------------
  9662. // Purpose: Destructor
  9663. // Input :
  9664. // Output :
  9665. //-----------------------------------------------------------------------------
  9666. CAI_BaseNPC::~CAI_BaseNPC(void)
  9667. {
  9668. g_AI_Manager.RemoveAI( this );
  9669. delete m_pLockedBestSound;
  9670. RemoveMemory();
  9671. delete m_pPathfinder;
  9672. delete m_pNavigator;
  9673. delete m_pMotor;
  9674. delete m_pLocalNavigator;
  9675. delete m_pMoveProbe;
  9676. delete m_pSenses;
  9677. delete m_pTacticalServices;
  9678. }
  9679. //-----------------------------------------------------------------------------
  9680. // Purpose:
  9681. //-----------------------------------------------------------------------------
  9682. void CAI_BaseNPC::UpdateOnRemove(void)
  9683. {
  9684. if ( !m_bDidDeathCleanup )
  9685. {
  9686. if ( m_NPCState == NPC_STATE_DEAD )
  9687. DevMsg( "May not have cleaned up on NPC death\n");
  9688. CleanupOnDeath( NULL, false );
  9689. }
  9690. // Chain at end to mimic destructor unwind order
  9691. BaseClass::UpdateOnRemove();
  9692. }
  9693. //-----------------------------------------------------------------------------
  9694. //-----------------------------------------------------------------------------
  9695. int CAI_BaseNPC::UpdateTransmitState()
  9696. {
  9697. if( gpGlobals->curtime < m_flTimePingEffect )
  9698. {
  9699. return SetTransmitState( FL_EDICT_ALWAYS );
  9700. }
  9701. return BaseClass::UpdateTransmitState();
  9702. }
  9703. //-----------------------------------------------------------------------------
  9704. bool CAI_BaseNPC::CreateComponents()
  9705. {
  9706. m_pSenses = CreateSenses();
  9707. if ( !m_pSenses )
  9708. return false;
  9709. m_pMotor = CreateMotor();
  9710. if ( !m_pMotor )
  9711. return false;
  9712. m_pLocalNavigator = CreateLocalNavigator();
  9713. if ( !m_pLocalNavigator )
  9714. return false;
  9715. m_pMoveProbe = CreateMoveProbe();
  9716. if ( !m_pMoveProbe )
  9717. return false;
  9718. m_pNavigator = CreateNavigator();
  9719. if ( !m_pNavigator )
  9720. return false;
  9721. m_pPathfinder = CreatePathfinder();
  9722. if ( !m_pPathfinder )
  9723. return false;
  9724. m_pTacticalServices = CreateTacticalServices();
  9725. if ( !m_pTacticalServices )
  9726. return false;
  9727. m_MoveAndShootOverlay.SetOuter( this );
  9728. m_pMotor->Init( m_pLocalNavigator );
  9729. m_pLocalNavigator->Init( m_pNavigator );
  9730. m_pNavigator->Init( g_pBigAINet );
  9731. m_pPathfinder->Init( g_pBigAINet );
  9732. m_pTacticalServices->Init( g_pBigAINet );
  9733. return true;
  9734. }
  9735. //-----------------------------------------------------------------------------
  9736. CAI_Senses *CAI_BaseNPC::CreateSenses()
  9737. {
  9738. CAI_Senses *pSenses = new CAI_Senses;
  9739. pSenses->SetOuter( this );
  9740. return pSenses;
  9741. }
  9742. //-----------------------------------------------------------------------------
  9743. CAI_Motor *CAI_BaseNPC::CreateMotor()
  9744. {
  9745. return new CAI_Motor( this );
  9746. }
  9747. //-----------------------------------------------------------------------------
  9748. CAI_MoveProbe *CAI_BaseNPC::CreateMoveProbe()
  9749. {
  9750. return new CAI_MoveProbe( this );
  9751. }
  9752. //-----------------------------------------------------------------------------
  9753. CAI_LocalNavigator *CAI_BaseNPC::CreateLocalNavigator()
  9754. {
  9755. return new CAI_LocalNavigator( this );
  9756. }
  9757. //-----------------------------------------------------------------------------
  9758. CAI_TacticalServices *CAI_BaseNPC::CreateTacticalServices()
  9759. {
  9760. return new CAI_TacticalServices( this );
  9761. }
  9762. //-----------------------------------------------------------------------------
  9763. CAI_Navigator *CAI_BaseNPC::CreateNavigator()
  9764. {
  9765. return new CAI_Navigator( this );
  9766. }
  9767. //-----------------------------------------------------------------------------
  9768. CAI_Pathfinder *CAI_BaseNPC::CreatePathfinder()
  9769. {
  9770. return new CAI_Pathfinder( this );
  9771. }
  9772. //-----------------------------------------------------------------------------
  9773. // Purpose:
  9774. //-----------------------------------------------------------------------------
  9775. void CAI_BaseNPC::InputSetRelationship( inputdata_t &inputdata )
  9776. {
  9777. AddRelationship( inputdata.value.String(), inputdata.pActivator );
  9778. }
  9779. //-----------------------------------------------------------------------------
  9780. // Won't affect the current enemy, only future enemy acquisitions.
  9781. //-----------------------------------------------------------------------------
  9782. void CAI_BaseNPC::InputSetEnemyFilter( inputdata_t &inputdata )
  9783. {
  9784. // Get a handle to my enemy filter entity if there is one.
  9785. CBaseEntity *pFilter = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
  9786. m_hEnemyFilter = dynamic_cast<CBaseFilter*>(pFilter);
  9787. }
  9788. //-----------------------------------------------------------------------------
  9789. // Purpose:
  9790. //-----------------------------------------------------------------------------
  9791. void CAI_BaseNPC::InputSetHealth( inputdata_t &inputdata )
  9792. {
  9793. int iNewHealth = inputdata.value.Int();
  9794. int iDelta = abs(GetHealth() - iNewHealth);
  9795. if ( iNewHealth > GetHealth() )
  9796. {
  9797. TakeHealth( iDelta, DMG_GENERIC );
  9798. }
  9799. else if ( iNewHealth < GetHealth() )
  9800. {
  9801. TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) );
  9802. }
  9803. }
  9804. //-----------------------------------------------------------------------------
  9805. // Purpose:
  9806. //-----------------------------------------------------------------------------
  9807. void CAI_BaseNPC::InputBeginRappel( inputdata_t &inputdata )
  9808. {
  9809. BeginRappel();
  9810. }
  9811. //-----------------------------------------------------------------------------
  9812. // Purpose:
  9813. //-----------------------------------------------------------------------------
  9814. void CAI_BaseNPC::InputSetSquad( inputdata_t &inputdata )
  9815. {
  9816. if ( !( CapabilitiesGet() & bits_CAP_SQUAD ) )
  9817. {
  9818. Warning("SetSquad Input received for NPC %s, but that NPC can't use squads.\n", GetDebugName() );
  9819. return;
  9820. }
  9821. m_SquadName = inputdata.value.StringID();
  9822. // Removing from squad?
  9823. if ( m_SquadName == NULL_STRING )
  9824. {
  9825. if ( m_pSquad )
  9826. {
  9827. m_pSquad->RemoveFromSquad(this, true);
  9828. m_pSquad = NULL;
  9829. }
  9830. }
  9831. else
  9832. {
  9833. m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName);
  9834. }
  9835. }
  9836. //-----------------------------------------------------------------------------
  9837. // Purpose:
  9838. //-----------------------------------------------------------------------------
  9839. void CAI_BaseNPC::InputWake( inputdata_t &inputdata )
  9840. {
  9841. Wake();
  9842. // Check if we have a path to follow. This is normally done in StartNPC,
  9843. // but putting the NPC to sleep will cancel it, so we have to do it again.
  9844. if ( m_target != NULL_STRING )// this npc has a target
  9845. {
  9846. // Find the npc's initial target entity, stash it
  9847. SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) );
  9848. if ( !GetGoalEnt() )
  9849. {
  9850. Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target));
  9851. }
  9852. else
  9853. {
  9854. StartTargetHandling( GetGoalEnt() );
  9855. }
  9856. }
  9857. }
  9858. //-----------------------------------------------------------------------------
  9859. // Purpose:
  9860. //-----------------------------------------------------------------------------
  9861. void CAI_BaseNPC::InputForgetEntity( inputdata_t &inputdata )
  9862. {
  9863. const char *pszEntityToForget = inputdata.value.String();
  9864. if ( g_pDeveloper->GetInt() && pszEntityToForget[strlen( pszEntityToForget ) - 1] == '*' )
  9865. DevMsg( "InputForgetEntity does not support wildcards\n" );
  9866. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, pszEntityToForget );
  9867. if ( pEntity )
  9868. {
  9869. if ( GetEnemy() == pEntity )
  9870. {
  9871. SetEnemy( NULL );
  9872. SetIdealState( NPC_STATE_ALERT );
  9873. }
  9874. GetEnemies()->ClearMemory( pEntity );
  9875. }
  9876. }
  9877. //-----------------------------------------------------------------------------
  9878. //-----------------------------------------------------------------------------
  9879. void CAI_BaseNPC::InputIgnoreDangerSounds( inputdata_t &inputdata )
  9880. {
  9881. // Default is 10 seconds.
  9882. float flDelay = 10.0f;
  9883. if( inputdata.value.Float() > 0.0f )
  9884. {
  9885. flDelay = inputdata.value.Float();
  9886. }
  9887. m_flIgnoreDangerSoundsUntil = gpGlobals->curtime + flDelay;
  9888. }
  9889. //-----------------------------------------------------------------------------
  9890. //-----------------------------------------------------------------------------
  9891. void CAI_BaseNPC::InputUpdateEnemyMemory( inputdata_t &inputdata )
  9892. {
  9893. const char *pszEnemy = inputdata.value.String();
  9894. CBaseEntity *pEnemy = gEntList.FindEntityByName( NULL, pszEnemy );
  9895. if( pEnemy )
  9896. {
  9897. UpdateEnemyMemory( pEnemy, pEnemy->GetAbsOrigin(), this );
  9898. }
  9899. }
  9900. //-----------------------------------------------------------------------------
  9901. // Purpose:
  9902. // Input : &inputdata -
  9903. //-----------------------------------------------------------------------------
  9904. void CAI_BaseNPC::InputOutsideTransition( inputdata_t &inputdata )
  9905. {
  9906. }
  9907. //-----------------------------------------------------------------------------
  9908. // Purpose: Called when this NPC transitions to another level with the player
  9909. // Input : &inputdata -
  9910. //-----------------------------------------------------------------------------
  9911. void CAI_BaseNPC::InputInsideTransition( inputdata_t &inputdata )
  9912. {
  9913. CleanupScriptsOnTeleport( true );
  9914. // If we're inside a vcd, tell it to stop
  9915. if ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) )
  9916. {
  9917. RemoveActorFromScriptedScenes( this, false );
  9918. }
  9919. }
  9920. //-----------------------------------------------------------------------------
  9921. // Purpose:
  9922. //-----------------------------------------------------------------------------
  9923. void CAI_BaseNPC::CleanupScriptsOnTeleport( bool bEnrouteAsWell )
  9924. {
  9925. // If I'm running a scripted sequence, I need to clean up
  9926. if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine )
  9927. {
  9928. if ( !bEnrouteAsWell )
  9929. {
  9930. //
  9931. // Don't cancel scripts when they're teleporting an NPC
  9932. // to the script for the purposes of movement.
  9933. //
  9934. if ( ( m_scriptState == CAI_BaseNPC::SCRIPT_WALK_TO_MARK ) ||
  9935. ( m_scriptState == CAI_BaseNPC::SCRIPT_RUN_TO_MARK ) ||
  9936. ( m_scriptState == CAI_BaseNPC::SCRIPT_CUSTOM_MOVE_TO_MARK ) ||
  9937. m_hCine->IsTeleportingDueToMoveTo() )
  9938. {
  9939. return;
  9940. }
  9941. }
  9942. m_hCine->ScriptEntityCancel( m_hCine, true );
  9943. }
  9944. }
  9945. //-----------------------------------------------------------------------------
  9946. //-----------------------------------------------------------------------------
  9947. bool CAI_BaseNPC::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
  9948. {
  9949. #ifdef HL2_DLL
  9950. if ( interactionType == g_interactionBarnacleVictimGrab )
  9951. {
  9952. // Make the victim stop thinking so they're as good as dead without
  9953. // shocking the system by destroying the entity.
  9954. StopLoopingSounds();
  9955. BarnacleDeathSound();
  9956. SetThink( NULL );
  9957. // Gag the NPC so they won't talk anymore
  9958. AddSpawnFlags( SF_NPC_GAG );
  9959. // Drop any weapon they're holding
  9960. if ( GetActiveWeapon() )
  9961. {
  9962. Weapon_Drop( GetActiveWeapon() );
  9963. }
  9964. return true;
  9965. }
  9966. #endif // HL2_DLL
  9967. return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
  9968. }
  9969. CAI_BaseNPC *CAI_BaseNPC::GetInteractionPartner( void )
  9970. {
  9971. if ( m_hInteractionPartner == NULL )
  9972. return NULL;
  9973. return m_hInteractionPartner->MyNPCPointer();
  9974. }
  9975. //-----------------------------------------------------------------------------
  9976. // Purpose: Called when exiting a scripted sequence.
  9977. // Output : Returns true if alive, false if dead.
  9978. //-----------------------------------------------------------------------------
  9979. bool CAI_BaseNPC::ExitScriptedSequence( )
  9980. {
  9981. if ( m_lifeState == LIFE_DYING )
  9982. {
  9983. // is this legal?
  9984. // BUGBUG -- This doesn't call Killed()
  9985. SetIdealState( NPC_STATE_DEAD );
  9986. return false;
  9987. }
  9988. if (m_hCine)
  9989. {
  9990. m_hCine->CancelScript( );
  9991. }
  9992. return true;
  9993. }
  9994. 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." );
  9995. bool CAI_BaseNPC::CineCleanup()
  9996. {
  9997. CAI_ScriptedSequence *pOldCine = m_hCine;
  9998. int nSavedFlags = ( m_hCine ? m_hCine->m_savedFlags : GetFlags() );
  9999. bool bDestroyCine = false;
  10000. if ( IsRunningDynamicInteraction() )
  10001. {
  10002. bDestroyCine = true;
  10003. // Re-enable physics collisions between me & the other NPC
  10004. if ( m_hInteractionPartner )
  10005. {
  10006. PhysEnableEntityCollisions( this, m_hInteractionPartner );
  10007. //Msg("%s(%s) enabled collisions with %s(%s) at %0.2f\n", GetClassname(), GetDebugName(), m_hInteractionPartner->GetClassName(), m_hInteractionPartner->GetDebugName(), gpGlobals->curtime );
  10008. }
  10009. if ( m_hForcedInteractionPartner )
  10010. {
  10011. // We've finished a forced interaction. Let the mapmaker know.
  10012. m_OnForcedInteractionFinished.FireOutput( this, this );
  10013. }
  10014. // Clear interaction partner, because we're not running a scripted sequence anymore
  10015. m_hInteractionPartner = NULL;
  10016. CleanupForcedInteraction();
  10017. }
  10018. // am I linked to a cinematic?
  10019. if (m_hCine)
  10020. {
  10021. // okay, reset me to what it thought I was before
  10022. m_hCine->SetTarget( NULL );
  10023. // NOTE that this will have had EF_NODRAW removed in script.dll when it's cached off
  10024. SetEffects( m_hCine->m_saved_effects );
  10025. SetCollisionGroup( m_hCine->m_savedCollisionGroup );
  10026. }
  10027. else
  10028. {
  10029. // arg, punt
  10030. AddSolidFlags( FSOLID_NOT_STANDABLE );
  10031. }
  10032. m_hCine = NULL;
  10033. SetTarget( NULL );
  10034. SetGoalEnt( NULL );
  10035. if (m_lifeState == LIFE_DYING)
  10036. {
  10037. // last frame of death animation?
  10038. if ( m_iHealth > 0 )
  10039. {
  10040. m_iHealth = 0;
  10041. }
  10042. AddSolidFlags( FSOLID_NOT_SOLID );
  10043. SetState( NPC_STATE_DEAD );
  10044. m_lifeState = LIFE_DEAD;
  10045. UTIL_SetSize( this, WorldAlignMins(), Vector(WorldAlignMaxs().x, WorldAlignMaxs().y, WorldAlignMins().z + 2) );
  10046. if ( pOldCine && pOldCine->HasSpawnFlags( SF_SCRIPT_LEAVECORPSE ) )
  10047. {
  10048. SetUse( NULL ); // BUGBUG -- This doesn't call Killed()
  10049. SetThink( NULL ); // This will probably break some stuff
  10050. SetTouch( NULL );
  10051. }
  10052. else
  10053. SUB_StartFadeOut(); // SetThink( SUB_DoNothing );
  10054. //Not becoming a ragdoll, so set the NOINTERP flag on.
  10055. if ( CanBecomeRagdoll() == false )
  10056. {
  10057. StopAnimation();
  10058. IncrementInterpolationFrame(); // Don't interpolate either, assume the corpse is positioned in its final resting place
  10059. }
  10060. SetMoveType( MOVETYPE_NONE );
  10061. return false;
  10062. }
  10063. // If we actually played a sequence
  10064. if ( pOldCine && pOldCine->m_iszPlay != NULL_STRING && pOldCine->PlayedSequence() )
  10065. {
  10066. if ( !pOldCine->HasSpawnFlags(SF_SCRIPT_DONT_TELEPORT_AT_END) )
  10067. {
  10068. // reset position
  10069. Vector new_origin;
  10070. QAngle new_angle;
  10071. GetBonePosition( 0, new_origin, new_angle );
  10072. // Figure out how far they have moved
  10073. // We can't really solve this problem because we can't query the movement of the origin relative
  10074. // to the sequence. We can get the root bone's position as we do here, but there are
  10075. // cases where the root bone is in a different relative position to the entity's origin
  10076. // before/after the sequence plays. So we are stuck doing this:
  10077. // !!!HACKHACK: Float the origin up and drop to floor because some sequences have
  10078. // irregular motion that can't be properly accounted for.
  10079. // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE.
  10080. Vector oldOrigin = GetLocalOrigin();
  10081. // UNDONE: ugly hack. Don't move NPC if they don't "seem" to move
  10082. // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly
  10083. // being set, so animations that really do move won't be caught.
  10084. if ((oldOrigin - new_origin).Length2D() < 8.0)
  10085. new_origin = oldOrigin;
  10086. Vector origin = GetLocalOrigin();
  10087. origin.x = new_origin.x;
  10088. origin.y = new_origin.y;
  10089. origin.z += 1;
  10090. if ( nSavedFlags & FL_FLY )
  10091. {
  10092. origin.z = new_origin.z;
  10093. SetLocalOrigin( origin );
  10094. }
  10095. else
  10096. {
  10097. SetLocalOrigin( origin );
  10098. int drop = UTIL_DropToFloor( this, MASK_NPCSOLID, UTIL_GetLocalPlayer() );
  10099. // Origin in solid? Set to org at the end of the sequence
  10100. if ( ( drop < 0 ) || sv_test_scripted_sequences.GetBool() )
  10101. {
  10102. SetLocalOrigin( oldOrigin );
  10103. }
  10104. else if ( drop == 0 ) // Hanging in air?
  10105. {
  10106. Vector origin = GetLocalOrigin();
  10107. origin.z = new_origin.z;
  10108. SetLocalOrigin( origin );
  10109. SetGroundEntity( NULL );
  10110. }
  10111. }
  10112. origin = GetLocalOrigin();
  10113. // teleport if it's a non-trivial distance
  10114. if ((oldOrigin - origin).Length() > 8.0)
  10115. {
  10116. // Call teleport to notify
  10117. Teleport( &origin, NULL, NULL );
  10118. SetLocalOrigin( origin );
  10119. IncrementInterpolationFrame();
  10120. }
  10121. if ( m_iHealth <= 0 )
  10122. {
  10123. // Dropping out because he got killed
  10124. SetIdealState( NPC_STATE_DEAD );
  10125. SetCondition( COND_LIGHT_DAMAGE );
  10126. m_lifeState = LIFE_DYING;
  10127. }
  10128. }
  10129. // We should have some animation to put these guys in, but for now it's idle.
  10130. // Due to NOINTERP above, there won't be any blending between this anim & the sequence
  10131. m_Activity = ACT_RESET;
  10132. }
  10133. // set them back into a normal state
  10134. if ( m_iHealth > 0 )
  10135. {
  10136. SetIdealState( NPC_STATE_IDLE );
  10137. }
  10138. else
  10139. {
  10140. // Dropping out because he got killed
  10141. SetIdealState( NPC_STATE_DEAD );
  10142. SetCondition( COND_LIGHT_DAMAGE );
  10143. }
  10144. // SetAnimation( m_NPCState );
  10145. CLEARBITS(m_spawnflags, SF_NPC_WAIT_FOR_SCRIPT );
  10146. if ( bDestroyCine )
  10147. {
  10148. UTIL_Remove( pOldCine );
  10149. }
  10150. return true;
  10151. }
  10152. //-----------------------------------------------------------------------------
  10153. void CAI_BaseNPC::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
  10154. {
  10155. CleanupScriptsOnTeleport( false );
  10156. BaseClass::Teleport( newPosition, newAngles, newVelocity );
  10157. }
  10158. //-----------------------------------------------------------------------------
  10159. bool CAI_BaseNPC::FindSpotForNPCInRadius( Vector *pResult, const Vector &vStartPos, CAI_BaseNPC *pNPC, float radius, bool bOutOfPlayerViewcone )
  10160. {
  10161. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  10162. QAngle fan;
  10163. fan.x = 0;
  10164. fan.z = 0;
  10165. for( fan.y = 0 ; fan.y < 360 ; fan.y += 18.0 )
  10166. {
  10167. Vector vecTest;
  10168. Vector vecDir;
  10169. AngleVectors( fan, &vecDir );
  10170. vecTest = vStartPos + vecDir * radius;
  10171. if ( bOutOfPlayerViewcone && pPlayer && !pPlayer->FInViewCone( vecTest ) )
  10172. continue;
  10173. trace_t tr;
  10174. UTIL_TraceLine( vecTest, vecTest - Vector( 0, 0, 8192 ), MASK_SHOT, pNPC, COLLISION_GROUP_NONE, &tr );
  10175. if( tr.fraction == 1.0 )
  10176. {
  10177. continue;
  10178. }
  10179. UTIL_TraceHull( tr.endpos,
  10180. tr.endpos + Vector( 0, 0, 10 ),
  10181. pNPC->GetHullMins(),
  10182. pNPC->GetHullMaxs(),
  10183. MASK_NPCSOLID,
  10184. pNPC,
  10185. COLLISION_GROUP_NONE,
  10186. &tr );
  10187. if( tr.fraction == 1.0 && pNPC->GetMoveProbe()->CheckStandPosition( tr.endpos, MASK_NPCSOLID ) )
  10188. {
  10189. *pResult = tr.endpos;
  10190. return true;
  10191. }
  10192. }
  10193. return false;
  10194. }
  10195. //-----------------------------------------------------------------------------
  10196. bool CAI_BaseNPC::IsNavigationUrgent()
  10197. {
  10198. // return true if the navigation is for something that can't react well to failure
  10199. if ( IsCurSchedule( SCHED_SCRIPTED_WALK, false ) ||
  10200. IsCurSchedule( SCHED_SCRIPTED_RUN, false ) ||
  10201. IsCurSchedule( SCHED_SCRIPTED_CUSTOM_MOVE, false ) ||
  10202. ( IsCurSchedule( SCHED_SCENE_GENERIC, false ) && IsInLockedScene() ) )
  10203. {
  10204. return true;
  10205. }
  10206. return false;
  10207. }
  10208. //-----------------------------------------------------------------------------
  10209. bool CAI_BaseNPC::ShouldFailNav( bool bMovementFailed )
  10210. {
  10211. #ifdef HL2_EPISODIC
  10212. if ( ai_vehicle_avoidance.GetBool() )
  10213. {
  10214. // Never be blocked this way by a vehicle (creates too many headaches around the levels)
  10215. CBaseEntity *pEntity = GetNavigator()->GetBlockingEntity();
  10216. if ( pEntity && pEntity->GetServerVehicle() )
  10217. {
  10218. // Vital allies never get stuck, and urgent moves cannot be blocked by a vehicle
  10219. if ( Classify() == CLASS_PLAYER_ALLY_VITAL || IsNavigationUrgent() )
  10220. return false;
  10221. }
  10222. }
  10223. #endif // HL2_EPISODIC
  10224. // It's up to the schedule that requested movement to deal with failed movement. Currently, only a handfull of
  10225. // schedules are considered Urgent, and they need to deal with what to do when there's no route, which by inspection
  10226. // they'd don't.
  10227. if ( IsNavigationUrgent())
  10228. {
  10229. return false;
  10230. }
  10231. return true;
  10232. }
  10233. Navigation_t CAI_BaseNPC::GetNavType() const
  10234. {
  10235. return m_pNavigator->GetNavType();
  10236. }
  10237. void CAI_BaseNPC::SetNavType( Navigation_t navType )
  10238. {
  10239. m_pNavigator->SetNavType( navType );
  10240. }
  10241. //-----------------------------------------------------------------------------
  10242. // NPCs can override this to tweak with how costly particular movements are
  10243. //-----------------------------------------------------------------------------
  10244. bool CAI_BaseNPC::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
  10245. {
  10246. // We have nothing to say on the matter, but derived classes might
  10247. return false;
  10248. }
  10249. bool CAI_BaseNPC::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
  10250. {
  10251. return false;
  10252. }
  10253. bool CAI_BaseNPC::OverrideMove( float flInterval )
  10254. {
  10255. return false;
  10256. }
  10257. //=========================================================
  10258. // VecToYaw - turns a directional vector into a yaw value
  10259. // that points down that vector.
  10260. //=========================================================
  10261. float CAI_BaseNPC::VecToYaw( const Vector &vecDir )
  10262. {
  10263. if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0)
  10264. return GetLocalAngles().y;
  10265. return UTIL_VecToYaw( vecDir );
  10266. }
  10267. //-----------------------------------------------------------------------------
  10268. // Inherited from IAI_MotorMovementServices
  10269. //-----------------------------------------------------------------------------
  10270. float CAI_BaseNPC::CalcYawSpeed( void )
  10271. {
  10272. // Negative values are invalud
  10273. return -1.0f;
  10274. }
  10275. bool CAI_BaseNPC::OnCalcBaseMove( AILocalMoveGoal_t *pMoveGoal,
  10276. float distClear,
  10277. AIMoveResult_t *pResult )
  10278. {
  10279. if ( pMoveGoal->directTrace.pObstruction )
  10280. {
  10281. CBasePropDoor *pPropDoor = dynamic_cast<CBasePropDoor *>( pMoveGoal->directTrace.pObstruction );
  10282. if ( pPropDoor && OnUpcomingPropDoor( pMoveGoal, pPropDoor, distClear, pResult ) )
  10283. {
  10284. return true;
  10285. }
  10286. }
  10287. return false;
  10288. }
  10289. bool CAI_BaseNPC::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal,
  10290. float distClear,
  10291. AIMoveResult_t *pResult )
  10292. {
  10293. if ( pMoveGoal->directTrace.pObstruction )
  10294. {
  10295. CBaseDoor *pDoor = dynamic_cast<CBaseDoor *>( pMoveGoal->directTrace.pObstruction );
  10296. if ( pDoor && OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) )
  10297. {
  10298. return true;
  10299. }
  10300. }
  10301. return false;
  10302. }
  10303. bool CAI_BaseNPC::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal,
  10304. CBaseDoor *pDoor,
  10305. float distClear,
  10306. AIMoveResult_t *pResult )
  10307. {
  10308. if ( pMoveGoal->maxDist < distClear )
  10309. return false;
  10310. // By default, NPCs don't know how to open doors
  10311. if ( pDoor->m_toggle_state == TS_AT_BOTTOM || pDoor->m_toggle_state == TS_GOING_DOWN )
  10312. {
  10313. if ( distClear < 0.1 )
  10314. {
  10315. *pResult = AIMR_BLOCKED_ENTITY;
  10316. }
  10317. else
  10318. {
  10319. pMoveGoal->maxDist = distClear;
  10320. *pResult = AIMR_OK;
  10321. }
  10322. return true;
  10323. }
  10324. return false;
  10325. }
  10326. //-----------------------------------------------------------------------------
  10327. // Purpose:
  10328. // Input : pMoveGoal -
  10329. // pDoor -
  10330. // distClear -
  10331. // default -
  10332. // spawn -
  10333. // oldorg -
  10334. // pfPosition -
  10335. // neworg -
  10336. // Output : Returns true if movement is solved, false otherwise.
  10337. //-----------------------------------------------------------------------------
  10338. bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal,
  10339. CBasePropDoor *pDoor,
  10340. float distClear,
  10341. AIMoveResult_t *pResult )
  10342. {
  10343. if ( (pMoveGoal->flags & AILMG_TARGET_IS_GOAL) && pMoveGoal->maxDist < distClear )
  10344. return false;
  10345. if ( pMoveGoal->maxDist + GetHullWidth() * .25 < distClear )
  10346. return false;
  10347. if (pDoor == m_hOpeningDoor)
  10348. {
  10349. if ( pDoor->IsNPCOpening( this ) )
  10350. {
  10351. // We're in the process of opening the door, don't be blocked by it.
  10352. pMoveGoal->maxDist = distClear;
  10353. *pResult = AIMR_OK;
  10354. return true;
  10355. }
  10356. m_hOpeningDoor = NULL;
  10357. }
  10358. if ((CapabilitiesGet() & bits_CAP_DOORS_GROUP) && !pDoor->IsDoorLocked() && (pDoor->IsDoorClosed() || pDoor->IsDoorClosing()))
  10359. {
  10360. AI_Waypoint_t *pOpenDoorRoute = NULL;
  10361. opendata_t opendata;
  10362. pDoor->GetNPCOpenData(this, opendata);
  10363. // dvs: FIXME: local route might not be sufficient
  10364. pOpenDoorRoute = GetPathfinder()->BuildLocalRoute(
  10365. GetLocalOrigin(),
  10366. opendata.vecStandPos,
  10367. NULL,
  10368. bits_WP_TO_DOOR | bits_WP_DONT_SIMPLIFY,
  10369. NO_NODE,
  10370. bits_BUILD_GROUND | bits_BUILD_IGNORE_NPCS,
  10371. 0.0);
  10372. if ( pOpenDoorRoute )
  10373. {
  10374. if ( AIIsDebuggingDoors(this) )
  10375. {
  10376. NDebugOverlay::Cross3D(opendata.vecStandPos + Vector(0,0,1), 32, 255, 255, 255, false, 1.0 );
  10377. Msg( "Opening door!\n" );
  10378. }
  10379. // Attach the door to the waypoint so we open it when we get there.
  10380. // dvs: FIXME: this is kind of bullshit, I need to find the exact waypoint to open the door
  10381. // should I just walk the path until I find it?
  10382. pOpenDoorRoute->m_hData = pDoor;
  10383. GetNavigator()->GetPath()->PrependWaypoints( pOpenDoorRoute );
  10384. m_hOpeningDoor = pDoor;
  10385. pMoveGoal->maxDist = distClear;
  10386. *pResult = AIMR_CHANGE_TYPE;
  10387. return true;
  10388. }
  10389. else
  10390. AIDoorDebugMsg( this, "Failed create door route!\n" );
  10391. }
  10392. return false;
  10393. }
  10394. //-----------------------------------------------------------------------------
  10395. // Purpose: Called by the navigator to initiate the opening of a prop_door
  10396. // that is in our way.
  10397. //-----------------------------------------------------------------------------
  10398. void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor )
  10399. {
  10400. // dvs: not quite working, disabled for now.
  10401. //opendata_t opendata;
  10402. //pDoor->GetNPCOpenData(this, opendata);
  10403. //
  10404. //if (HaveSequenceForActivity(opendata.eActivity))
  10405. //{
  10406. // SetIdealActivity(opendata.eActivity);
  10407. //}
  10408. //else
  10409. {
  10410. // We don't have an appropriate sequence, just open the door magically.
  10411. OpenPropDoorNow( pDoor );
  10412. }
  10413. }
  10414. //-----------------------------------------------------------------------------
  10415. // Purpose: Called when we are trying to open a prop_door and it's time to start
  10416. // the door moving. This is called either in response to an anim event
  10417. // or as a fallback when we don't have an appropriate open activity.
  10418. //-----------------------------------------------------------------------------
  10419. void CAI_BaseNPC::OpenPropDoorNow( CBasePropDoor *pDoor )
  10420. {
  10421. // Start the door moving.
  10422. pDoor->NPCOpenDoor(this);
  10423. // Wait for the door to finish opening before trying to move through the doorway.
  10424. m_flMoveWaitFinished = gpGlobals->curtime + pDoor->GetOpenInterval();
  10425. }
  10426. //-----------------------------------------------------------------------------
  10427. // Purpose: Called when the door we were trying to open becomes fully open.
  10428. // Input : pDoor -
  10429. //-----------------------------------------------------------------------------
  10430. void CAI_BaseNPC::OnDoorFullyOpen(CBasePropDoor *pDoor)
  10431. {
  10432. // We're done with the door.
  10433. m_hOpeningDoor = NULL;
  10434. }
  10435. //-----------------------------------------------------------------------------
  10436. // Purpose: Called when the door we were trying to open becomes blocked before opening.
  10437. // Input : pDoor -
  10438. //-----------------------------------------------------------------------------
  10439. void CAI_BaseNPC::OnDoorBlocked(CBasePropDoor *pDoor)
  10440. {
  10441. // dvs: FIXME: do something so that we don't loop forever trying to open this door
  10442. // not clearing out the door handle will cause the NPC to invalidate the connection
  10443. // We're done with the door.
  10444. //m_hOpeningDoor = NULL;
  10445. }
  10446. //-----------------------------------------------------------------------------
  10447. // Purpose: Template NPCs are marked as templates by the level designer. They
  10448. // do not spawn, but their keyvalues are saved for use by a template
  10449. // spawner.
  10450. //-----------------------------------------------------------------------------
  10451. bool CAI_BaseNPC::IsTemplate( void )
  10452. {
  10453. return HasSpawnFlags( SF_NPC_TEMPLATE );
  10454. }
  10455. //-----------------------------------------------------------------------------
  10456. //
  10457. // Movement code for walking + flying
  10458. //
  10459. //-----------------------------------------------------------------------------
  10460. int CAI_BaseNPC::FlyMove( const Vector& pfPosition, unsigned int mask )
  10461. {
  10462. Vector oldorg, neworg;
  10463. trace_t trace;
  10464. // try the move
  10465. VectorCopy( GetAbsOrigin(), oldorg );
  10466. VectorAdd( oldorg, pfPosition, neworg );
  10467. UTIL_TraceEntity( this, oldorg, neworg, mask, &trace );
  10468. if (trace.fraction == 1)
  10469. {
  10470. if ( (GetFlags() & FL_SWIM) && enginetrace->GetPointContents(trace.endpos) == CONTENTS_EMPTY )
  10471. return false; // swim monster left water
  10472. SetAbsOrigin( trace.endpos );
  10473. PhysicsTouchTriggers();
  10474. return true;
  10475. }
  10476. return false;
  10477. }
  10478. //-----------------------------------------------------------------------------
  10479. // Purpose:
  10480. // Input : ent -
  10481. // Dir - Normalized direction vector for movement.
  10482. // dist - Distance along 'Dir' to move.
  10483. // iMode -
  10484. // Output : Returns nonzero on success, zero on failure.
  10485. //-----------------------------------------------------------------------------
  10486. int CAI_BaseNPC::WalkMove( const Vector& vecPosition, unsigned int mask )
  10487. {
  10488. if ( GetFlags() & (FL_FLY | FL_SWIM) )
  10489. {
  10490. return FlyMove( vecPosition, mask );
  10491. }
  10492. if ( (GetFlags() & FL_ONGROUND) == 0 )
  10493. {
  10494. return 0;
  10495. }
  10496. trace_t trace;
  10497. Vector oldorg, neworg, end;
  10498. Vector move( vecPosition[0], vecPosition[1], 0.0f );
  10499. VectorCopy( GetAbsOrigin(), oldorg );
  10500. VectorAdd( oldorg, move, neworg );
  10501. // push down from a step height above the wished position
  10502. float flStepSize = sv_stepsize.GetFloat();
  10503. neworg[2] += flStepSize;
  10504. VectorCopy(neworg, end);
  10505. end[2] -= flStepSize*2;
  10506. UTIL_TraceEntity( this, neworg, end, mask, &trace );
  10507. if ( trace.allsolid )
  10508. return false;
  10509. if (trace.startsolid)
  10510. {
  10511. neworg[2] -= flStepSize;
  10512. UTIL_TraceEntity( this, neworg, end, mask, &trace );
  10513. if ( trace.allsolid || trace.startsolid )
  10514. return false;
  10515. }
  10516. if (trace.fraction == 1)
  10517. {
  10518. // if monster had the ground pulled out, go ahead and fall
  10519. if ( GetFlags() & FL_PARTIALGROUND )
  10520. {
  10521. SetAbsOrigin( oldorg + move );
  10522. PhysicsTouchTriggers();
  10523. SetGroundEntity( NULL );
  10524. return true;
  10525. }
  10526. return false; // walked off an edge
  10527. }
  10528. // check point traces down for dangling corners
  10529. SetAbsOrigin( trace.endpos );
  10530. if (UTIL_CheckBottom( this, NULL, flStepSize ) == 0)
  10531. {
  10532. if ( GetFlags() & FL_PARTIALGROUND )
  10533. {
  10534. // entity had floor mostly pulled out from underneath it
  10535. // and is trying to correct
  10536. PhysicsTouchTriggers();
  10537. return true;
  10538. }
  10539. // Reset to original position
  10540. SetAbsOrigin( oldorg );
  10541. return false;
  10542. }
  10543. if ( GetFlags() & FL_PARTIALGROUND )
  10544. {
  10545. // Con_Printf ("back on ground\n");
  10546. RemoveFlag( FL_PARTIALGROUND );
  10547. }
  10548. // the move is ok
  10549. SetGroundEntity( trace.m_pEnt );
  10550. PhysicsTouchTriggers();
  10551. return true;
  10552. }
  10553. //-----------------------------------------------------------------------------
  10554. static void AIMsgGuts( CAI_BaseNPC *pAI, unsigned flags, const char *pszMsg )
  10555. {
  10556. int len = strlen( pszMsg );
  10557. const char *pszFmt2 = NULL;
  10558. if ( len && pszMsg[len-1] == '\n' )
  10559. {
  10560. (const_cast<char *>(pszMsg))[len-1] = 0;
  10561. pszFmt2 = "%s (%s: %d/%s) [%d]\n";
  10562. }
  10563. else
  10564. pszFmt2 = "%s (%s: %d/%s) [%d]";
  10565. DevMsg( pszFmt2,
  10566. pszMsg,
  10567. pAI->GetClassname(),
  10568. pAI->entindex(),
  10569. ( pAI->GetEntityName() == NULL_STRING ) ? "<unnamed>" : STRING(pAI->GetEntityName()),
  10570. gpGlobals->tickcount );
  10571. }
  10572. void DevMsg( CAI_BaseNPC *pAI, unsigned flags, const char *pszFormat, ... )
  10573. {
  10574. if ( (flags & AIMF_IGNORE_SELECTED) || (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
  10575. {
  10576. va_list ap;
  10577. va_start(ap, pszFormat);
  10578. char szTempMsgBuf[512];
  10579. V_vsprintf_safe( szTempMsgBuf, pszFormat, ap );
  10580. AIMsgGuts( pAI, flags, szTempMsgBuf );
  10581. va_end(ap);
  10582. }
  10583. }
  10584. //-----------------------------------------------------------------------------
  10585. void DevMsg( CAI_BaseNPC *pAI, const char *pszFormat, ... )
  10586. {
  10587. if ( (pAI->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
  10588. {
  10589. va_list ap;
  10590. va_start(ap, pszFormat);
  10591. char szTempMsgBuf[512];
  10592. V_vsprintf_safe( szTempMsgBuf, pszFormat, ap );
  10593. AIMsgGuts( pAI, 0, szTempMsgBuf );
  10594. va_end(ap);
  10595. }
  10596. }
  10597. //-----------------------------------------------------------------------------
  10598. bool CAI_BaseNPC::IsPlayerAlly( CBasePlayer *pPlayer )
  10599. {
  10600. if ( pPlayer == NULL )
  10601. {
  10602. // in multiplayer mode we need a valid pPlayer
  10603. // or override this virtual function
  10604. if ( !AI_IsSinglePlayer() )
  10605. return false;
  10606. // NULL means single player mode
  10607. pPlayer = UTIL_GetLocalPlayer();
  10608. }
  10609. return ( !pPlayer || IRelationType( pPlayer ) == D_LI );
  10610. }
  10611. //-----------------------------------------------------------------------------
  10612. void CAI_BaseNPC::SetCommandGoal( const Vector &vecGoal )
  10613. {
  10614. m_vecCommandGoal = vecGoal;
  10615. m_CommandMoveMonitor.ClearMark();
  10616. }
  10617. //-----------------------------------------------------------------------------
  10618. void CAI_BaseNPC::ClearCommandGoal()
  10619. {
  10620. m_vecCommandGoal = vec3_invalid;
  10621. m_CommandMoveMonitor.ClearMark();
  10622. }
  10623. //-----------------------------------------------------------------------------
  10624. bool CAI_BaseNPC::IsInPlayerSquad() const
  10625. {
  10626. return ( m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() && !CAI_Squad::IsSilentMember(this) );
  10627. }
  10628. //-----------------------------------------------------------------------------
  10629. bool CAI_BaseNPC::CanBeUsedAsAFriend( void )
  10630. {
  10631. if ( IsCurSchedule(SCHED_FORCED_GO) || IsCurSchedule(SCHED_FORCED_GO_RUN) )
  10632. return false;
  10633. return true;
  10634. }
  10635. //-----------------------------------------------------------------------------
  10636. Vector CAI_BaseNPC::GetSmoothedVelocity( void )
  10637. {
  10638. if( GetNavType() == NAV_GROUND || GetNavType() == NAV_FLY )
  10639. {
  10640. return m_pMotor->GetCurVel();
  10641. }
  10642. return BaseClass::GetSmoothedVelocity();
  10643. }
  10644. //-----------------------------------------------------------------------------
  10645. bool CAI_BaseNPC::IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition )
  10646. {
  10647. trace_t tr;
  10648. // By default, we ignore the viewer (me) when determining cover positions
  10649. CTraceFilterLOS filter( NULL, COLLISION_GROUP_NONE, this );
  10650. // If I'm trying to find cover from the player, and the player is in a vehicle,
  10651. // ignore the vehicle for the purpose of determining line of sight.
  10652. CBaseEntity *pEnemy = GetEnemy();
  10653. if ( pEnemy )
  10654. {
  10655. // Hack to see if our threat position is our enemy
  10656. bool bThreatPosIsEnemy = ( (vecThreat - GetEnemy()->EyePosition()).LengthSqr() < 0.1f );
  10657. if ( bThreatPosIsEnemy )
  10658. {
  10659. CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
  10660. if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
  10661. {
  10662. // Ignore the vehicle
  10663. filter.SetPassEntity( pCCEnemy->GetVehicleEntity() );
  10664. }
  10665. if ( !filter.GetPassEntity() )
  10666. {
  10667. filter.SetPassEntity( pEnemy );
  10668. }
  10669. }
  10670. }
  10671. AI_TraceLOS( vecThreat, vecPosition, this, &tr, &filter );
  10672. if( tr.fraction != 1.0 && hl2_episodic.GetBool() )
  10673. {
  10674. if( tr.m_pEnt->m_iClassname == m_iClassname )
  10675. {
  10676. // Don't hide behind buddies!
  10677. return false;
  10678. }
  10679. }
  10680. return (tr.fraction != 1.0);
  10681. }
  10682. //-----------------------------------------------------------------------------
  10683. float CAI_BaseNPC::SetWait( float minWait, float maxWait )
  10684. {
  10685. int minThinks = Ceil2Int( minWait * 10 );
  10686. if ( maxWait == 0.0 )
  10687. {
  10688. m_flWaitFinished = gpGlobals->curtime + ( 0.1 * minThinks );
  10689. }
  10690. else
  10691. {
  10692. if ( minThinks == 0 ) // random 0..n is almost certain to not return 0
  10693. minThinks = 1;
  10694. int maxThinks = Ceil2Int( maxWait * 10 );
  10695. m_flWaitFinished = gpGlobals->curtime + ( 0.1 * random->RandomInt( minThinks, maxThinks ) );
  10696. }
  10697. return m_flWaitFinished;
  10698. }
  10699. //-----------------------------------------------------------------------------
  10700. void CAI_BaseNPC::ClearWait()
  10701. {
  10702. m_flWaitFinished = FLT_MAX;
  10703. }
  10704. //-----------------------------------------------------------------------------
  10705. bool CAI_BaseNPC::IsWaitFinished()
  10706. {
  10707. return ( gpGlobals->curtime >= m_flWaitFinished );
  10708. }
  10709. //-----------------------------------------------------------------------------
  10710. bool CAI_BaseNPC::IsWaitSet()
  10711. {
  10712. return ( m_flWaitFinished != FLT_MAX );
  10713. }
  10714. void CAI_BaseNPC::TestPlayerPushing( CBaseEntity *pEntity )
  10715. {
  10716. if ( HasSpawnFlags( SF_NPC_NO_PLAYER_PUSHAWAY ) )
  10717. return;
  10718. // Heuristic for determining if the player is pushing me away
  10719. CBasePlayer *pPlayer = ToBasePlayer( pEntity );
  10720. if ( pPlayer && !( pPlayer->GetFlags() & FL_NOTARGET ) )
  10721. {
  10722. if ( (pPlayer->m_nButtons & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT)) ||
  10723. pPlayer->GetAbsVelocity().AsVector2D().LengthSqr() > 50*50 )
  10724. {
  10725. SetCondition( COND_PLAYER_PUSHING );
  10726. Vector vecPush = GetAbsOrigin() - pPlayer->GetAbsOrigin();
  10727. VectorNormalize( vecPush );
  10728. CascadePlayerPush( vecPush, pPlayer->WorldSpaceCenter() );
  10729. }
  10730. }
  10731. }
  10732. void CAI_BaseNPC::CascadePlayerPush( const Vector &push, const Vector &pushOrigin )
  10733. {
  10734. //
  10735. // Try to push any friends that are in the way.
  10736. //
  10737. float hullWidth = GetHullWidth();
  10738. const Vector & origin = GetAbsOrigin();
  10739. const Vector2D &origin2D = origin.AsVector2D();
  10740. const float MIN_Z_TO_TRANSMIT = GetHullHeight() * 0.5 + 0.1;
  10741. const float DIST_REQD_TO_TRANSMIT_PUSH_SQ = Square( hullWidth * 5 + 0.1 );
  10742. const float DIST_FROM_PUSH_VECTOR_REQD_SQ = Square( hullWidth + 0.1 );
  10743. Vector2D pushTestPoint = vec2_invalid;
  10744. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  10745. {
  10746. CAI_BaseNPC *pOther = g_AI_Manager.AccessAIs()[i];
  10747. if ( pOther != this && pOther->IRelationType(this) == D_LI && !pOther->HasCondition( COND_PLAYER_PUSHING ) )
  10748. {
  10749. const Vector &friendOrigin = pOther->GetAbsOrigin();
  10750. if ( fabsf( friendOrigin.z - origin.z ) < MIN_Z_TO_TRANSMIT &&
  10751. ( friendOrigin.AsVector2D() - origin.AsVector2D() ).LengthSqr() < DIST_REQD_TO_TRANSMIT_PUSH_SQ )
  10752. {
  10753. if ( pushTestPoint == vec2_invalid )
  10754. {
  10755. pushTestPoint = origin2D - pushOrigin.AsVector2D();
  10756. // No normalize, since it wants to just be a big number and we can't be less that a hull away
  10757. pushTestPoint *= 2000;
  10758. pushTestPoint += origin2D;
  10759. }
  10760. float t;
  10761. float distSq = CalcDistanceSqrToLine2D( friendOrigin.AsVector2D(), origin2D, pushTestPoint, &t );
  10762. if ( t > 0 && distSq < DIST_FROM_PUSH_VECTOR_REQD_SQ )
  10763. {
  10764. pOther->SetCondition( COND_PLAYER_PUSHING );
  10765. }
  10766. }
  10767. }
  10768. }
  10769. }
  10770. //-----------------------------------------------------------------------------
  10771. // Break into pieces!
  10772. //-----------------------------------------------------------------------------
  10773. void CAI_BaseNPC::Break( CBaseEntity *pBreaker )
  10774. {
  10775. m_takedamage = DAMAGE_NO;
  10776. Vector velocity;
  10777. AngularImpulse angVelocity;
  10778. IPhysicsObject *pPhysics = VPhysicsGetObject();
  10779. Vector origin;
  10780. QAngle angles;
  10781. AddSolidFlags( FSOLID_NOT_SOLID );
  10782. if ( pPhysics )
  10783. {
  10784. pPhysics->GetVelocity( &velocity, &angVelocity );
  10785. pPhysics->GetPosition( &origin, &angles );
  10786. pPhysics->RecheckCollisionFilter();
  10787. }
  10788. else
  10789. {
  10790. velocity = GetAbsVelocity();
  10791. QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity );
  10792. origin = GetAbsOrigin();
  10793. angles = GetAbsAngles();
  10794. }
  10795. breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity );
  10796. params.impactEnergyScale = m_impactEnergyScale;
  10797. params.defCollisionGroup = GetCollisionGroup();
  10798. if ( params.defCollisionGroup == COLLISION_GROUP_NONE )
  10799. {
  10800. // don't automatically make anything COLLISION_GROUP_NONE or it will
  10801. // collide with debris being ejected by breaking
  10802. params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE;
  10803. }
  10804. // no damage/damage force? set a burst of 100 for some movement
  10805. params.defBurstScale = 100;//pDamageInfo ? 0 : 100;
  10806. PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, false );
  10807. UTIL_Remove(this);
  10808. }
  10809. //-----------------------------------------------------------------------------
  10810. // Purpose: Input handler for breaking the breakable immediately.
  10811. //-----------------------------------------------------------------------------
  10812. void CAI_BaseNPC::InputBreak( inputdata_t &inputdata )
  10813. {
  10814. Break( inputdata.pActivator );
  10815. }
  10816. //-----------------------------------------------------------------------------
  10817. bool CAI_BaseNPC::FindNearestValidGoalPos( const Vector &vTestPoint, Vector *pResult )
  10818. {
  10819. AIMoveTrace_t moveTrace;
  10820. Vector vCandidate = vec3_invalid;
  10821. if ( GetNavigator()->CanFitAtPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) )
  10822. {
  10823. if ( GetMoveProbe()->CheckStandPosition( vTestPoint, MASK_SOLID_BRUSHONLY ) )
  10824. {
  10825. vCandidate = vTestPoint;
  10826. }
  10827. }
  10828. if ( vCandidate == vec3_invalid )
  10829. {
  10830. int iNearestNode = GetPathfinder()->NearestNodeToPoint( vTestPoint );
  10831. if ( iNearestNode != NO_NODE )
  10832. {
  10833. GetMoveProbe()->MoveLimit( NAV_GROUND,
  10834. g_pBigAINet->GetNodePosition(GetHullType(), iNearestNode ),
  10835. vTestPoint,
  10836. MASK_SOLID_BRUSHONLY,
  10837. NULL,
  10838. 0,
  10839. &moveTrace );
  10840. if ( ( moveTrace.vEndPosition - vTestPoint ).Length2DSqr() < Square( GetHullWidth() * 3.0 ) &&
  10841. GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY ) )
  10842. {
  10843. vCandidate = moveTrace.vEndPosition;
  10844. }
  10845. }
  10846. }
  10847. if ( vCandidate != vec3_invalid )
  10848. {
  10849. AI_Waypoint_t *pPathToPoint = GetPathfinder()->BuildRoute( GetAbsOrigin(), vCandidate, AI_GetSinglePlayer(), 5*12, NAV_NONE, true );
  10850. if ( pPathToPoint )
  10851. {
  10852. GetPathfinder()->UnlockRouteNodes( pPathToPoint );
  10853. CAI_Path tempPath;
  10854. tempPath.SetWaypoints( pPathToPoint ); // path object will delete waypoints
  10855. }
  10856. else
  10857. vCandidate = vec3_invalid;
  10858. }
  10859. if ( vCandidate == vec3_invalid )
  10860. {
  10861. GetMoveProbe()->MoveLimit( NAV_GROUND,
  10862. GetAbsOrigin(),
  10863. vTestPoint,
  10864. MASK_SOLID_BRUSHONLY,
  10865. NULL,
  10866. 0,
  10867. &moveTrace );
  10868. vCandidate = moveTrace.vEndPosition;
  10869. }
  10870. if ( vCandidate == vec3_invalid )
  10871. return false;
  10872. if ( pResult != NULL )
  10873. {
  10874. *pResult = vCandidate;
  10875. }
  10876. return true;
  10877. }
  10878. //---------------------------------------------------------
  10879. // Pass a direction to get how far an NPC would see if facing
  10880. // that direction. Pass nothing to get the length of the NPC's
  10881. // current line of sight.
  10882. //---------------------------------------------------------
  10883. float CAI_BaseNPC::LineOfSightDist( const Vector &vecDir, float zEye )
  10884. {
  10885. Vector testDir;
  10886. if( vecDir == vec3_invalid )
  10887. {
  10888. testDir = EyeDirection3D();
  10889. }
  10890. else
  10891. {
  10892. testDir = vecDir;
  10893. }
  10894. if ( zEye == FLT_MAX )
  10895. zEye = EyePosition().z;
  10896. trace_t tr;
  10897. // Need to center trace so don't get erratic results based on orientation
  10898. Vector testPos( GetAbsOrigin().x, GetAbsOrigin().y, zEye );
  10899. AI_TraceLOS( testPos, testPos + testDir * MAX_COORD_RANGE, this, &tr );
  10900. return (tr.startpos - tr.endpos ).Length();
  10901. }
  10902. ConVar ai_LOS_mode( "ai_LOS_mode", "0", FCVAR_REPLICATED );
  10903. //-----------------------------------------------------------------------------
  10904. // Purpose: Use this to perform AI tracelines that are trying to determine LOS between points.
  10905. // LOS checks between entities should use FVisible.
  10906. //-----------------------------------------------------------------------------
  10907. void AI_TraceLOS( const Vector& vecAbsStart, const Vector& vecAbsEnd, CBaseEntity *pLooker, trace_t *ptr, ITraceFilter *pFilter )
  10908. {
  10909. AI_PROFILE_SCOPE( AI_TraceLOS );
  10910. if ( ai_LOS_mode.GetBool() )
  10911. {
  10912. // Don't use LOS tracefilter
  10913. UTIL_TraceLine( vecAbsStart, vecAbsEnd, MASK_BLOCKLOS, pLooker, COLLISION_GROUP_NONE, ptr );
  10914. return;
  10915. }
  10916. // Use the custom LOS trace filter
  10917. CTraceFilterLOS traceFilter( pLooker, COLLISION_GROUP_NONE );
  10918. if ( !pFilter )
  10919. pFilter = &traceFilter;
  10920. AI_TraceLine( vecAbsStart, vecAbsEnd, MASK_BLOCKLOS_AND_NPCS, pFilter, ptr );
  10921. }
  10922. void CAI_BaseNPC::InputSetSpeedModifierRadius( inputdata_t &inputdata )
  10923. {
  10924. m_iSpeedModRadius = inputdata.value.Int();
  10925. m_iSpeedModRadius *= m_iSpeedModRadius;
  10926. }
  10927. void CAI_BaseNPC::InputSetSpeedModifierSpeed( inputdata_t &inputdata )
  10928. {
  10929. m_iSpeedModSpeed = inputdata.value.Int();
  10930. }
  10931. //-----------------------------------------------------------------------------
  10932. // Purpose:
  10933. //-----------------------------------------------------------------------------
  10934. bool CAI_BaseNPC::IsAllowedToDodge( void )
  10935. {
  10936. // Can't do it if I'm not available
  10937. if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT )
  10938. return false;
  10939. return ( m_flNextDodgeTime <= gpGlobals->curtime );
  10940. }
  10941. //-----------------------------------------------------------------------------
  10942. // Purpose:
  10943. //-----------------------------------------------------------------------------
  10944. void CAI_BaseNPC::ParseScriptedNPCInteractions( void )
  10945. {
  10946. // Already parsed them?
  10947. if ( m_ScriptedInteractions.Count() )
  10948. return;
  10949. // Parse the model's key values and find any dynamic interactions
  10950. KeyValues *modelKeyValues = new KeyValues("");
  10951. CUtlBuffer buf( 1024, 0, CUtlBuffer::TEXT_BUFFER );
  10952. if (! modelinfo->GetModelKeyValue( GetModel(), buf ))
  10953. return;
  10954. if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), buf ) )
  10955. {
  10956. // Do we have a dynamic interactions section?
  10957. KeyValues *pkvInteractions = modelKeyValues->FindKey("dynamic_interactions");
  10958. if ( pkvInteractions )
  10959. {
  10960. KeyValues *pkvNode = pkvInteractions->GetFirstSubKey();
  10961. while ( pkvNode )
  10962. {
  10963. ScriptedNPCInteraction_t sInteraction;
  10964. sInteraction.iszInteractionName = AllocPooledString( pkvNode->GetName() );
  10965. // Trigger method
  10966. const char *pszTrigger = pkvNode->GetString( "trigger", NULL );
  10967. if ( pszTrigger )
  10968. {
  10969. if ( !Q_strncmp( pszTrigger, "auto_in_combat", 14) )
  10970. {
  10971. sInteraction.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT;
  10972. }
  10973. }
  10974. // Loop Break trigger method
  10975. pszTrigger = pkvNode->GetString( "loop_break_trigger", NULL );
  10976. if ( pszTrigger )
  10977. {
  10978. char szTrigger[256];
  10979. Q_strncpy( szTrigger, pszTrigger, sizeof(szTrigger) );
  10980. char *pszParam = strtok( szTrigger, " " );
  10981. while (pszParam)
  10982. {
  10983. if ( !Q_strncmp( pszParam, "on_damage", 9) )
  10984. {
  10985. sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE;
  10986. }
  10987. if ( !Q_strncmp( pszParam, "on_flashlight_illum", 19) )
  10988. {
  10989. sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM;
  10990. }
  10991. pszParam = strtok(NULL," ");
  10992. }
  10993. }
  10994. // Origin
  10995. const char *pszOrigin = pkvNode->GetString( "origin_relative", "0 0 0" );
  10996. UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), pszOrigin );
  10997. // Angles
  10998. const char *pszAngles = pkvNode->GetString( "angles_relative", NULL );
  10999. if ( pszAngles )
  11000. {
  11001. sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES;
  11002. UTIL_StringToVector( sInteraction.angRelativeAngles.Base(), pszAngles );
  11003. }
  11004. // Velocity
  11005. const char *pszVelocity = pkvNode->GetString( "velocity_relative", NULL );
  11006. if ( pszVelocity )
  11007. {
  11008. sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY;
  11009. UTIL_StringToVector( sInteraction.vecRelativeVelocity.Base(), pszVelocity );
  11010. }
  11011. // Entry Sequence
  11012. const char *pszSequence = pkvNode->GetString( "entry_sequence", NULL );
  11013. if ( pszSequence )
  11014. {
  11015. sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString( pszSequence );
  11016. }
  11017. // Entry Activity
  11018. const char *pszActivity = pkvNode->GetString( "entry_activity", NULL );
  11019. if ( pszActivity )
  11020. {
  11021. sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID( pszActivity );
  11022. }
  11023. // Sequence
  11024. pszSequence = pkvNode->GetString( "sequence", NULL );
  11025. if ( pszSequence )
  11026. {
  11027. sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( pszSequence );
  11028. }
  11029. // Activity
  11030. pszActivity = pkvNode->GetString( "activity", NULL );
  11031. if ( pszActivity )
  11032. {
  11033. sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID( pszActivity );
  11034. }
  11035. // Exit Sequence
  11036. pszSequence = pkvNode->GetString( "exit_sequence", NULL );
  11037. if ( pszSequence )
  11038. {
  11039. sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString( pszSequence );
  11040. }
  11041. // Exit Activity
  11042. pszActivity = pkvNode->GetString( "exit_activity", NULL );
  11043. if ( pszActivity )
  11044. {
  11045. sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID( pszActivity );
  11046. }
  11047. // Delay
  11048. sInteraction.flDelay = pkvNode->GetFloat( "delay", 10.0 );
  11049. // Delta
  11050. sInteraction.flDistSqr = pkvNode->GetFloat( "origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST) );
  11051. // Loop?
  11052. if ( pkvNode->GetFloat( "loop_in_action", 0 ) )
  11053. {
  11054. sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION;
  11055. }
  11056. // Fixup position?
  11057. const char *pszDontFixup = pkvNode->GetString( "dont_teleport_at_end", NULL );
  11058. if ( pszDontFixup )
  11059. {
  11060. if ( !Q_stricmp( pszDontFixup, "me" ) || !Q_stricmp( pszDontFixup, "both" ) )
  11061. {
  11062. sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME;
  11063. }
  11064. else if ( !Q_stricmp( pszDontFixup, "them" ) || !Q_stricmp( pszDontFixup, "both" ) )
  11065. {
  11066. sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM;
  11067. }
  11068. }
  11069. // Needs a weapon?
  11070. const char *pszNeedsWeapon = pkvNode->GetString( "needs_weapon", NULL );
  11071. if ( pszNeedsWeapon )
  11072. {
  11073. if ( !Q_strncmp( pszNeedsWeapon, "ME", 2 ) )
  11074. {
  11075. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
  11076. }
  11077. else if ( !Q_strncmp( pszNeedsWeapon, "THEM", 4 ) )
  11078. {
  11079. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
  11080. }
  11081. else if ( !Q_strncmp( pszNeedsWeapon, "BOTH", 4 ) )
  11082. {
  11083. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
  11084. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
  11085. }
  11086. }
  11087. // Specific weapon types
  11088. const char *pszWeaponName = pkvNode->GetString( "weapon_mine", NULL );
  11089. if ( pszWeaponName )
  11090. {
  11091. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME;
  11092. sInteraction.iszMyWeapon = AllocPooledString( pszWeaponName );
  11093. }
  11094. pszWeaponName = pkvNode->GetString( "weapon_theirs", NULL );
  11095. if ( pszWeaponName )
  11096. {
  11097. sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM;
  11098. sInteraction.iszTheirWeapon = AllocPooledString( pszWeaponName );
  11099. }
  11100. // Add it to the list
  11101. AddScriptedNPCInteraction( &sInteraction );
  11102. // Move to next interaction
  11103. pkvNode = pkvNode->GetNextKey();
  11104. }
  11105. }
  11106. }
  11107. modelKeyValues->deleteThis();
  11108. }
  11109. //-----------------------------------------------------------------------------
  11110. // Purpose:
  11111. //-----------------------------------------------------------------------------
  11112. void CAI_BaseNPC::AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction )
  11113. {
  11114. int nNewIndex = m_ScriptedInteractions.AddToTail();
  11115. if ( ai_debug_dyninteractions.GetBool() )
  11116. {
  11117. Msg("%s(%s): Added dynamic interaction: %s\n", GetClassname(), GetDebugName(), STRING(pInteraction->iszInteractionName) );
  11118. }
  11119. // Copy the interaction over
  11120. ScriptedNPCInteraction_t *pNewInt = &(m_ScriptedInteractions[nNewIndex]);
  11121. memcpy( pNewInt, pInteraction, sizeof(ScriptedNPCInteraction_t) );
  11122. // Calculate the local to world matrix
  11123. m_ScriptedInteractions[nNewIndex].matDesiredLocalToWorld.SetupMatrixOrgAngles( pInteraction->vecRelativeOrigin, pInteraction->angRelativeAngles );
  11124. }
  11125. //-----------------------------------------------------------------------------
  11126. // Purpose:
  11127. //-----------------------------------------------------------------------------
  11128. const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase )
  11129. {
  11130. if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID )
  11131. {
  11132. int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity );
  11133. return GetSequenceName( iSequence );
  11134. }
  11135. if ( pInteraction->sPhases[iPhase].iszSequence != NULL_STRING )
  11136. return STRING(pInteraction->sPhases[iPhase].iszSequence);
  11137. return NULL;
  11138. }
  11139. //-----------------------------------------------------------------------------
  11140. // Purpose:
  11141. //-----------------------------------------------------------------------------
  11142. void CAI_BaseNPC::StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive )
  11143. {
  11144. m_hInteractionPartner = pOtherNPC;
  11145. if ( bActive )
  11146. {
  11147. m_iInteractionState = NPCINT_RUNNING_ACTIVE;
  11148. }
  11149. else
  11150. {
  11151. m_iInteractionState = NPCINT_RUNNING_PARTNER;
  11152. }
  11153. m_bCannotDieDuringInteraction = true;
  11154. // Force the NPC into an idle schedule so they don't move.
  11155. // NOTE: We must set SCHED_IDLE_STAND directly, to prevent derived NPC
  11156. // classes from translating the idle stand schedule away to do something bad.
  11157. SetSchedule( GetSchedule(SCHED_IDLE_STAND) );
  11158. // Prepare the NPC for the script. Setting this allows the scripted sequences
  11159. // that we're about to create to immediately grab & use this NPC right away.
  11160. // This prevents the NPC from being able to make any schedule decisions
  11161. // before the DSS gets underway.
  11162. m_scriptState = SCRIPT_PLAYING;
  11163. }
  11164. //-----------------------------------------------------------------------------
  11165. // Purpose:
  11166. //-----------------------------------------------------------------------------
  11167. void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles )
  11168. {
  11169. variant_t emptyVariant;
  11170. StartRunningInteraction( pOtherNPC, true );
  11171. if ( pOtherNPC )
  11172. {
  11173. pOtherNPC->StartRunningInteraction( this, false );
  11174. //Msg("%s(%s) disabled collisions with %s(%s) at %0.2f\n", GetClassname(), GetDebugName(), pOtherNPC->GetClassName(), pOtherNPC->GetDebugName(), gpGlobals->curtime );
  11175. PhysDisableEntityCollisions( this, pOtherNPC );
  11176. }
  11177. // Determine which sequences we're going to use
  11178. const char *pszEntrySequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_ENTRY );
  11179. const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE );
  11180. const char *pszExitSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_EXIT );
  11181. // Debug
  11182. if ( ai_debug_dyninteractions.GetBool() )
  11183. {
  11184. if ( pOtherNPC )
  11185. {
  11186. Msg("%s(%s) starting dynamic interaction \"%s\" with %s(%s).\n", GetClassname(), GetDebugName(), STRING(pInteraction->iszInteractionName), pOtherNPC->GetClassname(), pOtherNPC->GetDebugName() );
  11187. if ( pszEntrySequence )
  11188. {
  11189. Msg( " - Entry sequence: %s\n", pszEntrySequence );
  11190. }
  11191. Msg( " - Core sequence: %s\n", pszSequence );
  11192. if ( pszExitSequence )
  11193. {
  11194. Msg( " - Exit sequence: %s\n", pszExitSequence );
  11195. }
  11196. }
  11197. }
  11198. // Create a scripted sequence name that's guaranteed to be unique
  11199. char szSSName[256];
  11200. if ( pOtherNPC )
  11201. {
  11202. Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d%s%d", GetDebugName(), entindex(), pOtherNPC->GetDebugName(), pOtherNPC->entindex() );
  11203. }
  11204. else
  11205. {
  11206. Q_snprintf( szSSName, sizeof(szSSName), "dss_%s%d", GetDebugName(), entindex() );
  11207. }
  11208. string_t iszSSName = AllocPooledString(szSSName);
  11209. // Setup next attempt
  11210. pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2);
  11211. // Spawn a scripted sequence for this NPC to play the interaction anim
  11212. CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
  11213. pMySequence->KeyValue( "m_iszEntry", pszEntrySequence );
  11214. pMySequence->KeyValue( "m_iszPlay", pszSequence );
  11215. pMySequence->KeyValue( "m_iszPostIdle", pszExitSequence );
  11216. pMySequence->KeyValue( "m_fMoveTo", "5" );
  11217. pMySequence->SetAbsOrigin( GetAbsOrigin() );
  11218. QAngle angDesired = GetAbsAngles();
  11219. angDesired[YAW] = m_flInteractionYaw;
  11220. pMySequence->SetAbsAngles( angDesired );
  11221. pMySequence->ForceSetTargetEntity( this, true );
  11222. pMySequence->SetName( iszSSName );
  11223. pMySequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
  11224. if ((pInteraction->iFlags & SCNPC_FLAG_DONT_TELEPORT_AT_END_ME) != 0)
  11225. {
  11226. pMySequence->AddSpawnFlags( SF_SCRIPT_DONT_TELEPORT_AT_END );
  11227. }
  11228. pMySequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 );
  11229. pMySequence->SetSynchPostIdles( true );
  11230. if ( ai_debug_dyninteractions.GetBool() )
  11231. {
  11232. pMySequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT;
  11233. }
  11234. // Spawn the matching scripted sequence for the other NPC
  11235. CAI_ScriptedSequence *pTheirSequence = NULL;
  11236. if ( pOtherNPC )
  11237. {
  11238. pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" );
  11239. pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence );
  11240. pTheirSequence->KeyValue( "m_iszPlay", pszSequence );
  11241. pTheirSequence->KeyValue( "m_iszPostIdle", pszExitSequence );
  11242. pTheirSequence->KeyValue( "m_fMoveTo", "5" );
  11243. pTheirSequence->SetAbsOrigin( vecOtherOrigin );
  11244. pTheirSequence->SetAbsAngles( angOtherAngles );
  11245. pTheirSequence->ForceSetTargetEntity( pOtherNPC, true );
  11246. pTheirSequence->SetName( iszSSName );
  11247. pTheirSequence->AddSpawnFlags( SF_SCRIPT_NOINTERRUPT | SF_SCRIPT_HIGH_PRIORITY | SF_SCRIPT_OVERRIDESTATE );
  11248. if ((pInteraction->iFlags & SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM) != 0)
  11249. {
  11250. pTheirSequence->AddSpawnFlags( SF_SCRIPT_DONT_TELEPORT_AT_END );
  11251. }
  11252. pTheirSequence->SetLoopActionSequence( (pInteraction->iFlags & SCNPC_FLAG_LOOP_IN_ACTION) != 0 );
  11253. pTheirSequence->SetSynchPostIdles( true );
  11254. if ( ai_debug_dyninteractions.GetBool() )
  11255. {
  11256. pTheirSequence->m_debugOverlays |= OVERLAY_TEXT_BIT | OVERLAY_PIVOT_BIT;
  11257. }
  11258. // Tell their sequence to keep their position relative to me
  11259. pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld );
  11260. }
  11261. // Spawn both sequences at once
  11262. pMySequence->Spawn();
  11263. if ( pTheirSequence )
  11264. {
  11265. pTheirSequence->Spawn();
  11266. }
  11267. // Call activate on both sequences at once
  11268. pMySequence->Activate();
  11269. if ( pTheirSequence )
  11270. {
  11271. pTheirSequence->Activate();
  11272. }
  11273. // Setup the outputs for both sequences. The first kills them both when it finishes
  11274. pMySequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11275. if ( pszExitSequence )
  11276. {
  11277. pMySequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11278. if ( pTheirSequence )
  11279. {
  11280. pTheirSequence->KeyValue( "OnPostIdleEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11281. }
  11282. }
  11283. else
  11284. {
  11285. pMySequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11286. if ( pTheirSequence )
  11287. {
  11288. pTheirSequence->KeyValue( "OnEndSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11289. }
  11290. }
  11291. if ( pTheirSequence )
  11292. {
  11293. pTheirSequence->KeyValue( "OnCancelFailedSequence", UTIL_VarArgs("%s,Kill,,0,-1", szSSName ) );
  11294. }
  11295. // Tell both sequences to start
  11296. pMySequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );
  11297. if ( pTheirSequence )
  11298. {
  11299. pTheirSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 );
  11300. }
  11301. }
  11302. //-----------------------------------------------------------------------------
  11303. // Purpose:
  11304. //-----------------------------------------------------------------------------
  11305. bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced )
  11306. {
  11307. if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT )
  11308. return false;
  11309. if ( !IsAlive() )
  11310. return false;
  11311. if ( IsOnFire() )
  11312. return false;
  11313. if ( IsCrouching() )
  11314. return false;
  11315. // Not while running scripted sequences
  11316. if ( m_hCine )
  11317. return false;
  11318. if ( bForced )
  11319. {
  11320. if ( !m_hForcedInteractionPartner )
  11321. return false;
  11322. }
  11323. else
  11324. {
  11325. if ( m_hForcedInteractionPartner || m_hInteractionPartner )
  11326. return false;
  11327. if ( IsInAScript() || !HasCondition(COND_IN_PVS) )
  11328. return false;
  11329. if ( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_MOVE_AWAY) )
  11330. return false;
  11331. // Default AI prevents interactions while melee attacking, but not ranged attacking
  11332. if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) )
  11333. return false;
  11334. }
  11335. return true;
  11336. }
  11337. //-----------------------------------------------------------------------------
  11338. // Purpose:
  11339. //-----------------------------------------------------------------------------
  11340. void CAI_BaseNPC::CheckForScriptedNPCInteractions( void )
  11341. {
  11342. // Are we being forced to interact with another NPC? If so, do that
  11343. if ( m_hForcedInteractionPartner )
  11344. {
  11345. CheckForcedNPCInteractions();
  11346. return;
  11347. }
  11348. // Otherwise, see if we can interaction with our enemy
  11349. if ( !m_ScriptedInteractions.Count() || !GetEnemy() )
  11350. return;
  11351. CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer();
  11352. if( !pNPC )
  11353. return;
  11354. // Recalculate interaction capability whenever we switch enemies
  11355. if ( m_hLastInteractionTestTarget != GetEnemy() )
  11356. {
  11357. m_hLastInteractionTestTarget = GetEnemy();
  11358. CalculateValidEnemyInteractions();
  11359. }
  11360. // First, make sure I'm in a state where I can do this
  11361. if ( !CanRunAScriptedNPCInteraction() )
  11362. return;
  11363. if ( pNPC && !pNPC->CanRunAScriptedNPCInteraction() )
  11364. return;
  11365. for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
  11366. {
  11367. ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i];
  11368. if ( !pInteraction->bValidOnCurrentEnemy )
  11369. continue;
  11370. if ( pInteraction->flNextAttemptTime > gpGlobals->curtime )
  11371. continue;
  11372. Vector vecOrigin;
  11373. QAngle angAngles;
  11374. if ( InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) )
  11375. {
  11376. m_iInteractionPlaying = i;
  11377. StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles );
  11378. break;
  11379. }
  11380. }
  11381. }
  11382. //-----------------------------------------------------------------------------
  11383. // Purpose: Calculate all the valid dynamic interactions we can perform with our current enemy
  11384. //-----------------------------------------------------------------------------
  11385. void CAI_BaseNPC::CalculateValidEnemyInteractions( void )
  11386. {
  11387. CAI_BaseNPC *pNPC = GetEnemy()->MyNPCPointer();
  11388. if ( !pNPC )
  11389. return;
  11390. bool bDebug = (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT && ai_debug_dyninteractions.GetBool());
  11391. if ( bDebug )
  11392. {
  11393. Msg("%s(%s): Computing valid interactions with %s(%s)\n", GetClassname(), GetDebugName(), pNPC->GetClassname(), pNPC->GetDebugName() );
  11394. }
  11395. bool bFound = false;
  11396. for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
  11397. {
  11398. ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i];
  11399. pInteraction->bValidOnCurrentEnemy = false;
  11400. // If the trigger method of the interaction isn't the one we're after, we're done
  11401. if ( pInteraction->iTriggerMethod != SNPCINT_AUTOMATIC_IN_COMBAT )
  11402. continue;
  11403. if ( !pNPC->GetModelPtr() )
  11404. continue;
  11405. // If we have a damage filter that prevents us hurting the enemy,
  11406. // don't interact with him, since most interactions kill the enemy.
  11407. // Create a fake damage info to test it with.
  11408. CTakeDamageInfo tempinfo( this, this, vec3_origin, vec3_origin, 1.0, DMG_BULLET );
  11409. if ( !pNPC->PassesDamageFilter( tempinfo ) )
  11410. continue;
  11411. // Check the weapon requirements for the interaction
  11412. if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_ME )
  11413. {
  11414. if ( !GetActiveWeapon())
  11415. continue;
  11416. // Check the specific weapon type
  11417. if ( pInteraction->iszMyWeapon != NULL_STRING && GetActiveWeapon()->m_iClassname != pInteraction->iszMyWeapon )
  11418. continue;
  11419. }
  11420. if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_THEM )
  11421. {
  11422. if ( !pNPC->GetActiveWeapon() )
  11423. continue;
  11424. // Check the specific weapon type
  11425. if ( pInteraction->iszTheirWeapon != NULL_STRING && pNPC->GetActiveWeapon()->m_iClassname != pInteraction->iszTheirWeapon )
  11426. continue;
  11427. }
  11428. // Script needs the other NPC, so make sure they're not dead
  11429. if ( !pNPC->IsAlive() )
  11430. continue;
  11431. // Use sequence? or activity?
  11432. if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID )
  11433. {
  11434. // Resolve the activity to a sequence, and make sure our enemy has it
  11435. const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE );
  11436. if ( !pszSequence )
  11437. continue;
  11438. if ( pNPC->LookupSequence( pszSequence ) == -1 )
  11439. continue;
  11440. }
  11441. else
  11442. {
  11443. if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 )
  11444. continue;
  11445. }
  11446. pInteraction->bValidOnCurrentEnemy = true;
  11447. bFound = true;
  11448. if ( bDebug )
  11449. {
  11450. Msg(" Found: %s\n", STRING(pInteraction->iszInteractionName) );
  11451. }
  11452. }
  11453. if ( bDebug && !bFound )
  11454. {
  11455. Msg(" No valid interactions found.\n");
  11456. }
  11457. }
  11458. //-----------------------------------------------------------------------------
  11459. // Purpose:
  11460. //-----------------------------------------------------------------------------
  11461. void CAI_BaseNPC::CheckForcedNPCInteractions( void )
  11462. {
  11463. // If we don't have an interaction, we're waiting for our partner to start it. Do nothing.
  11464. if ( m_iInteractionPlaying == NPCINT_NONE )
  11465. return;
  11466. CAI_BaseNPC *pNPC = m_hForcedInteractionPartner->MyNPCPointer();
  11467. bool bAbort = false;
  11468. // First, make sure both NPCs are able to do this
  11469. if ( !CanRunAScriptedNPCInteraction( true ) || !pNPC->CanRunAScriptedNPCInteraction( true ) )
  11470. {
  11471. // If we were still moving to our target, abort.
  11472. if ( m_iInteractionState == NPCINT_MOVING_TO_MARK )
  11473. {
  11474. bAbort = true;
  11475. }
  11476. else
  11477. {
  11478. return;
  11479. }
  11480. }
  11481. // Check to see if we can start our interaction. If we can, dance.
  11482. Vector vecOrigin;
  11483. QAngle angAngles;
  11484. ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[m_iInteractionPlaying];
  11485. if ( !bAbort )
  11486. {
  11487. if ( !InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) )
  11488. {
  11489. if ( ( gpGlobals->curtime > m_flForcedInteractionTimeout ) && ( m_iInteractionState == NPCINT_MOVING_TO_MARK ) )
  11490. {
  11491. bAbort = true;
  11492. }
  11493. else
  11494. {
  11495. return;
  11496. }
  11497. }
  11498. }
  11499. if ( bAbort )
  11500. {
  11501. if ( m_hForcedInteractionPartner )
  11502. {
  11503. // We've aborted a forced interaction. Let the mapmaker know.
  11504. m_OnForcedInteractionAborted.FireOutput( this, this );
  11505. }
  11506. CleanupForcedInteraction();
  11507. pNPC->CleanupForcedInteraction();
  11508. return;
  11509. }
  11510. StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles );
  11511. m_OnForcedInteractionStarted.FireOutput( this, this );
  11512. }
  11513. //-----------------------------------------------------------------------------
  11514. // Returns whether two NPCs can fit at each other's origin.
  11515. // Kinda like that movie with Eddie Murphy and Dan Akroyd.
  11516. //-----------------------------------------------------------------------------
  11517. bool CanNPCsTradePlaces( CAI_BaseNPC *pNPC1, CAI_BaseNPC *pNPC2, bool bDebug )
  11518. {
  11519. bool bTest1At2 = true;
  11520. bool bTest2At1 = true;
  11521. if ( ( pNPC1->GetHullMins().x <= pNPC2->GetHullMins().x ) &&
  11522. ( pNPC1->GetHullMins().y <= pNPC2->GetHullMins().y ) &&
  11523. ( pNPC1->GetHullMins().z <= pNPC2->GetHullMins().z ) &&
  11524. ( pNPC1->GetHullMaxs().x >= pNPC2->GetHullMaxs().x ) &&
  11525. ( pNPC1->GetHullMaxs().y >= pNPC2->GetHullMaxs().y ) &&
  11526. ( pNPC1->GetHullMaxs().z >= pNPC2->GetHullMaxs().z ) )
  11527. {
  11528. // 1 bigger than 2 in all axes, skip 2 in 1 test
  11529. bTest2At1 = false;
  11530. }
  11531. else if ( ( pNPC2->GetHullMins().x <= pNPC1->GetHullMins().x ) &&
  11532. ( pNPC2->GetHullMins().y <= pNPC1->GetHullMins().y ) &&
  11533. ( pNPC2->GetHullMins().z <= pNPC1->GetHullMins().z ) &&
  11534. ( pNPC2->GetHullMaxs().x >= pNPC1->GetHullMaxs().x ) &&
  11535. ( pNPC2->GetHullMaxs().y >= pNPC1->GetHullMaxs().y ) &&
  11536. ( pNPC2->GetHullMaxs().z >= pNPC1->GetHullMaxs().z ) )
  11537. {
  11538. // 2 bigger than 1 in all axes, skip 1 in 2 test
  11539. bTest1At2 = false;
  11540. }
  11541. trace_t tr;
  11542. CTraceFilterSkipTwoEntities traceFilter( pNPC1, pNPC2, COLLISION_GROUP_NONE );
  11543. if ( bTest1At2 )
  11544. {
  11545. AI_TraceHull( pNPC2->GetAbsOrigin(), pNPC2->GetAbsOrigin(), pNPC1->GetHullMins(), pNPC1->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr );
  11546. if ( tr.startsolid )
  11547. {
  11548. if ( bDebug )
  11549. {
  11550. NDebugOverlay::Box( pNPC2->GetAbsOrigin(), pNPC1->GetHullMins(), pNPC1->GetHullMaxs(), 255,0,0, true, 1.0 );
  11551. }
  11552. return false;
  11553. }
  11554. }
  11555. if ( bTest2At1 )
  11556. {
  11557. AI_TraceHull( pNPC1->GetAbsOrigin(), pNPC1->GetAbsOrigin(), pNPC2->GetHullMins(), pNPC2->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr );
  11558. if ( tr.startsolid )
  11559. {
  11560. if ( bDebug )
  11561. {
  11562. NDebugOverlay::Box( pNPC1->GetAbsOrigin(), pNPC2->GetHullMins(), pNPC2->GetHullMaxs(), 255,0,0, true, 1.0 );
  11563. }
  11564. return false;
  11565. }
  11566. }
  11567. return true;
  11568. }
  11569. //-----------------------------------------------------------------------------
  11570. // Purpose:
  11571. //-----------------------------------------------------------------------------
  11572. bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles )
  11573. {
  11574. // Get a matrix that'll convert from my local interaction space to world space
  11575. VMatrix matMeToWorld, matLocalToWorld;
  11576. QAngle angMyCurrent = GetAbsAngles();
  11577. angMyCurrent[YAW] = m_flInteractionYaw;
  11578. matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angMyCurrent );
  11579. MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld );
  11580. // Get the desired NPC position in worldspace
  11581. vecOrigin = matLocalToWorld.GetTranslation();
  11582. MatrixToAngles( matLocalToWorld, angAngles );
  11583. bool bDebug = ai_debug_dyninteractions.GetBool();
  11584. if ( bDebug )
  11585. {
  11586. NDebugOverlay::Axis( vecOrigin, angAngles, 20, true, 0.1 );
  11587. }
  11588. // Determine whether or not the enemy is on the target
  11589. float flDistSqr = (vecOrigin - pOtherNPC->GetAbsOrigin()).LengthSqr();
  11590. if ( flDistSqr > pInteraction->flDistSqr )
  11591. {
  11592. if ( bDebug )
  11593. {
  11594. if ( m_debugOverlays & OVERLAY_NPC_SELECTED_BIT || pOtherNPC->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  11595. {
  11596. if ( ai_debug_dyninteractions.GetFloat() == 2 )
  11597. {
  11598. Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: <%0.2f (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
  11599. pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, pInteraction->flDistSqr, vecOrigin.x, vecOrigin.y, vecOrigin.z );
  11600. }
  11601. }
  11602. }
  11603. return false;
  11604. }
  11605. if ( bDebug )
  11606. {
  11607. Msg("DYNINT: (%s) testing interaction \"%s\"\n", GetDebugName(), STRING(pInteraction->iszInteractionName) );
  11608. Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  11609. Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr,
  11610. pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z );
  11611. if ( pOtherNPC )
  11612. {
  11613. float flOtherSpeed = pOtherNPC->GetSequenceGroundSpeed( pOtherNPC->GetSequence() );
  11614. Msg(" %s Speed: %.2f\n", pOtherNPC->GetSequenceName( pOtherNPC->GetSequence() ), flOtherSpeed);
  11615. }
  11616. }
  11617. // Angle check, if we're supposed to
  11618. if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES )
  11619. {
  11620. QAngle angEnemyAngles = pOtherNPC->GetAbsAngles();
  11621. bool bMatches = true;
  11622. for ( int ang = 0; ang < 3; ang++ )
  11623. {
  11624. float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] );
  11625. if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF )
  11626. {
  11627. bMatches = false;
  11628. break;
  11629. }
  11630. }
  11631. if ( !bMatches )
  11632. return false;
  11633. if ( bDebug )
  11634. {
  11635. Msg(" %s angle matched: (%0.2f %0.2f %0.2f), desired (%0.2f, %0.2f, %0.2f)\n", GetDebugName(),
  11636. anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) );
  11637. }
  11638. }
  11639. // TODO: Velocity check, if we're supposed to
  11640. if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY )
  11641. {
  11642. }
  11643. // Valid so far. Now check to make sure there's nothing in the way.
  11644. // This isn't a very good method of checking, but it's cheap and rules out the problems we're seeing so far.
  11645. // If we start getting interactions that start a fair distance apart, we're going to need to do more work here.
  11646. trace_t tr;
  11647. AI_TraceLine( EyePosition(), pOtherNPC->EyePosition(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
  11648. if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC )
  11649. {
  11650. if ( bDebug )
  11651. {
  11652. Msg( " %s Interaction was blocked.\n", GetDebugName() );
  11653. NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
  11654. NDebugOverlay::Line( pOtherNPC->EyePosition(), tr.endpos, 255,0,0, true, 1.0 );
  11655. }
  11656. return false;
  11657. }
  11658. if ( bDebug )
  11659. {
  11660. NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
  11661. }
  11662. // Do a knee-level trace to find low physics objects
  11663. Vector vecMyKnee, vecOtherKnee;
  11664. CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecMyKnee );
  11665. pOtherNPC->CollisionProp()->NormalizedToWorldSpace( Vector(0,0,0.25f), &vecOtherKnee );
  11666. AI_TraceLine( vecMyKnee, vecOtherKnee, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
  11667. if ( tr.fraction != 1.0 && tr.m_pEnt != pOtherNPC )
  11668. {
  11669. if ( bDebug )
  11670. {
  11671. Msg( " %s Interaction was blocked.\n", GetDebugName() );
  11672. NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
  11673. NDebugOverlay::Line( vecOtherKnee, tr.endpos, 255,0,0, true, 1.0 );
  11674. }
  11675. return false;
  11676. }
  11677. if ( bDebug )
  11678. {
  11679. NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, true, 1.0 );
  11680. }
  11681. // Finally, make sure the NPC can actually fit at the interaction position
  11682. // This solves problems with NPCs who are a few units or so above the
  11683. // interaction point, and would sink into the ground when playing the anim.
  11684. CTraceFilterSkipTwoEntities traceFilter( pOtherNPC, this, COLLISION_GROUP_NONE );
  11685. AI_TraceHull( vecOrigin, vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), MASK_SOLID, &traceFilter, &tr );
  11686. if ( tr.startsolid )
  11687. {
  11688. if ( bDebug )
  11689. {
  11690. NDebugOverlay::Box( vecOrigin, pOtherNPC->GetHullMins(), pOtherNPC->GetHullMaxs(), 255,0,0, true, 1.0 );
  11691. }
  11692. return false;
  11693. }
  11694. // If the NPCs are swapping places during this interaction, make sure they can fit at each
  11695. // others' origins before allowing the interaction.
  11696. if ( !CanNPCsTradePlaces( this, pOtherNPC, bDebug ) )
  11697. {
  11698. return false;
  11699. }
  11700. return true;
  11701. }
  11702. //-----------------------------------------------------------------------------
  11703. // Purpose: Return true if this NPC cannot die because it's in an interaction
  11704. // and the flag has been set by the animation.
  11705. //-----------------------------------------------------------------------------
  11706. bool CAI_BaseNPC::HasInteractionCantDie( void )
  11707. {
  11708. return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() );
  11709. }
  11710. //-----------------------------------------------------------------------------
  11711. // Purpose:
  11712. // Input : &inputdata -
  11713. //-----------------------------------------------------------------------------
  11714. void CAI_BaseNPC::InputForceInteractionWithNPC( inputdata_t &inputdata )
  11715. {
  11716. // Get the interaction name & target
  11717. char parseString[255];
  11718. Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString));
  11719. // First, the target's name
  11720. char *pszParam = strtok(parseString," ");
  11721. if ( !pszParam || !pszParam[0] )
  11722. {
  11723. Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC <target NPC> <interaction name>\n", GetClassname(), GetDebugName(), inputdata.value.String() );
  11724. return;
  11725. }
  11726. // Find the target
  11727. CBaseEntity *pTarget = FindNamedEntity( pszParam );
  11728. if ( !pTarget )
  11729. {
  11730. Warning("%s(%s) received ForceInteractionWithNPC input, but couldn't find entity named: %s\n", GetClassname(), GetDebugName(), pszParam );
  11731. return;
  11732. }
  11733. CAI_BaseNPC *pNPC = pTarget->MyNPCPointer();
  11734. if ( !pNPC || !pNPC->GetModelPtr() )
  11735. {
  11736. Warning("%s(%s) received ForceInteractionWithNPC input, but entity named %s cannot run dynamic interactions.\n", GetClassname(), GetDebugName(), pszParam );
  11737. return;
  11738. }
  11739. // Second, the interaction name
  11740. pszParam = strtok(NULL," ");
  11741. if ( !pszParam || !pszParam[0] )
  11742. {
  11743. Warning("%s(%s) received ForceInteractionWithNPC input with bad parameters: %s\nFormat should be: ForceInteractionWithNPC <target NPC> <interaction name>\n", GetClassname(), GetDebugName(), inputdata.value.String() );
  11744. return;
  11745. }
  11746. // Find the interaction from the name, and ensure it's one that the target NPC can play
  11747. int iInteraction = -1;
  11748. for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ )
  11749. {
  11750. if ( Q_strncmp( pszParam, STRING(m_ScriptedInteractions[i].iszInteractionName), strlen(pszParam) ) )
  11751. continue;
  11752. // Use sequence? or activity?
  11753. if ( m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID )
  11754. {
  11755. if ( !pNPC->HaveSequenceForActivity( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity ) )
  11756. {
  11757. // Other NPC may have all the matching sequences, but just without the activity specified.
  11758. // Lets find a single sequence for us, and ensure they have a matching one.
  11759. int iMySeq = SelectWeightedSequence( (Activity)m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iActivity );
  11760. if ( pNPC->LookupSequence( GetSequenceName(iMySeq) ) == -1 )
  11761. continue;
  11762. }
  11763. }
  11764. else
  11765. {
  11766. if ( pNPC->LookupSequence( STRING(m_ScriptedInteractions[i].sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 )
  11767. continue;
  11768. }
  11769. iInteraction = i;
  11770. break;
  11771. }
  11772. if ( iInteraction == -1 )
  11773. {
  11774. 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() );
  11775. return;
  11776. }
  11777. // Found both pieces of data, lets dance.
  11778. StartForcedInteraction( pNPC, iInteraction );
  11779. pNPC->StartForcedInteraction( this, NPCINT_NONE );
  11780. }
  11781. //-----------------------------------------------------------------------------
  11782. // Purpose:
  11783. //-----------------------------------------------------------------------------
  11784. void CAI_BaseNPC::StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction )
  11785. {
  11786. m_hForcedInteractionPartner = pNPC;
  11787. ClearSchedule( "Starting a forced interaction" );
  11788. m_flForcedInteractionTimeout = gpGlobals->curtime + 8.0f;
  11789. m_iInteractionPlaying = iInteraction;
  11790. m_iInteractionState = NPCINT_MOVING_TO_MARK;
  11791. }
  11792. //-----------------------------------------------------------------------------
  11793. // Purpose:
  11794. //-----------------------------------------------------------------------------
  11795. void CAI_BaseNPC::CleanupForcedInteraction( void )
  11796. {
  11797. m_hForcedInteractionPartner = NULL;
  11798. m_iInteractionPlaying = NPCINT_NONE;
  11799. m_iInteractionState = NPCINT_NOT_RUNNING;
  11800. m_flForcedInteractionTimeout = 0;
  11801. }
  11802. //-----------------------------------------------------------------------------
  11803. // Purpose: Calculate a position to move to so that I can interact with my
  11804. // target NPC.
  11805. //
  11806. // FIXME: THIS ONLY WORKS FOR INTERACTIONS THAT REQUIRE THE TARGET
  11807. // NPC TO BE SOME DISTANCE DIRECTLY IN FRONT OF ME.
  11808. //-----------------------------------------------------------------------------
  11809. void CAI_BaseNPC::CalculateForcedInteractionPosition( void )
  11810. {
  11811. if ( m_iInteractionPlaying == NPCINT_NONE )
  11812. return;
  11813. ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
  11814. // Pretend I was facing the target, and extrapolate from that the position I should be at
  11815. Vector vecToTarget = m_hForcedInteractionPartner->GetAbsOrigin() - GetAbsOrigin();
  11816. VectorNormalize( vecToTarget );
  11817. QAngle angToTarget;
  11818. VectorAngles( vecToTarget, angToTarget );
  11819. // Get the desired position in worldspace, relative to the target
  11820. VMatrix matMeToWorld, matLocalToWorld;
  11821. matMeToWorld.SetupMatrixOrgAngles( GetAbsOrigin(), angToTarget );
  11822. MatrixMultiply( matMeToWorld, pInteraction->matDesiredLocalToWorld, matLocalToWorld );
  11823. Vector vecOrigin = GetAbsOrigin() - matLocalToWorld.GetTranslation();
  11824. m_vecForcedWorldPosition = m_hForcedInteractionPartner->GetAbsOrigin() + vecOrigin;
  11825. //NDebugOverlay::Axis( m_vecForcedWorldPosition, angToTarget, 20, true, 3.0 );
  11826. }
  11827. //-----------------------------------------------------------------------------
  11828. // Purpose:
  11829. // Input : *pPlayer -
  11830. //-----------------------------------------------------------------------------
  11831. void CAI_BaseNPC::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
  11832. {
  11833. #ifdef HL2_EPISODIC
  11834. if ( IsActiveDynamicInteraction() )
  11835. {
  11836. ScriptedNPCInteraction_t *pInteraction = GetRunningDynamicInteraction();
  11837. if ( pInteraction->iLoopBreakTriggerMethod & SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM )
  11838. {
  11839. // Only do this in alyx darkness mode
  11840. if ( HL2GameRules()->IsAlyxInDarknessMode() )
  11841. {
  11842. // Can only break when we're in the action anim
  11843. if ( m_hCine->IsPlayingAction() )
  11844. {
  11845. m_hCine->StopActionLoop( true );
  11846. }
  11847. }
  11848. }
  11849. }
  11850. #endif
  11851. }
  11852. //-----------------------------------------------------------------------------
  11853. // Purpose:
  11854. //-----------------------------------------------------------------------------
  11855. void CAI_BaseNPC::ModifyOrAppendCriteria( AI_CriteriaSet& set )
  11856. {
  11857. BaseClass::ModifyOrAppendCriteria( set );
  11858. // Append time since seen player
  11859. if ( m_flLastSawPlayerTime )
  11860. {
  11861. set.AppendCriteria( "timesinceseenplayer", UTIL_VarArgs( "%f", gpGlobals->curtime - m_flLastSawPlayerTime ) );
  11862. }
  11863. else
  11864. {
  11865. set.AppendCriteria( "timesinceseenplayer", "-1" );
  11866. }
  11867. // Append distance to my enemy
  11868. if ( GetEnemy() )
  11869. {
  11870. set.AppendCriteria( "distancetoenemy", UTIL_VarArgs( "%f", EnemyDistance(GetEnemy()) ) );
  11871. }
  11872. else
  11873. {
  11874. set.AppendCriteria( "distancetoenemy", "-1" );
  11875. }
  11876. }
  11877. //-----------------------------------------------------------------------------
  11878. // If I were crouching at my current location, could I shoot this target?
  11879. //-----------------------------------------------------------------------------
  11880. bool CAI_BaseNPC::CouldShootIfCrouching( CBaseEntity *pTarget )
  11881. {
  11882. bool bWasStanding = !IsCrouching();
  11883. Crouch();
  11884. Vector vecTarget;
  11885. if (GetActiveWeapon())
  11886. {
  11887. vecTarget = pTarget->BodyTarget( GetActiveWeapon()->GetLocalOrigin() );
  11888. }
  11889. else
  11890. {
  11891. vecTarget = pTarget->BodyTarget( GetLocalOrigin() );
  11892. }
  11893. bool bResult = WeaponLOSCondition( GetLocalOrigin(), vecTarget, false );
  11894. if ( bWasStanding )
  11895. {
  11896. Stand();
  11897. }
  11898. return bResult;
  11899. }
  11900. //-----------------------------------------------------------------------------
  11901. // Purpose:
  11902. //-----------------------------------------------------------------------------
  11903. bool CAI_BaseNPC::IsCrouchedActivity( Activity activity )
  11904. {
  11905. Activity realActivity = TranslateActivity(activity);
  11906. switch ( realActivity )
  11907. {
  11908. case ACT_RELOAD_LOW:
  11909. case ACT_COVER_LOW:
  11910. case ACT_COVER_PISTOL_LOW:
  11911. case ACT_COVER_SMG1_LOW:
  11912. case ACT_RELOAD_SMG1_LOW:
  11913. return true;
  11914. }
  11915. return false;
  11916. }
  11917. //-----------------------------------------------------------------------------
  11918. // Purpose: Get shoot position of BCC at an arbitrary position
  11919. //-----------------------------------------------------------------------------
  11920. Vector CAI_BaseNPC::Weapon_ShootPosition( void )
  11921. {
  11922. Vector right;
  11923. GetVectors( NULL, &right, NULL );
  11924. bool bStanding = !IsCrouching();
  11925. if ( bStanding && (CapabilitiesGet() & bits_CAP_DUCK) )
  11926. {
  11927. if ( IsCrouchedActivity( GetActivity() ) )
  11928. {
  11929. bStanding = false;
  11930. }
  11931. }
  11932. if ( !bStanding )
  11933. return (GetAbsOrigin() + GetCrouchGunOffset() + right * 8);
  11934. return BaseClass::Weapon_ShootPosition();
  11935. }
  11936. //-----------------------------------------------------------------------------
  11937. // Purpose:
  11938. //-----------------------------------------------------------------------------
  11939. bool CAI_BaseNPC::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity )
  11940. {
  11941. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  11942. {
  11943. if ( ai_test_moveprobe_ignoresmall.GetBool() && IsNavigationUrgent() )
  11944. {
  11945. IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
  11946. if ( pPhysics->IsMoveable() && pPhysics->GetMass() < 40.0 )
  11947. return false;
  11948. }
  11949. }
  11950. return true;
  11951. }
  11952. //-----------------------------------------------------------------------------
  11953. // Purpose:
  11954. //-----------------------------------------------------------------------------
  11955. bool CAI_BaseNPC::Crouch( void )
  11956. {
  11957. m_bIsCrouching = true;
  11958. return true;
  11959. }
  11960. //-----------------------------------------------------------------------------
  11961. // Purpose:
  11962. //-----------------------------------------------------------------------------
  11963. bool CAI_BaseNPC::IsCrouching( void )
  11964. {
  11965. return ( (CapabilitiesGet() & bits_CAP_DUCK) && m_bIsCrouching );
  11966. }
  11967. //-----------------------------------------------------------------------------
  11968. // Purpose:
  11969. //-----------------------------------------------------------------------------
  11970. bool CAI_BaseNPC::Stand( void )
  11971. {
  11972. if ( m_bForceCrouch )
  11973. return false;
  11974. m_bIsCrouching = false;
  11975. DesireStand();
  11976. return true;
  11977. }
  11978. //-----------------------------------------------------------------------------
  11979. // Purpose:
  11980. //-----------------------------------------------------------------------------
  11981. void CAI_BaseNPC::DesireCrouch( void )
  11982. {
  11983. m_bCrouchDesired = true;
  11984. }
  11985. bool CAI_BaseNPC::IsInChoreo() const
  11986. {
  11987. return m_bInChoreo;
  11988. }