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.

3151 lines
96 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ai_behavior_actbusy.h"
  8. #include "ai_navigator.h"
  9. #include "ai_hint.h"
  10. #include "ai_behavior_follow.h"
  11. #include "KeyValues.h"
  12. #include "filesystem.h"
  13. #include "eventqueue.h"
  14. #include "ai_playerally.h"
  15. #include "SoundEmitterSystem/isoundemittersystembase.h"
  16. #include "entityblocker.h"
  17. #include "npcevent.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. #define ACTBUSY_SEE_ENTITY_TIMEOUT 1.0f
  21. #define ACTBUSY_COMBAT_PLAYER_MAX_DIST 720.0f // NPCs in combat actbusy should try to stay within this distance of the player.
  22. ConVar ai_actbusy_search_time( "ai_actbusy_search_time","10.0" );
  23. ConVar ai_debug_actbusy( "ai_debug_actbusy", "0", FCVAR_CHEAT, "Used to debug actbusy behavior. Usage:\n\
  24. 1: Constantly draw lines from NPCs to the actbusy nodes they've chosen to actbusy at.\n\
  25. 2: Whenever an NPC makes a decision to use an actbusy, show which actbusy they've chosen.\n\
  26. 3: Selected NPCs (with npc_select) will report why they're not choosing actbusy nodes.\n\
  27. 4: Display debug output of actbusy logic.\n\
  28. 5: Display safe zone volumes and info.\n\
  29. ");
  30. // Anim events
  31. static int AE_ACTBUSY_WEAPON_FIRE_ON;
  32. static int AE_ACTBUSY_WEAPON_FIRE_OFF;
  33. BEGIN_DATADESC( CAI_ActBusyBehavior )
  34. DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
  35. DEFINE_FIELD( m_bForceActBusy, FIELD_BOOLEAN ),
  36. DEFINE_CUSTOM_FIELD( m_ForcedActivity, ActivityDataOps() ),
  37. DEFINE_FIELD( m_bTeleportToBusy, FIELD_BOOLEAN ),
  38. DEFINE_FIELD( m_bUseNearestBusy, FIELD_BOOLEAN ),
  39. DEFINE_FIELD( m_bLeaving, FIELD_BOOLEAN ),
  40. DEFINE_FIELD( m_bVisibleOnly, FIELD_BOOLEAN ),
  41. DEFINE_FIELD( m_bUseRenderBoundsForCollision, FIELD_BOOLEAN ),
  42. DEFINE_FIELD( m_flForcedMaxTime, FIELD_FLOAT ),
  43. DEFINE_FIELD( m_bBusy, FIELD_BOOLEAN ),
  44. DEFINE_FIELD( m_bMovingToBusy, FIELD_BOOLEAN ),
  45. DEFINE_FIELD( m_bNeedsToPlayExitAnim, FIELD_BOOLEAN ),
  46. DEFINE_FIELD( m_flNextBusySearchTime, FIELD_TIME ),
  47. DEFINE_FIELD( m_flEndBusyAt, FIELD_TIME ),
  48. DEFINE_FIELD( m_flBusySearchRange, FIELD_FLOAT ),
  49. DEFINE_FIELD( m_bInQueue, FIELD_BOOLEAN ),
  50. DEFINE_FIELD( m_iCurrentBusyAnim, FIELD_INTEGER ),
  51. DEFINE_FIELD( m_hActBusyGoal, FIELD_EHANDLE ),
  52. DEFINE_FIELD( m_bNeedToSetBounds, FIELD_BOOLEAN ),
  53. DEFINE_FIELD( m_hSeeEntity, FIELD_EHANDLE ),
  54. DEFINE_FIELD( m_fTimeLastSawSeeEntity, FIELD_TIME ),
  55. DEFINE_FIELD( m_bExitedBusyToDueLostSeeEntity, FIELD_BOOLEAN ),
  56. DEFINE_FIELD( m_bExitedBusyToDueSeeEnemy, FIELD_BOOLEAN ),
  57. DEFINE_FIELD( m_iNumConsecutivePathFailures, FIELD_INTEGER ),
  58. DEFINE_FIELD( m_bAutoFireWeapon, FIELD_BOOLEAN ),
  59. DEFINE_FIELD( m_flDeferUntil, FIELD_TIME ),
  60. DEFINE_FIELD( m_iNumEnemiesInSafeZone, FIELD_INTEGER ),
  61. END_DATADESC();
  62. enum
  63. {
  64. ACTBUSY_SIGHT_METHOD_FULL = 0, // LOS and Viewcone
  65. ACTBUSY_SIGHT_METHOD_LOS_ONLY,
  66. };
  67. //=============================================================================
  68. //-----------------------------------------------------------------------------
  69. // Purpose: Gamesystem that parses the act busy anim file
  70. //-----------------------------------------------------------------------------
  71. class CActBusyAnimData : public CAutoGameSystem
  72. {
  73. public:
  74. CActBusyAnimData( void ) : CAutoGameSystem( "CActBusyAnimData" )
  75. {
  76. }
  77. // Inherited from IAutoServerSystem
  78. virtual void LevelInitPostEntity( void );
  79. virtual void LevelShutdownPostEntity( void );
  80. // Read in the data from the anim data file
  81. void ParseAnimDataFile( void );
  82. // Parse a keyvalues section into an act busy anim
  83. bool ParseActBusyFromKV( busyanim_t *pAnim, KeyValues *pSection );
  84. // Purpose: Returns the index of the busyanim data for the specified activity or sequence
  85. int FindBusyAnim( Activity iActivity, const char *pSequence );
  86. busyanim_t *GetBusyAnim( int iIndex ) { return &m_ActBusyAnims[iIndex]; }
  87. protected:
  88. CUtlVector<busyanim_t> m_ActBusyAnims;
  89. };
  90. CActBusyAnimData g_ActBusyAnimDataSystem;
  91. //-----------------------------------------------------------------------------
  92. // Inherited from IAutoServerSystem
  93. //-----------------------------------------------------------------------------
  94. void CActBusyAnimData::LevelInitPostEntity( void )
  95. {
  96. ParseAnimDataFile();
  97. }
  98. //-----------------------------------------------------------------------------
  99. // Purpose:
  100. //-----------------------------------------------------------------------------
  101. void CActBusyAnimData::LevelShutdownPostEntity( void )
  102. {
  103. m_ActBusyAnims.Purge();
  104. }
  105. //-----------------------------------------------------------------------------
  106. // Clear out the stats + their history
  107. //-----------------------------------------------------------------------------
  108. void CActBusyAnimData::ParseAnimDataFile( void )
  109. {
  110. KeyValues *pKVAnimData = new KeyValues( "ActBusyAnimDatafile" );
  111. if ( pKVAnimData->LoadFromFile( filesystem, "scripts/actbusy.txt" ) )
  112. {
  113. // Now try and parse out each act busy anim
  114. KeyValues *pKVAnim = pKVAnimData->GetFirstSubKey();
  115. while ( pKVAnim )
  116. {
  117. // Create a new anim and add it to our list
  118. int index = m_ActBusyAnims.AddToTail();
  119. busyanim_t *pAnim = &m_ActBusyAnims[index];
  120. if ( !ParseActBusyFromKV( pAnim, pKVAnim ) )
  121. {
  122. m_ActBusyAnims.Remove( index );
  123. }
  124. pKVAnim = pKVAnim->GetNextKey();
  125. }
  126. }
  127. pKVAnimData->deleteThis();
  128. }
  129. //-----------------------------------------------------------------------------
  130. // Purpose: Parse a keyvalues section into the prop
  131. //-----------------------------------------------------------------------------
  132. bool CActBusyAnimData::ParseActBusyFromKV( busyanim_t *pAnim, KeyValues *pSection )
  133. {
  134. pAnim->iszName = AllocPooledString( pSection->GetName() );
  135. // Activities
  136. pAnim->iActivities[BA_BUSY] = (Activity)CAI_BaseNPC::GetActivityID( pSection->GetString( "busy_anim", "ACT_INVALID" ) );
  137. pAnim->iActivities[BA_ENTRY] = (Activity)CAI_BaseNPC::GetActivityID( pSection->GetString( "entry_anim", "ACT_INVALID" ) );
  138. pAnim->iActivities[BA_EXIT] = (Activity)CAI_BaseNPC::GetActivityID( pSection->GetString( "exit_anim", "ACT_INVALID" ) );
  139. // Sequences
  140. pAnim->iszSequences[BA_BUSY] = AllocPooledString( pSection->GetString( "busy_sequence", NULL ) );
  141. pAnim->iszSequences[BA_ENTRY] = AllocPooledString( pSection->GetString( "entry_sequence", NULL ) );
  142. pAnim->iszSequences[BA_EXIT] = AllocPooledString( pSection->GetString( "exit_sequence", NULL ) );
  143. // Sounds
  144. pAnim->iszSounds[BA_BUSY] = AllocPooledString( pSection->GetString( "busy_sound", NULL ) );
  145. pAnim->iszSounds[BA_ENTRY] = AllocPooledString( pSection->GetString( "entry_sound", NULL ) );
  146. pAnim->iszSounds[BA_EXIT] = AllocPooledString( pSection->GetString( "exit_sound", NULL ) );
  147. // Times
  148. pAnim->flMinTime = pSection->GetFloat( "min_time", 10.0 );
  149. pAnim->flMaxTime = pSection->GetFloat( "max_time", 20.0 );
  150. pAnim->bUseAutomovement = pSection->GetInt( "use_automovement", 0 ) != 0;
  151. const char *sInterrupt = pSection->GetString( "interrupts", "BA_INT_DANGER" );
  152. if ( !strcmp( sInterrupt, "BA_INT_PLAYER" ) )
  153. {
  154. pAnim->iBusyInterruptType = BA_INT_PLAYER;
  155. }
  156. else if ( !strcmp( sInterrupt, "BA_INT_DANGER" ) )
  157. {
  158. pAnim->iBusyInterruptType = BA_INT_DANGER;
  159. }
  160. else if ( !strcmp( sInterrupt, "BA_INT_AMBUSH" ) )
  161. {
  162. pAnim->iBusyInterruptType = BA_INT_AMBUSH;
  163. }
  164. else if ( !strcmp( sInterrupt, "BA_INT_COMBAT" ) )
  165. {
  166. pAnim->iBusyInterruptType = BA_INT_COMBAT;
  167. }
  168. else if ( !strcmp( sInterrupt, "BA_INT_ZOMBIESLUMP" ))
  169. {
  170. pAnim->iBusyInterruptType = BA_INT_ZOMBIESLUMP;
  171. }
  172. else if ( !strcmp( sInterrupt, "BA_INT_SIEGE_DEFENSE" ))
  173. {
  174. pAnim->iBusyInterruptType = BA_INT_SIEGE_DEFENSE;
  175. }
  176. else
  177. {
  178. pAnim->iBusyInterruptType = BA_INT_NONE;
  179. }
  180. return true;
  181. }
  182. //-----------------------------------------------------------------------------
  183. // Purpose: Returns the busyanim data for the specified activity
  184. //-----------------------------------------------------------------------------
  185. int CActBusyAnimData::FindBusyAnim( Activity iActivity, const char *pSequence )
  186. {
  187. int iCount = m_ActBusyAnims.Count();
  188. for ( int i = 0; i < iCount; i++ )
  189. {
  190. busyanim_t *pBusyAnim = &m_ActBusyAnims[i];
  191. Assert( pBusyAnim );
  192. if ( pSequence && pBusyAnim->iszName != NULL_STRING && !Q_stricmp( STRING(pBusyAnim->iszName), pSequence ) )
  193. return i;
  194. if ( iActivity != ACT_INVALID && pBusyAnim->iActivities[BA_BUSY] == iActivity )
  195. return i;
  196. }
  197. if ( pSequence )
  198. {
  199. Warning("Specified '%s' as a busy anim name, and it's not in the act busy anim list.\n", pSequence );
  200. }
  201. else if ( iActivity != ACT_INVALID )
  202. {
  203. Warning("Tried to use Activity %d as a busy anim, and it's not in the act busy anim list.\n", iActivity );
  204. }
  205. return -1;
  206. }
  207. //=============================================================================================================
  208. //=============================================================================================================
  209. //-----------------------------------------------------------------------------
  210. // Purpose:
  211. //-----------------------------------------------------------------------------
  212. CAI_ActBusyBehavior::CAI_ActBusyBehavior()
  213. {
  214. // Disabled by default. Enabled by map entity.
  215. m_bEnabled = false;
  216. m_bUseRenderBoundsForCollision = false;
  217. m_flDeferUntil = 0;
  218. }
  219. //-----------------------------------------------------------------------------
  220. // Purpose:
  221. //-----------------------------------------------------------------------------
  222. void CAI_ActBusyBehavior::Enable( CAI_ActBusyGoal *pGoal, float flRange, bool bVisibleOnly )
  223. {
  224. NotifyBusyEnding();
  225. if ( pGoal )
  226. {
  227. m_hActBusyGoal = pGoal;
  228. }
  229. m_bEnabled = true;
  230. m_bBusy = false;
  231. m_bMovingToBusy = false;
  232. m_bNeedsToPlayExitAnim = false;
  233. m_bLeaving = false;
  234. m_flNextBusySearchTime = gpGlobals->curtime + ai_actbusy_search_time.GetFloat();
  235. m_flEndBusyAt = 0;
  236. m_bVisibleOnly = bVisibleOnly;
  237. m_bInQueue = dynamic_cast<CAI_ActBusyQueueGoal*>(m_hActBusyGoal.Get()) != NULL;
  238. m_ForcedActivity = ACT_INVALID;
  239. m_hSeeEntity = NULL;
  240. m_bExitedBusyToDueLostSeeEntity = false;
  241. m_bExitedBusyToDueSeeEnemy = false;
  242. m_iNumConsecutivePathFailures = 0;
  243. SetBusySearchRange( flRange );
  244. if ( ai_debug_actbusy.GetInt() == 4 )
  245. {
  246. Msg("ACTBUSY: behavior enabled on NPC %s (%s)\n", GetOuter()->GetClassname(), GetOuter()->GetDebugName() );
  247. }
  248. if( IsCombatActBusy() )
  249. {
  250. CollectSafeZoneVolumes( pGoal );
  251. }
  252. // Robin: Due to ai goal entities delaying their EnableGoal call on each
  253. // of their target Actors, NPCs that are spawned with active actbusies
  254. // will have their SelectSchedule() called before their behavior has been
  255. // enabled. To fix this, if we're enabled while in a schedule that can be
  256. // overridden, immediately act busy.
  257. if ( IsCurScheduleOverridable() )
  258. {
  259. // Force search time to be now.
  260. m_flNextBusySearchTime = gpGlobals->curtime;
  261. GetOuter()->ClearSchedule( "Enabling act busy" );
  262. }
  263. ClearCondition( COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE );
  264. }
  265. //-----------------------------------------------------------------------------
  266. //-----------------------------------------------------------------------------
  267. void CAI_ActBusyBehavior::OnRestore()
  268. {
  269. if ( m_bEnabled && m_hActBusyGoal != NULL && IsCombatActBusy() )
  270. {
  271. CollectSafeZoneVolumes( m_hActBusyGoal );
  272. }
  273. BaseClass::OnRestore();
  274. }
  275. //-----------------------------------------------------------------------------
  276. // Purpose:
  277. //-----------------------------------------------------------------------------
  278. void CAI_ActBusyBehavior::SetBusySearchRange( float flRange )
  279. {
  280. m_flBusySearchRange = flRange;
  281. }
  282. //-----------------------------------------------------------------------------
  283. // Purpose:
  284. //-----------------------------------------------------------------------------
  285. void CAI_ActBusyBehavior::Disable( void )
  286. {
  287. if ( ai_debug_actbusy.GetInt() == 4 )
  288. {
  289. Msg("ACTBUSY: behavior disabled on NPC %s (%s)\n", GetOuter()->GetClassname(), GetOuter()->GetDebugName() );
  290. }
  291. if ( m_bEnabled )
  292. {
  293. SetCondition( COND_PROVOKED );
  294. }
  295. StopBusying();
  296. m_bEnabled = false;
  297. }
  298. //-----------------------------------------------------------------------------
  299. // Purpose:
  300. //-----------------------------------------------------------------------------
  301. void CAI_ActBusyBehavior::ForceActBusy( CAI_ActBusyGoal *pGoal, CAI_Hint *pHintNode, float flMaxTime, bool bVisibleOnly, bool bTeleportToBusy, bool bUseNearestBusy, CBaseEntity *pSeeEntity, Activity activity )
  302. {
  303. Assert( !m_bLeaving );
  304. if ( m_bNeedsToPlayExitAnim )
  305. {
  306. // If we hit this, the mapmaker's told this NPC to actbusy somewhere while it's still in an actbusy.
  307. // Right now, we don't support this. We could support it with a bit of work.
  308. if ( HasAnimForActBusy( m_iCurrentBusyAnim, BA_EXIT ) )
  309. {
  310. Warning("ACTBUSY: %s(%s) was told to actbusy while inside an actbusy that needs to exit first. IGNORING.\n", GetOuter()->GetDebugName(), GetOuter()->GetClassname() );
  311. return;
  312. }
  313. }
  314. if ( ai_debug_actbusy.GetInt() == 4 )
  315. {
  316. Msg("ACTBUSY: ForceActBusy on NPC %s (%s): ", GetOuter()->GetClassname(), GetOuter()->GetDebugName() );
  317. if ( pHintNode )
  318. {
  319. Msg("Hintnode %s", pHintNode->GetDebugName());
  320. }
  321. else
  322. {
  323. Msg("No Hintnode specified");
  324. }
  325. Msg("\n");
  326. }
  327. Enable( pGoal, m_flBusySearchRange, bVisibleOnly );
  328. m_bForceActBusy = true;
  329. m_flForcedMaxTime = flMaxTime;
  330. m_bTeleportToBusy = bTeleportToBusy;
  331. m_bUseNearestBusy = bUseNearestBusy;
  332. m_ForcedActivity = activity;
  333. m_hSeeEntity = pSeeEntity;
  334. if ( pHintNode )
  335. {
  336. if ( GetHintNode() && GetHintNode() != pHintNode )
  337. {
  338. GetHintNode()->Unlock();
  339. }
  340. if ( pHintNode->Lock( GetOuter() ) )
  341. {
  342. SetHintNode( pHintNode );
  343. }
  344. }
  345. SetCondition( COND_PROVOKED );
  346. }
  347. //-----------------------------------------------------------------------------
  348. // Purpose: Force the NPC to find an exit node and leave
  349. //-----------------------------------------------------------------------------
  350. void CAI_ActBusyBehavior::ForceActBusyLeave( bool bVisibleOnly )
  351. {
  352. if ( ai_debug_actbusy.GetInt() == 4 )
  353. {
  354. Msg("ACTBUSY: ForceActBusyLeave on NPC %s (%s)\n", GetOuter()->GetClassname(), GetOuter()->GetDebugName() );
  355. }
  356. Enable( NULL, m_flBusySearchRange, bVisibleOnly );
  357. m_bForceActBusy = true;
  358. m_bLeaving = true;
  359. m_ForcedActivity = ACT_INVALID;
  360. m_hSeeEntity = NULL;
  361. SetCondition( COND_PROVOKED );
  362. }
  363. //-----------------------------------------------------------------------------
  364. // Purpose: Break the NPC out of the current busy state, but don't disable busying
  365. //-----------------------------------------------------------------------------
  366. void CAI_ActBusyBehavior::StopBusying( void )
  367. {
  368. if ( !GetOuter() )
  369. return;
  370. // Make sure we turn this off unconditionally!
  371. m_bAutoFireWeapon = false;
  372. if ( ai_debug_actbusy.GetInt() == 4 )
  373. {
  374. Msg("ACTBUSY: StopBusying on NPC %s (%s)\n", GetOuter()->GetClassname(), GetOuter()->GetDebugName() );
  375. }
  376. if ( m_bBusy || m_bMovingToBusy )
  377. {
  378. SetCondition( COND_PROVOKED );
  379. }
  380. m_flEndBusyAt = gpGlobals->curtime;
  381. m_bForceActBusy = false;
  382. m_bTeleportToBusy = false;
  383. m_bUseNearestBusy = false;
  384. m_bLeaving = false;
  385. m_bMovingToBusy = false;
  386. m_ForcedActivity = ACT_INVALID;
  387. m_hSeeEntity = NULL;
  388. }
  389. //-----------------------------------------------------------------------------
  390. //-----------------------------------------------------------------------------
  391. bool CAI_ActBusyBehavior::IsStopBusying()
  392. {
  393. return IsCurSchedule(SCHED_ACTBUSY_STOP_BUSYING);
  394. }
  395. //-----------------------------------------------------------------------------
  396. // Purpose: Find a general purpose, suitable Hint Node for me to Act Busy.
  397. //-----------------------------------------------------------------------------
  398. CAI_Hint *CAI_ActBusyBehavior::FindActBusyHintNode()
  399. {
  400. Assert( !IsCombatActBusy() );
  401. int iBits = bits_HINT_NODE_USE_GROUP;
  402. if ( m_bVisibleOnly )
  403. {
  404. iBits |= bits_HINT_NODE_VISIBLE;
  405. }
  406. if ( ai_debug_actbusy.GetInt() == 3 && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  407. {
  408. iBits |= bits_HINT_NODE_REPORT_FAILURES;
  409. }
  410. if ( m_bUseNearestBusy )
  411. {
  412. iBits |= bits_HINT_NODE_NEAREST;
  413. }
  414. else
  415. {
  416. iBits |= bits_HINT_NODE_RANDOM;
  417. }
  418. CAI_Hint *pNode = CAI_HintManager::FindHint( GetOuter(), HINT_WORLD_WORK_POSITION, iBits, m_flBusySearchRange );
  419. return pNode;
  420. }
  421. //-----------------------------------------------------------------------------
  422. // Purpose: Find a node for me to combat act busy.
  423. //
  424. // Right now, all of this work assumes the actbusier is a player ally and
  425. // wants to fight near the player a'la Alyx in ep2_outland_10.
  426. //-----------------------------------------------------------------------------
  427. CAI_Hint *CAI_ActBusyBehavior::FindCombatActBusyHintNode()
  428. {
  429. Assert( IsCombatActBusy() );
  430. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  431. if( !pPlayer )
  432. return NULL;
  433. CHintCriteria criteria;
  434. // Ok, find a hint node THAT:
  435. // -Is in my hint group
  436. // -Is Visible (if specified by designer)
  437. // -Is Closest to me (if specified by designer)
  438. // -The player can see
  439. // -Is within the accepted max dist from player
  440. int iBits = bits_HINT_NODE_USE_GROUP;
  441. if ( m_bVisibleOnly )
  442. iBits |= bits_HINT_NODE_VISIBLE;
  443. if ( ai_debug_actbusy.GetInt() == 3 && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  444. iBits |= bits_HINT_NODE_REPORT_FAILURES;
  445. if ( m_bUseNearestBusy )
  446. iBits |= bits_HINT_NODE_NEAREST;
  447. else
  448. iBits |= bits_HINT_NODE_RANDOM;
  449. iBits |= bits_HAS_EYEPOSITION_LOS_TO_PLAYER;
  450. criteria.AddHintType( HINT_WORLD_WORK_POSITION );
  451. criteria.SetFlag( iBits );
  452. criteria.AddIncludePosition( pPlayer->GetAbsOrigin(), ACTBUSY_COMBAT_PLAYER_MAX_DIST );
  453. CAI_Hint *pNode = CAI_HintManager::FindHint( GetOuter(), criteria );
  454. return pNode;
  455. }
  456. //-----------------------------------------------------------------------------
  457. // Purpose: Find a suitable combat actbusy node to teleport to. That is, find
  458. // one that the player is not going to see me appear at.
  459. //-----------------------------------------------------------------------------
  460. CAI_Hint *CAI_ActBusyBehavior::FindCombatActBusyTeleportHintNode()
  461. {
  462. Assert( IsCombatActBusy() );
  463. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  464. if( !pPlayer )
  465. return NULL;
  466. CHintCriteria criteria;
  467. // Ok, find a hint node THAT:
  468. // -Is in my hint group
  469. // -The player CAN NOT see so that they don't see me teleport
  470. int iBits = bits_HINT_NODE_USE_GROUP;
  471. if ( ai_debug_actbusy.GetInt() == 3 && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  472. iBits |= bits_HINT_NODE_REPORT_FAILURES;
  473. iBits |= bits_HINT_NODE_RANDOM;
  474. iBits |= bits_HINT_NODE_NOT_VISIBLE_TO_PLAYER;
  475. criteria.AddHintType( HINT_WORLD_WORK_POSITION );
  476. criteria.SetFlag( iBits );
  477. criteria.AddIncludePosition( pPlayer->GetAbsOrigin(), ACTBUSY_COMBAT_PLAYER_MAX_DIST * 1.1f );
  478. CAI_Hint *pNode = CAI_HintManager::FindHint( GetOuter(), criteria );
  479. return pNode;
  480. }
  481. //-----------------------------------------------------------------------------
  482. // Purpose:
  483. //-----------------------------------------------------------------------------
  484. bool CAI_ActBusyBehavior::FValidateHintType( CAI_Hint *pHint )
  485. {
  486. if ( pHint->HintType() != HINT_WORLD_WORK_POSITION && pHint->HintType() != HINT_NPC_EXIT_POINT )
  487. return false;
  488. // If the node doesn't want to be teleported to, we need to check for clear
  489. const char *pSequenceOrActivity = STRING(pHint->HintActivityName());
  490. const char *cSpace = strchr( pSequenceOrActivity, ' ' );
  491. if ( cSpace )
  492. {
  493. if ( !Q_strncmp( cSpace+1, "teleport", 8 ) )
  494. {
  495. // Node is a teleport node, so it's good
  496. return true;
  497. }
  498. }
  499. // Check for clearance
  500. trace_t tr;
  501. AI_TraceHull( pHint->GetAbsOrigin(), pHint->GetAbsOrigin(), GetOuter()->WorldAlignMins(), GetOuter()->WorldAlignMaxs(), MASK_SOLID, GetOuter(), COLLISION_GROUP_NONE, &tr );
  502. if ( tr.fraction == 1.0 )
  503. return true;
  504. // Report failures
  505. if ( ai_debug_actbusy.GetInt() == 3 && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  506. {
  507. NDebugOverlay::Text( pHint->GetAbsOrigin(), "Node isn't clear.", false, 60 );
  508. NDebugOverlay::Box( pHint->GetAbsOrigin(), GetOuter()->WorldAlignMins(), GetOuter()->WorldAlignMaxs(), 255,0,0, 8, 2.0 );
  509. }
  510. return false;
  511. }
  512. //-----------------------------------------------------------------------------
  513. // Purpose:
  514. //-----------------------------------------------------------------------------
  515. bool CAI_ActBusyBehavior::CanSelectSchedule( void )
  516. {
  517. // Always active when we're busy
  518. if ( m_bBusy || m_bForceActBusy || m_bNeedsToPlayExitAnim )
  519. return true;
  520. if ( !m_bEnabled )
  521. return false;
  522. if ( m_flDeferUntil > gpGlobals->curtime )
  523. return false;
  524. if ( CountEnemiesInSafeZone() > 0 )
  525. {
  526. // I have enemies left in the safe zone. Actbusy isn't appropriate.
  527. // I should be off fighting them.
  528. return false;
  529. }
  530. if ( !IsCurScheduleOverridable() )
  531. return false;
  532. // Don't select actbusy if we're not going to search for a node anyway
  533. return (m_flNextBusySearchTime < gpGlobals->curtime);
  534. }
  535. //-----------------------------------------------------------------------------
  536. // Purpose: Return true if the current schedule is one that ActBusy is allowed to override
  537. //-----------------------------------------------------------------------------
  538. bool CAI_ActBusyBehavior::IsCurScheduleOverridable( void )
  539. {
  540. if( IsCombatActBusy() )
  541. {
  542. // The whole point of a combat actbusy is that it can run in any state (including combat)
  543. // the only exception is SCRIPT (sjb)
  544. return (GetOuter()->GetState() != NPC_STATE_SCRIPT);
  545. }
  546. // Act busies are not valid inside of a vehicle
  547. if ( GetOuter()->IsInAVehicle() )
  548. return false;
  549. // Only if we're about to idle (or SCHED_NONE to catch newly spawned guys)
  550. return ( IsCurSchedule( SCHED_IDLE_STAND ) || IsCurSchedule( SCHED_NONE ) );
  551. }
  552. //-----------------------------------------------------------------------------
  553. // Purpose:
  554. // Input : *pSound -
  555. // Output : Returns true on success, false on failure.
  556. //-----------------------------------------------------------------------------
  557. bool CAI_ActBusyBehavior::ShouldIgnoreSound( CSound *pSound )
  558. {
  559. // If we're busy in an actbusy anim that's an ambush, stay mum as long as we can
  560. if ( m_bBusy )
  561. {
  562. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( m_iCurrentBusyAnim );
  563. if( pBusyAnim && pBusyAnim->iBusyInterruptType == BA_INT_ZOMBIESLUMP )
  564. {
  565. // Slumped zombies are deaf.
  566. return true;
  567. }
  568. if ( pBusyAnim && ( ( pBusyAnim->iBusyInterruptType == BA_INT_AMBUSH ) || ( pBusyAnim->iBusyInterruptType == BA_INT_COMBAT ) ) )
  569. {
  570. /*
  571. // Robin: First version ignored sounds in front of the NPC.
  572. Vector vecToSound = (pSound->GetSoundReactOrigin() - GetAbsOrigin());
  573. vecToSound.z = 0;
  574. VectorNormalize( vecToSound );
  575. Vector facingDir = GetOuter()->EyeDirection2D();
  576. if ( DotProduct( vecToSound, facingDir ) > 0 )
  577. return true;
  578. */
  579. // Ignore sounds that aren't visible
  580. if ( !GetOuter()->FVisible( pSound->GetSoundReactOrigin() ) )
  581. return true;
  582. }
  583. }
  584. return BaseClass::ShouldIgnoreSound( pSound );
  585. }
  586. //-----------------------------------------------------------------------------
  587. // Purpose:
  588. //-----------------------------------------------------------------------------
  589. void CAI_ActBusyBehavior::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity *pAttacker )
  590. {
  591. if( IsCombatActBusy() && pSquadmate->IsPlayer() && IsInSafeZone( pAttacker ) )
  592. {
  593. SetCondition( COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE ); // Break the actbusy, if we're running it.
  594. m_flDeferUntil = gpGlobals->curtime + 4.0f; // Stop actbusying and go deal with that enemy!!
  595. }
  596. BaseClass::OnFriendDamaged( pSquadmate, pAttacker );
  597. }
  598. //-----------------------------------------------------------------------------
  599. // Purpose: Count the number of enemies of mine that are inside my safe zone
  600. // volume.
  601. //
  602. // NOTE: We keep this count to prevent the NPC re-entering combat
  603. // actbusy whilst too many enemies are present in the safe zone.
  604. // This count does not automatically alert the NPC that there are
  605. // enemies in the safe zone.
  606. // You must set COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE to let
  607. // the NPC know.
  608. //-----------------------------------------------------------------------------
  609. int CAI_ActBusyBehavior::CountEnemiesInSafeZone()
  610. {
  611. if( !IsCombatActBusy() )
  612. {
  613. return 0;
  614. }
  615. // Grovel the AI list and count the enemies in the zone. By enemies, I mean
  616. // anyone that I would fight if I saw.
  617. CAI_BaseNPC ** ppAIs = g_AI_Manager.AccessAIs();
  618. int nAIs = g_AI_Manager.NumAIs();
  619. int count = 0;
  620. for ( int i = 0; i < nAIs; i++ )
  621. {
  622. if( GetOuter()->IRelationType(ppAIs[i]) < D_LI )
  623. {
  624. if( IsInSafeZone(ppAIs[i]) )
  625. {
  626. count++;
  627. }
  628. }
  629. }
  630. return count;
  631. }
  632. //-----------------------------------------------------------------------------
  633. // Purpose:
  634. //-----------------------------------------------------------------------------
  635. int CAI_ActBusyBehavior::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  636. {
  637. if( IsCombatActBusy() && info.GetAttacker() && IsInSafeZone( info.GetAttacker() ) )
  638. {
  639. SetCondition( COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE ); // Break the actbusy, if we're running it.
  640. m_flDeferUntil = gpGlobals->curtime + 4.0f; // Stop actbusying and go deal with that enemy!!
  641. }
  642. return BaseClass::OnTakeDamage_Alive( info );
  643. }
  644. //-----------------------------------------------------------------------------
  645. // Purpose:
  646. //-----------------------------------------------------------------------------
  647. void CAI_ActBusyBehavior::GatherConditions( void )
  648. {
  649. // Clear this condition before we call up, since the look sensing code will run and
  650. // set this condition if it is relevant.
  651. if( !IsCurSchedule(SCHED_ACTBUSY_BUSY, false) )
  652. {
  653. // Only clear this condition when we aren't busying. We want it to be sticky
  654. // during that time so that schedule selection works properly (sjb)
  655. ClearCondition( COND_ACTBUSY_ENEMY_TOO_CLOSE );
  656. }
  657. BaseClass::GatherConditions();
  658. bool bCheckLOS = true;
  659. bool bCheckFOV = true;
  660. if( m_hActBusyGoal && m_hActBusyGoal->m_iSightMethod == ACTBUSY_SIGHT_METHOD_LOS_ONLY )
  661. {
  662. bCheckFOV = false;
  663. }
  664. // If we have a see entity, make sure we can still see it
  665. if ( m_hSeeEntity && m_bBusy )
  666. {
  667. if ( (!bCheckFOV||GetOuter()->FInViewCone(m_hSeeEntity)) && GetOuter()->QuerySeeEntity(m_hSeeEntity) && (!bCheckLOS||GetOuter()->FVisible(m_hSeeEntity)) )
  668. {
  669. m_fTimeLastSawSeeEntity = gpGlobals->curtime;
  670. ClearCondition( COND_ACTBUSY_LOST_SEE_ENTITY );
  671. }
  672. else if( m_hActBusyGoal )
  673. {
  674. float fDelta = gpGlobals->curtime - m_fTimeLastSawSeeEntity;
  675. if( fDelta >= m_hActBusyGoal->m_flSeeEntityTimeout )
  676. {
  677. SetCondition( COND_ACTBUSY_LOST_SEE_ENTITY );
  678. m_hActBusyGoal->NPCLostSeeEntity( GetOuter() );
  679. if( IsCombatActBusy() && (GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL && m_hSeeEntity->IsPlayer()) )
  680. {
  681. // Defer any actbusying for several seconds. This serves as a heuristic for waiting
  682. // for the player to settle after moving out of the room. This helps Alyx pick a more
  683. // pertinent Actbusy near the player's new location.
  684. m_flDeferUntil = gpGlobals->curtime + 4.0f;
  685. }
  686. }
  687. }
  688. }
  689. else
  690. {
  691. ClearCondition( COND_ACTBUSY_LOST_SEE_ENTITY );
  692. }
  693. // If we're busy, ignore sounds depending on our actbusy break rules
  694. if ( m_bBusy )
  695. {
  696. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( m_iCurrentBusyAnim );
  697. if ( pBusyAnim )
  698. {
  699. switch( pBusyAnim->iBusyInterruptType )
  700. {
  701. case BA_INT_DANGER:
  702. break;
  703. case BA_INT_AMBUSH:
  704. break;
  705. case BA_INT_ZOMBIESLUMP:
  706. {
  707. ClearCondition( COND_HEAR_PLAYER );
  708. ClearCondition( COND_SEE_ENEMY );
  709. ClearCondition( COND_NEW_ENEMY );
  710. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  711. if( pPlayer )
  712. {
  713. float flDist = pPlayer->GetAbsOrigin().DistTo( GetAbsOrigin() );
  714. if( flDist <= 60 )
  715. {
  716. StopBusying();
  717. }
  718. }
  719. }
  720. break;
  721. case BA_INT_COMBAT:
  722. // Ignore the player unless he shoots at us
  723. ClearCondition( COND_HEAR_PLAYER );
  724. ClearCondition( COND_SEE_ENEMY );
  725. ClearCondition( COND_NEW_ENEMY );
  726. break;
  727. case BA_INT_PLAYER:
  728. // Clear all but player.
  729. ClearCondition( COND_HEAR_DANGER );
  730. ClearCondition( COND_HEAR_COMBAT );
  731. ClearCondition( COND_HEAR_WORLD );
  732. ClearCondition( COND_HEAR_BULLET_IMPACT );
  733. break;
  734. case BA_INT_SIEGE_DEFENSE:
  735. ClearCondition( COND_HEAR_PLAYER );
  736. ClearCondition( COND_SEE_ENEMY );
  737. ClearCondition( COND_NEW_ENEMY );
  738. ClearCondition( COND_HEAR_COMBAT );
  739. ClearCondition( COND_HEAR_WORLD );
  740. ClearCondition( COND_HEAR_BULLET_IMPACT );
  741. break;
  742. case BA_INT_NONE:
  743. // Clear all
  744. ClearCondition( COND_HEAR_DANGER );
  745. ClearCondition( COND_HEAR_COMBAT );
  746. ClearCondition( COND_HEAR_WORLD );
  747. ClearCondition( COND_HEAR_BULLET_IMPACT );
  748. ClearCondition( COND_HEAR_PLAYER );
  749. break;
  750. default:
  751. break;
  752. }
  753. }
  754. }
  755. if( m_bAutoFireWeapon && random->RandomInt(0, 5) <= 3 )
  756. {
  757. CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon();
  758. if( pWeapon )
  759. {
  760. pWeapon->Operator_ForceNPCFire( GetOuter(), false );
  761. }
  762. }
  763. if( ai_debug_actbusy.GetInt() == 5 )
  764. {
  765. // Visualize them there Actbusy safe volumes
  766. for( int i = 0 ; i < m_SafeZones.Count() ; i++ )
  767. {
  768. busysafezone_t *pSafeZone = &m_SafeZones[i];
  769. Vector vecBoxOrigin = (pSafeZone->vecMins + pSafeZone->vecMaxs) * 0.5f;
  770. Vector vecBoxMins = vecBoxOrigin - pSafeZone->vecMins;
  771. Vector vecBoxMaxs = vecBoxOrigin - pSafeZone->vecMaxs;
  772. NDebugOverlay::Box( vecBoxOrigin, vecBoxMins, vecBoxMaxs, 255, 0, 255, 64, 0.2f );
  773. }
  774. }
  775. }
  776. //-----------------------------------------------------------------------------
  777. // Purpose:
  778. //-----------------------------------------------------------------------------
  779. void CAI_ActBusyBehavior::EndScheduleSelection( void )
  780. {
  781. NotifyBusyEnding();
  782. CheckAndCleanupOnExit();
  783. }
  784. //-----------------------------------------------------------------------------
  785. // Purpose:
  786. // Input : nActivity -
  787. //-----------------------------------------------------------------------------
  788. Activity CAI_ActBusyBehavior::NPC_TranslateActivity( Activity nActivity )
  789. {
  790. // Find out what the base class wants to do with the activity
  791. Activity nNewActivity = BaseClass::NPC_TranslateActivity( nActivity );
  792. if( nActivity == ACT_RUN )
  793. {
  794. // FIXME: Forcing STIMULATED here is illegal if the entity doesn't support it as an activity
  795. CAI_PlayerAlly *pAlly = dynamic_cast<CAI_PlayerAlly*>(GetOuter());
  796. if ( pAlly )
  797. return ACT_RUN_STIMULATED;
  798. }
  799. // Else stay with the base class' decision.
  800. return nNewActivity;
  801. }
  802. //-----------------------------------------------------------------------------
  803. //-----------------------------------------------------------------------------
  804. void CAI_ActBusyBehavior::HandleAnimEvent( animevent_t *pEvent )
  805. {
  806. if( pEvent->event == AE_ACTBUSY_WEAPON_FIRE_ON )
  807. {
  808. m_bAutoFireWeapon = true;
  809. return;
  810. }
  811. else if( pEvent->event == AE_ACTBUSY_WEAPON_FIRE_OFF )
  812. {
  813. m_bAutoFireWeapon = false;
  814. return;
  815. }
  816. return BaseClass::HandleAnimEvent( pEvent );
  817. }
  818. //-----------------------------------------------------------------------------
  819. // Purpose: Actbusy's ending, ensure we haven't left NPC in broken state.
  820. //-----------------------------------------------------------------------------
  821. void CAI_ActBusyBehavior::CheckAndCleanupOnExit( void )
  822. {
  823. if ( m_bNeedsToPlayExitAnim && !GetOuter()->IsMarkedForDeletion() && GetOuter()->IsAlive() )
  824. {
  825. Warning("NPC %s(%s) left actbusy without playing exit anim.\n", GetOuter()->GetDebugName(), GetOuter()->GetClassname() );
  826. m_bNeedsToPlayExitAnim = false;
  827. }
  828. GetOuter()->RemoveFlag( FL_FLY );
  829. // If we're supposed to use render bounds while inside the busy anim, restore normal now
  830. if ( m_bUseRenderBoundsForCollision )
  831. {
  832. GetOuter()->SetHullSizeNormal( true );
  833. }
  834. }
  835. //-----------------------------------------------------------------------------
  836. // Purpose:
  837. //-----------------------------------------------------------------------------
  838. void CAI_ActBusyBehavior::BuildScheduleTestBits( void )
  839. {
  840. BaseClass::BuildScheduleTestBits();
  841. // When going to an actbusy, we can't be interrupted during the entry anim
  842. if ( IsCurSchedule(SCHED_ACTBUSY_START_BUSYING) )
  843. {
  844. if ( GetOuter()->GetTask()->iTask == TASK_ACTBUSY_PLAY_ENTRY )
  845. return;
  846. GetOuter()->SetCustomInterruptCondition( COND_PROVOKED );
  847. if( IsCombatActBusy() )
  848. {
  849. GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal(COND_ACTBUSY_ENEMY_TOO_CLOSE) );
  850. }
  851. }
  852. // If we're in a queue, or leaving, we have no extra conditions
  853. if ( m_bInQueue || IsCurSchedule( SCHED_ACTBUSY_LEAVE ) )
  854. return;
  855. // If we're not busy, or we're exiting a busy, we have no extra conditions
  856. if ( !m_bBusy || IsCurSchedule( SCHED_ACTBUSY_STOP_BUSYING ) )
  857. return;
  858. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( m_iCurrentBusyAnim );
  859. if ( pBusyAnim )
  860. {
  861. switch( pBusyAnim->iBusyInterruptType )
  862. {
  863. case BA_INT_ZOMBIESLUMP:
  864. {
  865. GetOuter()->SetCustomInterruptCondition( COND_LIGHT_DAMAGE );
  866. GetOuter()->SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
  867. }
  868. break;
  869. case BA_INT_SIEGE_DEFENSE:
  870. {
  871. GetOuter()->SetCustomInterruptCondition( COND_HEAR_DANGER );
  872. GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal(COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE) );
  873. GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal(COND_ACTBUSY_ENEMY_TOO_CLOSE) );
  874. }
  875. break;
  876. case BA_INT_AMBUSH:
  877. case BA_INT_DANGER:
  878. {
  879. GetOuter()->SetCustomInterruptCondition( COND_LIGHT_DAMAGE );
  880. GetOuter()->SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
  881. GetOuter()->SetCustomInterruptCondition( COND_HEAR_DANGER );
  882. GetOuter()->SetCustomInterruptCondition( COND_HEAR_COMBAT );
  883. GetOuter()->SetCustomInterruptCondition( COND_HEAR_BULLET_IMPACT );
  884. GetOuter()->SetCustomInterruptCondition( COND_NEW_ENEMY );
  885. GetOuter()->SetCustomInterruptCondition( COND_SEE_ENEMY );
  886. GetOuter()->SetCustomInterruptCondition( COND_PLAYER_ADDED_TO_SQUAD );
  887. GetOuter()->SetCustomInterruptCondition( COND_RECEIVED_ORDERS );
  888. break;
  889. }
  890. case BA_INT_PLAYER:
  891. {
  892. GetOuter()->SetCustomInterruptCondition( COND_LIGHT_DAMAGE );
  893. GetOuter()->SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
  894. GetOuter()->SetCustomInterruptCondition( COND_HEAR_DANGER );
  895. GetOuter()->SetCustomInterruptCondition( COND_HEAR_COMBAT );
  896. GetOuter()->SetCustomInterruptCondition( COND_HEAR_BULLET_IMPACT );
  897. GetOuter()->SetCustomInterruptCondition( COND_NEW_ENEMY );
  898. GetOuter()->SetCustomInterruptCondition( COND_PLAYER_ADDED_TO_SQUAD );
  899. GetOuter()->SetCustomInterruptCondition( COND_RECEIVED_ORDERS );
  900. // The player can interrupt us
  901. GetOuter()->SetCustomInterruptCondition( COND_SEE_PLAYER );
  902. break;
  903. }
  904. case BA_INT_COMBAT:
  905. {
  906. GetOuter()->SetCustomInterruptCondition( COND_LIGHT_DAMAGE );
  907. GetOuter()->SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
  908. GetOuter()->SetCustomInterruptCondition( COND_HEAR_DANGER );
  909. break;
  910. }
  911. case BA_INT_NONE:
  912. break;
  913. default:
  914. break;
  915. }
  916. }
  917. }
  918. //-----------------------------------------------------------------------------
  919. // Purpose:
  920. //-----------------------------------------------------------------------------
  921. int CAI_ActBusyBehavior::SelectScheduleForLeaving( void )
  922. {
  923. // Are we already near an exit node?
  924. if ( GetHintNode() )
  925. {
  926. if ( GetHintNode()->HintType() == HINT_NPC_EXIT_POINT )
  927. {
  928. // Are we near it? If so, we're done. If not, move to it.
  929. if ( UTIL_DistApprox( GetHintNode()->GetAbsOrigin(), GetAbsOrigin() ) < 64 )
  930. {
  931. if ( !GetOuter()->IsMarkedForDeletion() )
  932. {
  933. CBaseEntity *pOwner = GetOuter()->GetOwnerEntity();
  934. if ( pOwner )
  935. {
  936. pOwner->DeathNotice( GetOuter() );
  937. GetOuter()->SetOwnerEntity( NULL );
  938. }
  939. GetOuter()->SetThink( &CBaseEntity::SUB_Remove); //SUB_Remove) ; //GetOuter()->SUB_Remove );
  940. GetOuter()->SetNextThink( gpGlobals->curtime + 0.1 );
  941. if ( m_hActBusyGoal )
  942. {
  943. m_hActBusyGoal->NPCLeft( GetOuter() );
  944. }
  945. }
  946. return SCHED_IDLE_STAND;
  947. }
  948. return SCHED_ACTBUSY_LEAVE;
  949. }
  950. else
  951. {
  952. // Clear the node, it's no use to us
  953. GetHintNode()->NPCStoppedUsing( GetOuter() );
  954. GetHintNode()->Unlock();
  955. SetHintNode( NULL );
  956. }
  957. }
  958. // Find an exit node
  959. CHintCriteria hintCriteria;
  960. hintCriteria.SetHintType( HINT_NPC_EXIT_POINT );
  961. hintCriteria.SetFlag( bits_HINT_NODE_RANDOM | bits_HINT_NODE_CLEAR | bits_HINT_NODE_USE_GROUP );
  962. CAI_Hint *pNode = CAI_HintManager::FindHintRandom( GetOuter(), GetOuter()->GetAbsOrigin(), hintCriteria );
  963. if ( pNode )
  964. {
  965. SetHintNode( pNode );
  966. return SCHED_ACTBUSY_LEAVE;
  967. }
  968. // We've been told to leave, but we can't find an exit node. What to do?
  969. return SCHED_IDLE_STAND;
  970. }
  971. //-----------------------------------------------------------------------------
  972. // Purpose:
  973. //-----------------------------------------------------------------------------
  974. int CAI_ActBusyBehavior::SelectScheduleWhileNotBusy( int iBase )
  975. {
  976. // Randomly act busy (unless we're being forced, in which case we should search immediately)
  977. if ( m_bForceActBusy || m_flNextBusySearchTime < gpGlobals->curtime )
  978. {
  979. // If we're being forced, think again quickly
  980. if ( m_bForceActBusy || IsCombatActBusy() )
  981. {
  982. m_flNextBusySearchTime = gpGlobals->curtime + 2.0;
  983. }
  984. else
  985. {
  986. m_flNextBusySearchTime = gpGlobals->curtime + RandomFloat(ai_actbusy_search_time.GetFloat(), ai_actbusy_search_time.GetFloat()*2);
  987. }
  988. // We may already have a node
  989. bool bForceTeleport = false;
  990. CAI_Hint *pNode = GetHintNode();
  991. if ( !pNode )
  992. {
  993. if( IsCombatActBusy() )
  994. {
  995. if ( m_hActBusyGoal->IsCombatActBusyTeleportAllowed() && m_iNumConsecutivePathFailures >= 2 && !AI_GetSinglePlayer()->FInViewCone(GetOuter()) )
  996. {
  997. // Looks like I've tried several times to find a path to a valid hint node and
  998. // haven't been able to. This means I'm on a patch of node graph that simply
  999. // does not connect to any hint nodes that match my criteria. So try to find
  1000. // a node that's safe to teleport to. (sjb) ep2_outland_10 (Alyx)
  1001. // (Also, I must not be in the player's viewcone)
  1002. pNode = FindCombatActBusyTeleportHintNode();
  1003. bForceTeleport = true;
  1004. }
  1005. else
  1006. {
  1007. pNode = FindCombatActBusyHintNode();
  1008. }
  1009. }
  1010. else
  1011. {
  1012. pNode = FindActBusyHintNode();
  1013. }
  1014. }
  1015. if ( pNode )
  1016. {
  1017. // Ensure we've got a sequence for the node
  1018. const char *pSequenceOrActivity = STRING(pNode->HintActivityName());
  1019. Activity iNodeActivity;
  1020. int iBusyAnim;
  1021. // See if the node specifies that we should teleport to it
  1022. const char *cSpace = strchr( pSequenceOrActivity, ' ' );
  1023. if ( cSpace )
  1024. {
  1025. if ( !Q_strncmp( cSpace+1, "teleport", 8 ) )
  1026. {
  1027. m_bTeleportToBusy = true;
  1028. }
  1029. char sActOrSeqName[512];
  1030. Q_strncpy( sActOrSeqName, pSequenceOrActivity, (cSpace-pSequenceOrActivity)+1 );
  1031. iNodeActivity = (Activity)CAI_BaseNPC::GetActivityID( sActOrSeqName );
  1032. iBusyAnim = g_ActBusyAnimDataSystem.FindBusyAnim( iNodeActivity, sActOrSeqName );
  1033. }
  1034. else
  1035. {
  1036. iNodeActivity = (Activity)CAI_BaseNPC::GetActivityID( pSequenceOrActivity );
  1037. iBusyAnim = g_ActBusyAnimDataSystem.FindBusyAnim( iNodeActivity, pSequenceOrActivity );
  1038. }
  1039. // Does this NPC have the activity or sequence for this node?
  1040. if ( HasAnimForActBusy( iBusyAnim, BA_BUSY ) )
  1041. {
  1042. if ( HasCondition(COND_ACTBUSY_LOST_SEE_ENTITY) )
  1043. {
  1044. // We've lost our see entity, which means we can't continue.
  1045. if ( m_bForceActBusy )
  1046. {
  1047. // We were being told to act busy, which we can't do now that we've lost the see entity.
  1048. // Abort, and assume that the mapmaker will make us retry.
  1049. StopBusying();
  1050. }
  1051. return iBase;
  1052. }
  1053. m_iCurrentBusyAnim = iBusyAnim;
  1054. if ( m_iCurrentBusyAnim == -1 )
  1055. return iBase;
  1056. if ( ai_debug_actbusy.GetInt() == 4 )
  1057. {
  1058. Msg("ACTBUSY: NPC %s (%s) found Actbusy node %s \n", GetOuter()->GetClassname(), GetOuter()->GetDebugName(), pNode->GetDebugName() );
  1059. }
  1060. if ( GetHintNode() )
  1061. {
  1062. GetHintNode()->Unlock();
  1063. }
  1064. SetHintNode( pNode );
  1065. if ( GetHintNode() && GetHintNode()->Lock( GetOuter() ) )
  1066. {
  1067. if ( ai_debug_actbusy.GetInt() == 2 )
  1068. {
  1069. // Show which actbusy we're moving towards
  1070. NDebugOverlay::Line( GetOuter()->WorldSpaceCenter(), pNode->GetAbsOrigin(), 0, 255, 0, true, 5.0 );
  1071. NDebugOverlay::Box( pNode->GetAbsOrigin(), GetOuter()->WorldAlignMins(), GetOuter()->WorldAlignMaxs(), 0, 255, 0, 64, 5.0 );
  1072. }
  1073. // Let our act busy know we're moving to a node
  1074. if ( m_hActBusyGoal )
  1075. {
  1076. m_hActBusyGoal->NPCMovingToBusy( GetOuter() );
  1077. }
  1078. m_bMovingToBusy = true;
  1079. if( m_hActBusyGoal && m_hActBusyGoal->m_iszSeeEntityName != NULL_STRING )
  1080. {
  1081. // Set the see entity Handle if we have one.
  1082. m_hSeeEntity.Set( gEntList.FindEntityByName(NULL, m_hActBusyGoal->m_iszSeeEntityName) );
  1083. }
  1084. // At this point we know we're starting.
  1085. ClearCondition( COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE );
  1086. // If we're supposed to teleport, do that instead
  1087. if ( m_bTeleportToBusy )
  1088. {
  1089. return SCHED_ACTBUSY_TELEPORT_TO_BUSY;
  1090. }
  1091. else if( bForceTeleport )
  1092. {
  1093. // We found a place to go, so teleport there and forget that we ever had trouble.
  1094. m_iNumConsecutivePathFailures = 0;
  1095. return SCHED_ACTBUSY_TELEPORT_TO_BUSY;
  1096. }
  1097. return SCHED_ACTBUSY_START_BUSYING;
  1098. }
  1099. }
  1100. }
  1101. else
  1102. {
  1103. // WE DIDN'T FIND A NODE!
  1104. if( IsCombatActBusy() )
  1105. {
  1106. // Don't try again right away, not enough state will have changed.
  1107. // Just go do something useful for a few seconds.
  1108. m_flNextBusySearchTime = gpGlobals->curtime + 10.0;
  1109. }
  1110. }
  1111. }
  1112. return SCHED_NONE;
  1113. }
  1114. //-----------------------------------------------------------------------------
  1115. // Purpose:
  1116. //-----------------------------------------------------------------------------
  1117. int CAI_ActBusyBehavior::SelectScheduleWhileBusy( void )
  1118. {
  1119. // Are we supposed to stop on our current actbusy, but stay in the actbusy state?
  1120. if ( !ActBusyNodeStillActive() || (m_flEndBusyAt && gpGlobals->curtime >= m_flEndBusyAt) )
  1121. {
  1122. if ( ai_debug_actbusy.GetInt() == 4 )
  1123. {
  1124. Msg("ACTBUSY: NPC %s (%s) ending actbusy.\n", GetOuter()->GetClassname(), GetOuter()->GetDebugName() );
  1125. }
  1126. StopBusying();
  1127. return SCHED_ACTBUSY_STOP_BUSYING;
  1128. }
  1129. if( IsCombatActBusy() && (HasCondition(COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE) || HasCondition(COND_ACTBUSY_ENEMY_TOO_CLOSE)) )
  1130. {
  1131. return SCHED_ACTBUSY_STOP_BUSYING;
  1132. }
  1133. return SCHED_ACTBUSY_BUSY;
  1134. }
  1135. //-----------------------------------------------------------------------------
  1136. // Purpose:
  1137. //-----------------------------------------------------------------------------
  1138. int CAI_ActBusyBehavior::SelectSchedule()
  1139. {
  1140. int iBase = BaseClass::SelectSchedule();
  1141. // Only do something if the base ai doesn't want to do anything
  1142. if ( !IsCombatActBusy() && !m_bForceActBusy && iBase != SCHED_IDLE_STAND )
  1143. {
  1144. // If we're busy, we need to get out of it first
  1145. if ( m_bBusy )
  1146. return SCHED_ACTBUSY_STOP_BUSYING;
  1147. CheckAndCleanupOnExit();
  1148. return iBase;
  1149. }
  1150. // If we're supposed to be leaving, find a leave node and exit
  1151. if ( m_bLeaving )
  1152. return SelectScheduleForLeaving();
  1153. // NPCs should not be busy if the actbusy behaviour has been disabled, or if they've received player squad commands
  1154. bool bShouldNotBeBusy = (!m_bEnabled || HasCondition( COND_PLAYER_ADDED_TO_SQUAD ) || HasCondition( COND_RECEIVED_ORDERS ) );
  1155. if ( bShouldNotBeBusy )
  1156. {
  1157. if ( !GetOuter()->IsMarkedForDeletion() && GetOuter()->IsAlive() )
  1158. return SCHED_ACTBUSY_STOP_BUSYING;
  1159. }
  1160. else
  1161. {
  1162. if ( m_bBusy )
  1163. return SelectScheduleWhileBusy();
  1164. // I'm not busy, and I'm supposed to be
  1165. int schedule = SelectScheduleWhileNotBusy( iBase );
  1166. if ( schedule != SCHED_NONE )
  1167. return schedule;
  1168. }
  1169. CheckAndCleanupOnExit();
  1170. return iBase;
  1171. }
  1172. //-----------------------------------------------------------------------------
  1173. // Purpose:
  1174. //-----------------------------------------------------------------------------
  1175. bool CAI_ActBusyBehavior::ActBusyNodeStillActive( void )
  1176. {
  1177. if ( !GetHintNode() )
  1178. return false;
  1179. return ( GetHintNode()->IsDisabled() == false );
  1180. }
  1181. //-----------------------------------------------------------------------------
  1182. // Purpose:
  1183. // Output : Returns true on success, false on failure.
  1184. //-----------------------------------------------------------------------------
  1185. bool CAI_ActBusyBehavior::IsInterruptable( void )
  1186. {
  1187. if ( IsActive() )
  1188. return false;
  1189. return BaseClass::IsInterruptable();
  1190. }
  1191. //-----------------------------------------------------------------------------
  1192. // Purpose:
  1193. // Output : Returns true on success, false on failure.
  1194. //-----------------------------------------------------------------------------
  1195. bool CAI_ActBusyBehavior::CanFlinch( void )
  1196. {
  1197. if ( m_bNeedsToPlayExitAnim )
  1198. return false;
  1199. return BaseClass::CanFlinch();
  1200. }
  1201. //-----------------------------------------------------------------------------
  1202. // Purpose:
  1203. //-----------------------------------------------------------------------------
  1204. bool CAI_ActBusyBehavior::CanRunAScriptedNPCInteraction( bool bForced )
  1205. {
  1206. // Prevent interactions during actbusy modes
  1207. if ( IsActive() )
  1208. return false;
  1209. return BaseClass::CanRunAScriptedNPCInteraction( bForced );
  1210. }
  1211. //-----------------------------------------------------------------------------
  1212. // Purpose:
  1213. //-----------------------------------------------------------------------------
  1214. void CAI_ActBusyBehavior::OnScheduleChange()
  1215. {
  1216. if( IsCurSchedule(SCHED_ACTBUSY_BUSY, false) )
  1217. {
  1218. if( HasCondition(COND_SEE_ENEMY) )
  1219. {
  1220. m_bExitedBusyToDueSeeEnemy = true;
  1221. }
  1222. if( HasCondition(COND_ACTBUSY_LOST_SEE_ENTITY) )
  1223. {
  1224. m_bExitedBusyToDueLostSeeEntity = true;
  1225. }
  1226. }
  1227. BaseClass::OnScheduleChange();
  1228. }
  1229. //-----------------------------------------------------------------------------
  1230. // Purpose:
  1231. //-----------------------------------------------------------------------------
  1232. bool CAI_ActBusyBehavior::QueryHearSound( CSound *pSound )
  1233. {
  1234. // Ignore friendly created combat sounds while in an actbusy.
  1235. // Fixes friendly NPCs going in & out of actbusies when the
  1236. // player fires shots at their feet.
  1237. if ( pSound->IsSoundType( SOUND_COMBAT ) || pSound->IsSoundType( SOUND_BULLET_IMPACT ) )
  1238. {
  1239. if ( GetOuter()->IRelationType( pSound->m_hOwner ) == D_LI )
  1240. return false;
  1241. }
  1242. return BaseClass::QueryHearSound( pSound );
  1243. }
  1244. //-----------------------------------------------------------------------------
  1245. // Purpose: Because none of the startbusy schedules break on COND_NEW_ENEMY
  1246. // we have to do this distance check against all enemy NPCs we
  1247. // see as we're traveling to an ACTBUSY node
  1248. //-----------------------------------------------------------------------------
  1249. #define ACTBUSY_ENEMY_TOO_CLOSE_DIST_SQR Square(240) // 20 feet
  1250. void CAI_ActBusyBehavior::OnSeeEntity( CBaseEntity *pEntity )
  1251. {
  1252. BaseClass::OnSeeEntity( pEntity );
  1253. if( IsCombatActBusy() && GetOuter()->IRelationType(pEntity) < D_LI )
  1254. {
  1255. if( pEntity->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) <= ACTBUSY_ENEMY_TOO_CLOSE_DIST_SQR )
  1256. {
  1257. SetCondition( COND_ACTBUSY_ENEMY_TOO_CLOSE );
  1258. }
  1259. }
  1260. }
  1261. //-----------------------------------------------------------------------------
  1262. // Purpose:
  1263. // Output : Returns true on success, false on failure.
  1264. //-----------------------------------------------------------------------------
  1265. bool CAI_ActBusyBehavior::ShouldPlayerAvoid( void )
  1266. {
  1267. if( IsCombatActBusy() )
  1268. {
  1269. // Alyx is only allowed to push if she's getting into or out of an actbusy
  1270. // animation. She isn't allowed to shove you around while she's running around
  1271. if ( IsCurSchedule(SCHED_ACTBUSY_START_BUSYING) )
  1272. {
  1273. if ( GetCurTask() && GetCurTask()->iTask == TASK_ACTBUSY_PLAY_ENTRY )
  1274. return true;
  1275. }
  1276. else if ( IsCurSchedule(SCHED_ACTBUSY_STOP_BUSYING) )
  1277. {
  1278. if ( GetCurTask() && GetCurTask()->iTask == TASK_ACTBUSY_PLAY_EXIT )
  1279. return true;
  1280. }
  1281. }
  1282. else
  1283. {
  1284. if ( IsCurSchedule ( SCHED_ACTBUSY_START_BUSYING ) )
  1285. {
  1286. if ( ( GetCurTask() && GetCurTask()->iTask == TASK_WAIT_FOR_MOVEMENT ) || GetOuter()->GetTask()->iTask == TASK_ACTBUSY_PLAY_ENTRY )
  1287. return true;
  1288. }
  1289. else if ( IsCurSchedule(SCHED_ACTBUSY_STOP_BUSYING) )
  1290. {
  1291. if ( GetCurTask() && GetCurTask()->iTask == TASK_ACTBUSY_PLAY_EXIT )
  1292. return true;
  1293. }
  1294. }
  1295. return BaseClass::ShouldPlayerAvoid();
  1296. }
  1297. //-----------------------------------------------------------------------------
  1298. //-----------------------------------------------------------------------------
  1299. void CAI_ActBusyBehavior::ComputeAndSetRenderBounds()
  1300. {
  1301. Vector mins, maxs;
  1302. if ( GetOuter()->ComputeHitboxSurroundingBox( &mins, &maxs ) )
  1303. {
  1304. UTIL_SetSize( GetOuter(), mins - GetAbsOrigin(), maxs - GetAbsOrigin());
  1305. if ( GetOuter()->VPhysicsGetObject() )
  1306. {
  1307. GetOuter()->SetupVPhysicsHull();
  1308. }
  1309. }
  1310. }
  1311. //-----------------------------------------------------------------------------
  1312. // Purpose: Returns true if the current NPC is acting busy, or moving to an actbusy
  1313. //-----------------------------------------------------------------------------
  1314. bool CAI_ActBusyBehavior::IsActive( void )
  1315. {
  1316. return ( m_bBusy || m_bForceActBusy || m_bNeedsToPlayExitAnim || m_bLeaving );
  1317. }
  1318. //-----------------------------------------------------------------------------
  1319. //-----------------------------------------------------------------------------
  1320. bool CAI_ActBusyBehavior::IsCombatActBusy()
  1321. {
  1322. if( m_hActBusyGoal != NULL )
  1323. return (m_hActBusyGoal->GetType() == ACTBUSY_TYPE_COMBAT);
  1324. return false;
  1325. }
  1326. //-----------------------------------------------------------------------------
  1327. //-----------------------------------------------------------------------------
  1328. void CAI_ActBusyBehavior::CollectSafeZoneVolumes( CAI_ActBusyGoal *pActBusyGoal )
  1329. {
  1330. // Reset these, so we don't use a volume from a previous actbusy goal.
  1331. m_SafeZones.RemoveAll();
  1332. if( pActBusyGoal->m_iszSafeZoneVolume != NULL_STRING )
  1333. {
  1334. CBaseEntity *pVolume = gEntList.FindEntityByName( NULL, pActBusyGoal->m_iszSafeZoneVolume );
  1335. while( pVolume != NULL )
  1336. {
  1337. busysafezone_t newSafeZone;
  1338. pVolume->CollisionProp()->WorldSpaceAABB( &newSafeZone.vecMins, &newSafeZone.vecMaxs );
  1339. m_SafeZones.AddToTail( newSafeZone );
  1340. pVolume = gEntList.FindEntityByName( pVolume, pActBusyGoal->m_iszSafeZoneVolume );
  1341. }
  1342. }
  1343. if( ai_debug_actbusy.GetInt() == 5 )
  1344. {
  1345. Msg( "Actbusy collected %d safe zones\n", m_SafeZones.Count() );
  1346. }
  1347. }
  1348. //-----------------------------------------------------------------------------
  1349. //-----------------------------------------------------------------------------
  1350. bool CAI_ActBusyBehavior::IsInSafeZone( CBaseEntity *pEntity )
  1351. {
  1352. Vector vecLocation = pEntity->WorldSpaceCenter();
  1353. for( int i = 0 ; i < m_SafeZones.Count() ; i++ )
  1354. {
  1355. busysafezone_t *pSafeZone = &m_SafeZones[i];
  1356. if( vecLocation.x > pSafeZone->vecMins.x &&
  1357. vecLocation.y > pSafeZone->vecMins.y &&
  1358. vecLocation.z > pSafeZone->vecMins.z &&
  1359. vecLocation.x < pSafeZone->vecMaxs.x &&
  1360. vecLocation.y < pSafeZone->vecMaxs.y &&
  1361. vecLocation.z < pSafeZone->vecMaxs.z )
  1362. {
  1363. return true;
  1364. }
  1365. }
  1366. return false;
  1367. }
  1368. //-----------------------------------------------------------------------------
  1369. // Purpose: Return true if this NPC has the anims required to use the specified actbusy hint
  1370. //-----------------------------------------------------------------------------
  1371. bool CAI_ActBusyBehavior::HasAnimForActBusy( int iActBusy, busyanimparts_t AnimPart )
  1372. {
  1373. if ( iActBusy == -1 )
  1374. return false;
  1375. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( iActBusy );
  1376. if ( !pBusyAnim )
  1377. return false;
  1378. // Try and play the sequence first
  1379. if ( pBusyAnim->iszSequences[AnimPart] != NULL_STRING )
  1380. return (GetOuter()->LookupSequence( (char*)STRING(pBusyAnim->iszSequences[AnimPart]) ) != ACTIVITY_NOT_AVAILABLE);
  1381. // Try and play the activity second
  1382. if ( pBusyAnim->iActivities[AnimPart] != ACT_INVALID )
  1383. return GetOuter()->HaveSequenceForActivity( pBusyAnim->iActivities[AnimPart] );
  1384. return false;
  1385. }
  1386. //-----------------------------------------------------------------------------
  1387. // Purpose: Play the sound associated with the specified part of the current actbusy, if any
  1388. //-----------------------------------------------------------------------------
  1389. void CAI_ActBusyBehavior::PlaySoundForActBusy( busyanimparts_t AnimPart )
  1390. {
  1391. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( m_iCurrentBusyAnim );
  1392. if ( !pBusyAnim )
  1393. return;
  1394. // Play the sound
  1395. if ( pBusyAnim->iszSounds[AnimPart] != NULL_STRING )
  1396. {
  1397. // See if we can treat it as a game sound name
  1398. CSoundParameters params;
  1399. if ( GetOuter()->GetParametersForSound( STRING(pBusyAnim->iszSounds[AnimPart]), params, STRING(GetOuter()->GetModelName()) ) )
  1400. {
  1401. CPASAttenuationFilter filter( GetOuter() );
  1402. GetOuter()->EmitSound( filter, GetOuter()->entindex(), params );
  1403. }
  1404. else
  1405. {
  1406. // Assume it's a response concept, and try to speak it
  1407. CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
  1408. if ( pExpresser )
  1409. {
  1410. const char *concept = STRING(pBusyAnim->iszSounds[AnimPart]);
  1411. // Must be able to speak the concept
  1412. if ( !pExpresser->IsSpeaking() && pExpresser->CanSpeakConcept( concept ) )
  1413. {
  1414. pExpresser->Speak( concept );
  1415. }
  1416. }
  1417. }
  1418. }
  1419. }
  1420. //-----------------------------------------------------------------------------
  1421. // Purpose: Play a sequence or activity for the current actbusy
  1422. //-----------------------------------------------------------------------------
  1423. bool CAI_ActBusyBehavior::PlayAnimForActBusy( busyanimparts_t AnimPart )
  1424. {
  1425. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( m_iCurrentBusyAnim );
  1426. if ( !pBusyAnim )
  1427. return false;
  1428. // Try and play the sequence first
  1429. if ( pBusyAnim->iszSequences[AnimPart] != NULL_STRING )
  1430. {
  1431. GetOuter()->SetSequenceByName( (char*)STRING(pBusyAnim->iszSequences[AnimPart]) );
  1432. GetOuter()->SetIdealActivity( ACT_DO_NOT_DISTURB );
  1433. return true;
  1434. }
  1435. // Try and play the activity second
  1436. if ( pBusyAnim->iActivities[AnimPart] != ACT_INVALID )
  1437. {
  1438. GetOuter()->SetIdealActivity( pBusyAnim->iActivities[AnimPart] );
  1439. return true;
  1440. }
  1441. return false;
  1442. }
  1443. //-----------------------------------------------------------------------------
  1444. // Purpose:
  1445. //-----------------------------------------------------------------------------
  1446. void CAI_ActBusyBehavior::StartTask( const Task_t *pTask )
  1447. {
  1448. switch ( pTask->iTask )
  1449. {
  1450. case TASK_ACTBUSY_PLAY_BUSY_ANIM:
  1451. {
  1452. // If we're not enabled here, it's due to the actbusy being deactivated during
  1453. // the NPC's entry animation. We can't abort in the middle of the entry, so we
  1454. // arrive here with a disabled actbusy behaviour. Exit gracefully.
  1455. if ( !m_bEnabled )
  1456. {
  1457. TaskComplete();
  1458. return;
  1459. }
  1460. // Set the flag to remind the code to recompute the NPC's box from render bounds.
  1461. // This is used to delay the process so that we don't get a box built from render bounds
  1462. // when a character is still interpolating to their busy pose.
  1463. m_bNeedToSetBounds = true;
  1464. // Get the busyanim for the specified activity
  1465. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( m_iCurrentBusyAnim );
  1466. // We start "flying" so we don't collide with the world, in case the level
  1467. // designer has us sitting on a chair, etc.
  1468. if( !pBusyAnim || !pBusyAnim->bUseAutomovement )
  1469. {
  1470. GetOuter()->AddFlag( FL_FLY );
  1471. }
  1472. GetOuter()->SetGroundEntity( NULL );
  1473. // Fail if we're not on the node & facing the correct way
  1474. // We only do this check if we're still moving to the busy. This will only
  1475. // be true if there was no entry animation for this busy. We do it this way
  1476. // because the entry code contains this same check, and so we assume we're
  1477. // valid even if we're off now, because some entry animations move the
  1478. // character off the node.
  1479. if ( m_bMovingToBusy )
  1480. {
  1481. if ( UTIL_DistApprox( GetHintNode()->GetAbsOrigin(), GetAbsOrigin() ) > 16 || !GetOuter()->FacingIdeal() )
  1482. {
  1483. TaskFail( "Not correctly on hintnode" );
  1484. m_flEndBusyAt = gpGlobals->curtime;
  1485. return;
  1486. }
  1487. }
  1488. m_bMovingToBusy = false;
  1489. if ( !ActBusyNodeStillActive() )
  1490. {
  1491. TaskFail( FAIL_NO_HINT_NODE );
  1492. return;
  1493. }
  1494. // Have we just started using this node?
  1495. if ( !m_bBusy )
  1496. {
  1497. m_bBusy = true;
  1498. GetHintNode()->NPCStartedUsing( GetOuter() );
  1499. if ( m_hActBusyGoal )
  1500. {
  1501. m_hActBusyGoal->NPCStartedBusy( GetOuter() );
  1502. }
  1503. if ( pBusyAnim )
  1504. {
  1505. float flMaxTime = pBusyAnim->flMaxTime;
  1506. float flMinTime = pBusyAnim->flMinTime;
  1507. // Mapmaker input may have specified it's own max time
  1508. if ( m_bForceActBusy && m_flForcedMaxTime != NO_MAX_TIME )
  1509. {
  1510. flMaxTime = m_flForcedMaxTime;
  1511. // Don't let non-unlimited time amounts be less than the mintime
  1512. if ( flMaxTime && flMaxTime < flMinTime )
  1513. {
  1514. flMinTime = flMaxTime;
  1515. }
  1516. }
  1517. // If we have no max time, or we're in a queue, we loop forever.
  1518. if ( !flMaxTime || m_bInQueue )
  1519. {
  1520. m_flEndBusyAt = 0;
  1521. GetOuter()->SetWait( 99999 );
  1522. }
  1523. else
  1524. {
  1525. float flTime = RandomFloat(flMinTime, flMaxTime);
  1526. m_flEndBusyAt = gpGlobals->curtime + flTime;
  1527. GetOuter()->SetWait( flTime );
  1528. }
  1529. }
  1530. }
  1531. // Start playing the act busy
  1532. PlayAnimForActBusy( BA_BUSY );
  1533. PlaySoundForActBusy( BA_BUSY );
  1534. // Now that we're busy, we don't need to be forced anymore
  1535. m_bForceActBusy = false;
  1536. m_bTeleportToBusy = false;
  1537. m_bUseNearestBusy = false;
  1538. m_ForcedActivity = ACT_INVALID;
  1539. // If we're supposed to use render bounds while inside the busy anim, do so
  1540. if ( m_bUseRenderBoundsForCollision )
  1541. {
  1542. ComputeAndSetRenderBounds();
  1543. }
  1544. }
  1545. break;
  1546. case TASK_ACTBUSY_PLAY_ENTRY:
  1547. {
  1548. // We start "flying" so we don't collide with the world, in case the level
  1549. // designer has us sitting on a chair, etc.
  1550. // Get the busyanim for the specified activity
  1551. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( m_iCurrentBusyAnim );
  1552. // We start "flying" so we don't collide with the world, in case the level
  1553. // designer has us sitting on a chair, etc.
  1554. if( !pBusyAnim || !pBusyAnim->bUseAutomovement )
  1555. {
  1556. GetOuter()->AddFlag( FL_FLY );
  1557. }
  1558. GetOuter()->SetGroundEntity( NULL );
  1559. m_bMovingToBusy = false;
  1560. m_bNeedsToPlayExitAnim = HasAnimForActBusy( m_iCurrentBusyAnim, BA_EXIT );
  1561. if ( !ActBusyNodeStillActive() )
  1562. {
  1563. TaskFail( FAIL_NO_HINT_NODE );
  1564. return;
  1565. }
  1566. // Fail if we're not on the node & facing the correct way
  1567. if ( UTIL_DistApprox( GetHintNode()->GetAbsOrigin(), GetAbsOrigin() ) > 16 || !GetOuter()->FacingIdeal() )
  1568. {
  1569. m_bBusy = false;
  1570. TaskFail( "Not correctly on hintnode" );
  1571. return;
  1572. }
  1573. PlaySoundForActBusy( BA_ENTRY );
  1574. // Play the entry animation. If it fails, we don't have an entry anim, so complete immediately.
  1575. if ( !PlayAnimForActBusy( BA_ENTRY ) )
  1576. {
  1577. TaskComplete();
  1578. }
  1579. }
  1580. break;
  1581. case TASK_ACTBUSY_VERIFY_EXIT:
  1582. {
  1583. // NPC's that changed their bounding box must ensure that they can restore their regular box
  1584. // before they exit their actbusy. This task is designed to delay until that time if necessary.
  1585. if( !m_bUseRenderBoundsForCollision )
  1586. {
  1587. // Don't bother if we didn't alter our BBox.
  1588. TaskComplete();
  1589. break;
  1590. }
  1591. // Set up a timer to check immediately.
  1592. GetOuter()->SetWait( 0 );
  1593. }
  1594. break;
  1595. case TASK_ACTBUSY_PLAY_EXIT:
  1596. {
  1597. // If we're supposed to use render bounds while inside the busy anim, restore normal now
  1598. if ( m_bUseRenderBoundsForCollision )
  1599. {
  1600. GetOuter()->SetHullSizeNormal( true );
  1601. }
  1602. if ( m_hActBusyGoal )
  1603. {
  1604. m_hActBusyGoal->NPCStartedLeavingBusy( GetOuter() );
  1605. }
  1606. PlaySoundForActBusy( BA_EXIT );
  1607. // Play the exit animation. If it fails, we don't have an entry anim, so complete immediately.
  1608. if ( !PlayAnimForActBusy( BA_EXIT ) )
  1609. {
  1610. m_bNeedsToPlayExitAnim = false;
  1611. GetOuter()->RemoveFlag( FL_FLY );
  1612. NotifyBusyEnding();
  1613. TaskComplete();
  1614. }
  1615. }
  1616. break;
  1617. case TASK_ACTBUSY_TELEPORT_TO_BUSY:
  1618. {
  1619. if ( !ActBusyNodeStillActive() )
  1620. {
  1621. TaskFail( FAIL_NO_HINT_NODE );
  1622. return;
  1623. }
  1624. Vector vecAbsOrigin = GetHintNode()->GetAbsOrigin();
  1625. QAngle vecAbsAngles = GetHintNode()->GetAbsAngles();
  1626. GetOuter()->Teleport( &vecAbsOrigin, &vecAbsAngles, NULL );
  1627. GetOuter()->GetMotor()->SetIdealYaw( vecAbsAngles.y );
  1628. TaskComplete();
  1629. }
  1630. break;
  1631. case TASK_ACTBUSY_WALK_PATH_TO_BUSY:
  1632. {
  1633. // If we have a forced activity, use that. Otherwise, walk.
  1634. if ( m_ForcedActivity != ACT_INVALID && m_ForcedActivity != ACT_RESET )
  1635. {
  1636. GetNavigator()->SetMovementActivity( m_ForcedActivity );
  1637. // Cover is void once I move
  1638. Forget( bits_MEMORY_INCOVER );
  1639. TaskComplete();
  1640. }
  1641. else
  1642. {
  1643. if( IsCombatActBusy() )
  1644. {
  1645. ChainStartTask( TASK_RUN_PATH );
  1646. }
  1647. else
  1648. {
  1649. ChainStartTask( TASK_WALK_PATH );
  1650. }
  1651. }
  1652. break;
  1653. }
  1654. case TASK_ACTBUSY_GET_PATH_TO_ACTBUSY:
  1655. {
  1656. ChainStartTask( TASK_GET_PATH_TO_HINTNODE );
  1657. if ( !HasCondition(COND_TASK_FAILED) )
  1658. {
  1659. // We successfully built a path, so stop counting consecutive failures.
  1660. m_iNumConsecutivePathFailures = 0;
  1661. // Set the arrival sequence for the actbusy to be the busy sequence, if we don't have an entry animation
  1662. busyanim_t *pBusyAnim = g_ActBusyAnimDataSystem.GetBusyAnim( m_iCurrentBusyAnim );
  1663. if ( pBusyAnim && pBusyAnim->iszSequences[BA_ENTRY] == NULL_STRING && pBusyAnim->iActivities[BA_ENTRY] == ACT_INVALID )
  1664. {
  1665. // Try and play the sequence first
  1666. if ( pBusyAnim->iszSequences[BA_BUSY] != NULL_STRING )
  1667. {
  1668. GetNavigator()->SetArrivalSequence( GetOuter()->LookupSequence( STRING(pBusyAnim->iszSequences[BA_BUSY]) ) );
  1669. }
  1670. else if ( pBusyAnim->iActivities[BA_BUSY] != ACT_INVALID )
  1671. {
  1672. // Try and play the activity second
  1673. GetNavigator()->SetArrivalActivity( pBusyAnim->iActivities[BA_BUSY] );
  1674. }
  1675. }
  1676. else
  1677. {
  1678. // Robin: Set the arrival sequence / activity to be the entry animation.
  1679. if ( pBusyAnim->iszSequences[BA_ENTRY] != NULL_STRING )
  1680. {
  1681. GetNavigator()->SetArrivalSequence( GetOuter()->LookupSequence( STRING(pBusyAnim->iszSequences[BA_ENTRY]) ) );
  1682. }
  1683. else if ( pBusyAnim->iActivities[BA_ENTRY] != ACT_INVALID )
  1684. {
  1685. // Try and play the activity second
  1686. GetNavigator()->SetArrivalActivity( pBusyAnim->iActivities[BA_ENTRY] );
  1687. }
  1688. }
  1689. }
  1690. else
  1691. {
  1692. m_iNumConsecutivePathFailures++;
  1693. if ( ai_debug_actbusy.GetInt() == 1 )
  1694. {
  1695. if ( GetHintNode() )
  1696. {
  1697. // Show which actbusy we're moving towards
  1698. NDebugOverlay::Line( GetOuter()->WorldSpaceCenter(), GetHintNode()->GetAbsOrigin(), 255, 0, 0, true, 1.0 );
  1699. }
  1700. }
  1701. }
  1702. break;
  1703. }
  1704. default:
  1705. BaseClass::StartTask( pTask);
  1706. }
  1707. }
  1708. //-----------------------------------------------------------------------------
  1709. // Purpose:
  1710. //-----------------------------------------------------------------------------
  1711. void CAI_ActBusyBehavior::RunTask( const Task_t *pTask )
  1712. {
  1713. switch ( pTask->iTask )
  1714. {
  1715. case TASK_WAIT_FOR_MOVEMENT:
  1716. {
  1717. // Ensure the hint node hasn't been disabled
  1718. if ( IsCurSchedule( SCHED_ACTBUSY_START_BUSYING ) )
  1719. {
  1720. if ( !ActBusyNodeStillActive() )
  1721. {
  1722. TaskFail(FAIL_NO_HINT_NODE);
  1723. return;
  1724. }
  1725. }
  1726. if ( ai_debug_actbusy.GetInt() == 1 )
  1727. {
  1728. if ( GetHintNode() )
  1729. {
  1730. // Show which actbusy we're moving towards
  1731. NDebugOverlay::Line( GetOuter()->WorldSpaceCenter(), GetHintNode()->GetAbsOrigin(), 0, 255, 0, true, 0.2 );
  1732. }
  1733. }
  1734. BaseClass::RunTask( pTask );
  1735. break;
  1736. }
  1737. case TASK_ACTBUSY_PLAY_BUSY_ANIM:
  1738. {
  1739. if( m_bUseRenderBoundsForCollision )
  1740. {
  1741. if( GetOuter()->IsSequenceFinished() && m_bNeedToSetBounds )
  1742. {
  1743. ComputeAndSetRenderBounds();
  1744. m_bNeedToSetBounds = false;
  1745. }
  1746. }
  1747. if( IsCombatActBusy() )
  1748. {
  1749. if( GetEnemy() != NULL && !HasCondition(COND_ENEMY_OCCLUDED) )
  1750. {
  1751. // Break a combat actbusy if an enemy gets very close.
  1752. // I'll probably go to hell for not doing this with conditions like I should. (sjb)
  1753. float flDistSqr = GetAbsOrigin().DistToSqr( GetEnemy()->GetAbsOrigin() );
  1754. if( flDistSqr < Square(12.0f * 15.0f) )
  1755. {
  1756. // End now.
  1757. m_flEndBusyAt = gpGlobals->curtime;
  1758. TaskComplete();
  1759. return;
  1760. }
  1761. }
  1762. }
  1763. GetOuter()->AutoMovement();
  1764. // Stop if the node's been disabled
  1765. if ( !ActBusyNodeStillActive() || GetOuter()->IsWaitFinished() )
  1766. {
  1767. TaskComplete();
  1768. }
  1769. else
  1770. {
  1771. CAI_PlayerAlly *pAlly = dynamic_cast<CAI_PlayerAlly*>(GetOuter());
  1772. if ( pAlly )
  1773. {
  1774. pAlly->SelectInterjection();
  1775. }
  1776. if( HasCondition(COND_ACTBUSY_LOST_SEE_ENTITY) )
  1777. {
  1778. StopBusying();
  1779. TaskComplete();
  1780. }
  1781. }
  1782. break;
  1783. }
  1784. case TASK_ACTBUSY_PLAY_ENTRY:
  1785. {
  1786. GetOuter()->AutoMovement();
  1787. if ( !ActBusyNodeStillActive() || GetOuter()->IsSequenceFinished() )
  1788. {
  1789. TaskComplete();
  1790. }
  1791. }
  1792. break;
  1793. case TASK_ACTBUSY_VERIFY_EXIT:
  1794. {
  1795. if( GetOuter()->IsWaitFinished() )
  1796. {
  1797. // Trace my normal hull over this spot to see if I'm able to stand up right now.
  1798. trace_t tr;
  1799. CTraceFilterOnlyNPCsAndPlayer filter( GetOuter(), COLLISION_GROUP_NONE );
  1800. UTIL_TraceHull( GetOuter()->GetAbsOrigin(), GetOuter()->GetAbsOrigin(), NAI_Hull::Mins( HULL_HUMAN ), NAI_Hull::Maxs( HULL_HUMAN ), MASK_NPCSOLID, &filter, &tr );
  1801. if( tr.startsolid )
  1802. {
  1803. // Blocked. Try again later.
  1804. GetOuter()->SetWait( 1.0f );
  1805. }
  1806. else
  1807. {
  1808. // Put an entity blocker here for a moment until I get into my bounding box.
  1809. CBaseEntity *pBlocker = CEntityBlocker::Create( GetOuter()->GetAbsOrigin(), NAI_Hull::Mins( HULL_HUMAN ), NAI_Hull::Maxs( HULL_HUMAN ), GetOuter(), true );
  1810. g_EventQueue.AddEvent( pBlocker, "Kill", 1.0, GetOuter(), GetOuter() );
  1811. TaskComplete();
  1812. }
  1813. }
  1814. }
  1815. break;
  1816. case TASK_ACTBUSY_PLAY_EXIT:
  1817. {
  1818. GetOuter()->AutoMovement();
  1819. if ( GetOuter()->IsSequenceFinished() )
  1820. {
  1821. m_bNeedsToPlayExitAnim = false;
  1822. GetOuter()->RemoveFlag( FL_FLY );
  1823. NotifyBusyEnding();
  1824. TaskComplete();
  1825. }
  1826. }
  1827. break;
  1828. default:
  1829. BaseClass::RunTask( pTask);
  1830. }
  1831. }
  1832. //-----------------------------------------------------------------------------
  1833. // Purpose:
  1834. //-----------------------------------------------------------------------------
  1835. void CAI_ActBusyBehavior::NotifyBusyEnding( void )
  1836. {
  1837. // Be sure to disable autofire
  1838. m_bAutoFireWeapon = false;
  1839. // Clear the hintnode if we're done with it
  1840. if ( GetHintNode() )
  1841. {
  1842. if ( m_bBusy || m_bMovingToBusy )
  1843. {
  1844. GetHintNode()->NPCStoppedUsing( GetOuter() );
  1845. }
  1846. GetHintNode()->Unlock();
  1847. if( IsCombatActBusy() )
  1848. {
  1849. // Don't allow anyone to use this node for a bit. This is so the tactical position
  1850. // doesn't get re-occupied the moment I leave it.
  1851. GetHintNode()->DisableForSeconds( random->RandomFloat( 10, 15) );
  1852. }
  1853. SetHintNode( NULL );
  1854. }
  1855. // Then, if we were busy, stop being busy
  1856. if ( m_bBusy )
  1857. {
  1858. m_bBusy = false;
  1859. if ( m_hActBusyGoal )
  1860. {
  1861. m_hActBusyGoal->NPCFinishedBusy( GetOuter() );
  1862. if ( m_bExitedBusyToDueLostSeeEntity )
  1863. {
  1864. m_hActBusyGoal->NPCLostSeeEntity( GetOuter() );
  1865. m_bExitedBusyToDueLostSeeEntity = false;
  1866. }
  1867. if ( m_bExitedBusyToDueSeeEnemy )
  1868. {
  1869. m_hActBusyGoal->NPCSeeEnemy( GetOuter() );
  1870. m_bExitedBusyToDueSeeEnemy = false;
  1871. }
  1872. }
  1873. }
  1874. else if ( m_bMovingToBusy && m_hActBusyGoal )
  1875. {
  1876. // Or if we were just on our way to be busy, let the goal know
  1877. m_hActBusyGoal->NPCAbortedMoveTo( GetOuter() );
  1878. }
  1879. // Don't busy again for a while
  1880. m_flEndBusyAt = 0;
  1881. if( IsCombatActBusy() )
  1882. {
  1883. // Actbusy again soon. Real soon.
  1884. m_flNextBusySearchTime = gpGlobals->curtime;
  1885. }
  1886. else
  1887. {
  1888. m_flNextBusySearchTime = gpGlobals->curtime + (RandomFloat(ai_actbusy_search_time.GetFloat(), ai_actbusy_search_time.GetFloat()*2));
  1889. }
  1890. }
  1891. //-------------------------------------
  1892. AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_ActBusyBehavior )
  1893. DECLARE_CONDITION( COND_ACTBUSY_LOST_SEE_ENTITY )
  1894. DECLARE_CONDITION( COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE )
  1895. DECLARE_CONDITION( COND_ACTBUSY_ENEMY_TOO_CLOSE )
  1896. DECLARE_TASK( TASK_ACTBUSY_PLAY_BUSY_ANIM )
  1897. DECLARE_TASK( TASK_ACTBUSY_PLAY_ENTRY )
  1898. DECLARE_TASK( TASK_ACTBUSY_PLAY_EXIT )
  1899. DECLARE_TASK( TASK_ACTBUSY_TELEPORT_TO_BUSY )
  1900. DECLARE_TASK( TASK_ACTBUSY_WALK_PATH_TO_BUSY )
  1901. DECLARE_TASK( TASK_ACTBUSY_GET_PATH_TO_ACTBUSY )
  1902. DECLARE_TASK( TASK_ACTBUSY_VERIFY_EXIT )
  1903. DECLARE_ANIMEVENT( AE_ACTBUSY_WEAPON_FIRE_ON )
  1904. DECLARE_ANIMEVENT( AE_ACTBUSY_WEAPON_FIRE_OFF )
  1905. //---------------------------------
  1906. DEFINE_SCHEDULE
  1907. (
  1908. SCHED_ACTBUSY_START_BUSYING,
  1909. " Tasks"
  1910. " TASK_SET_TOLERANCE_DISTANCE 4"
  1911. " TASK_ACTBUSY_GET_PATH_TO_ACTBUSY 0"
  1912. " TASK_ACTBUSY_WALK_PATH_TO_BUSY 0"
  1913. " TASK_WAIT_FOR_MOVEMENT 0"
  1914. " TASK_STOP_MOVING 0"
  1915. " TASK_FACE_HINTNODE 0"
  1916. " TASK_ACTBUSY_PLAY_ENTRY 0"
  1917. " TASK_SET_SCHEDULE SCHEDULE:SCHED_ACTBUSY_BUSY"
  1918. ""
  1919. " Interrupts"
  1920. " COND_ACTBUSY_LOST_SEE_ENTITY"
  1921. )
  1922. DEFINE_SCHEDULE
  1923. (
  1924. SCHED_ACTBUSY_BUSY,
  1925. " Tasks"
  1926. " TASK_ACTBUSY_PLAY_BUSY_ANIM 0"
  1927. ""
  1928. " Interrupts"
  1929. " COND_PROVOKED"
  1930. )
  1931. DEFINE_SCHEDULE
  1932. (
  1933. SCHED_ACTBUSY_STOP_BUSYING,
  1934. " Tasks"
  1935. " TASK_ACTBUSY_VERIFY_EXIT 0"
  1936. " TASK_ACTBUSY_PLAY_EXIT 0"
  1937. " TASK_WAIT 0.1"
  1938. ""
  1939. " Interrupts"
  1940. " COND_NO_CUSTOM_INTERRUPTS"
  1941. )
  1942. DEFINE_SCHEDULE
  1943. (
  1944. SCHED_ACTBUSY_LEAVE,
  1945. " Tasks"
  1946. " TASK_SET_TOLERANCE_DISTANCE 4"
  1947. " TASK_ACTBUSY_GET_PATH_TO_ACTBUSY 0"
  1948. " TASK_ACTBUSY_WALK_PATH_TO_BUSY 0"
  1949. " TASK_WAIT_FOR_MOVEMENT 0"
  1950. ""
  1951. " Interrupts"
  1952. " COND_PROVOKED"
  1953. )
  1954. DEFINE_SCHEDULE
  1955. (
  1956. SCHED_ACTBUSY_TELEPORT_TO_BUSY,
  1957. " Tasks"
  1958. " TASK_ACTBUSY_TELEPORT_TO_BUSY 0"
  1959. " TASK_ACTBUSY_PLAY_ENTRY 0"
  1960. " TASK_SET_SCHEDULE SCHEDULE:SCHED_ACTBUSY_BUSY"
  1961. ""
  1962. " Interrupts"
  1963. " COND_PROVOKED"
  1964. )
  1965. AI_END_CUSTOM_SCHEDULE_PROVIDER()
  1966. //==========================================================================================================
  1967. // ACT BUSY GOALS
  1968. //==========================================================================================================
  1969. //-----------------------------------------------------------------------------
  1970. // Purpose: A level tool to control the actbusy behavior.
  1971. //-----------------------------------------------------------------------------
  1972. LINK_ENTITY_TO_CLASS( ai_goal_actbusy, CAI_ActBusyGoal );
  1973. BEGIN_DATADESC( CAI_ActBusyGoal )
  1974. DEFINE_KEYFIELD( m_flBusySearchRange, FIELD_FLOAT, "busysearchrange" ),
  1975. DEFINE_KEYFIELD( m_bVisibleOnly, FIELD_BOOLEAN, "visibleonly" ),
  1976. DEFINE_KEYFIELD( m_iType, FIELD_INTEGER, "type" ),
  1977. DEFINE_KEYFIELD( m_bAllowCombatActBusyTeleport, FIELD_BOOLEAN, "allowteleport" ),
  1978. DEFINE_KEYFIELD( m_iszSeeEntityName, FIELD_STRING, "SeeEntity" ),
  1979. DEFINE_KEYFIELD( m_flSeeEntityTimeout, FIELD_FLOAT, "SeeEntityTimeout" ),
  1980. DEFINE_KEYFIELD( m_iszSafeZoneVolume, FIELD_STRING, "SafeZone" ),
  1981. DEFINE_KEYFIELD( m_iSightMethod, FIELD_INTEGER, "sightmethod" ),
  1982. // Inputs
  1983. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetBusySearchRange", InputSetBusySearchRange ),
  1984. DEFINE_INPUTFUNC( FIELD_STRING, "ForceNPCToActBusy", InputForceNPCToActBusy ),
  1985. DEFINE_INPUTFUNC( FIELD_EHANDLE, "ForceThisNPCToActBusy", InputForceThisNPCToActBusy ),
  1986. DEFINE_INPUTFUNC( FIELD_EHANDLE, "ForceThisNPCToLeave", InputForceThisNPCToLeave ),
  1987. // Outputs
  1988. DEFINE_OUTPUT( m_OnNPCStartedBusy, "OnNPCStartedBusy" ),
  1989. DEFINE_OUTPUT( m_OnNPCFinishedBusy, "OnNPCFinishedBusy" ),
  1990. DEFINE_OUTPUT( m_OnNPCLeft, "OnNPCLeft" ),
  1991. DEFINE_OUTPUT( m_OnNPCLostSeeEntity, "OnNPCLostSeeEntity" ),
  1992. DEFINE_OUTPUT( m_OnNPCSeeEnemy, "OnNPCSeeEnemy" ),
  1993. END_DATADESC()
  1994. //-----------------------------------------------------------------------------
  1995. // Purpose:
  1996. //-----------------------------------------------------------------------------
  1997. CAI_ActBusyBehavior *CAI_ActBusyGoal::GetBusyBehaviorForNPC( CBaseEntity *pEntity, const char *sInputName )
  1998. {
  1999. CAI_BaseNPC *pActor = dynamic_cast<CAI_BaseNPC*>(pEntity);
  2000. if ( !pActor )
  2001. {
  2002. Msg("ai_goal_actbusy input %s fired targeting an entity that isn't an NPC.\n", sInputName);
  2003. return NULL;
  2004. }
  2005. // Get the NPC's behavior
  2006. CAI_ActBusyBehavior *pBehavior;
  2007. if ( !pActor->GetBehavior( &pBehavior ) )
  2008. {
  2009. Msg("ai_goal_actbusy input %s fired on an NPC that doesn't support ActBusy behavior.\n", sInputName );
  2010. return NULL;
  2011. }
  2012. return pBehavior;
  2013. }
  2014. //-----------------------------------------------------------------------------
  2015. // Purpose:
  2016. //-----------------------------------------------------------------------------
  2017. CAI_ActBusyBehavior *CAI_ActBusyGoal::GetBusyBehaviorForNPC( const char *pszActorName, CBaseEntity *pActivator, CBaseEntity *pCaller, const char *sInputName )
  2018. {
  2019. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, MAKE_STRING(pszActorName), NULL, pActivator, pCaller );
  2020. if ( !pEntity )
  2021. {
  2022. Msg("ai_goal_actbusy input %s fired targeting a non-existant entity (%s).\n", sInputName, pszActorName );
  2023. return NULL;
  2024. }
  2025. return GetBusyBehaviorForNPC( pEntity, sInputName );
  2026. }
  2027. //-----------------------------------------------------------------------------
  2028. // Purpose:
  2029. // Input : &inputdata -
  2030. //-----------------------------------------------------------------------------
  2031. void CAI_ActBusyGoal::EnableGoal( CAI_BaseNPC *pAI )
  2032. {
  2033. BaseClass::EnableGoal( pAI );
  2034. // Now use this actor to lookup the Behavior
  2035. CAI_ActBusyBehavior *pBehavior;
  2036. if ( pAI->GetBehavior( &pBehavior ) )
  2037. {
  2038. // Some NPCs may already be active due to a ForceActBusy input.
  2039. if ( !pBehavior->IsEnabled() )
  2040. {
  2041. pBehavior->Enable( this, m_flBusySearchRange, m_bVisibleOnly );
  2042. }
  2043. }
  2044. else
  2045. {
  2046. DevMsg( "ActBusy goal entity activated for an NPC (%s) that doesn't have the ActBusy behavior\n", pAI->GetDebugName() );
  2047. return;
  2048. }
  2049. }
  2050. //-----------------------------------------------------------------------------
  2051. // Purpose:
  2052. // Input : &inputdata -
  2053. //-----------------------------------------------------------------------------
  2054. void CAI_ActBusyGoal::InputActivate( inputdata_t &inputdata )
  2055. {
  2056. if ( ai_debug_actbusy.GetInt() == 4 )
  2057. {
  2058. Msg("ACTBUSY: Actbusy goal %s (%s) activated.\n", GetClassname(), GetDebugName() );
  2059. }
  2060. BaseClass::InputActivate( inputdata );
  2061. }
  2062. //-----------------------------------------------------------------------------
  2063. // Purpose:
  2064. // Input : &inputdata -
  2065. //-----------------------------------------------------------------------------
  2066. void CAI_ActBusyGoal::InputDeactivate( inputdata_t &inputdata )
  2067. {
  2068. if ( ai_debug_actbusy.GetInt() == 4 )
  2069. {
  2070. Msg("ACTBUSY: Actbusy goal %s (%s) disabled.\n", GetClassname(), GetDebugName() );
  2071. }
  2072. BaseClass::InputDeactivate( inputdata );
  2073. for( int i = 0 ; i < NumActors() ; i++ )
  2074. {
  2075. CAI_BaseNPC *pActor = GetActor( i );
  2076. if ( pActor )
  2077. {
  2078. // Now use this actor to lookup the Behavior
  2079. CAI_ActBusyBehavior *pBehavior;
  2080. if ( pActor->GetBehavior( &pBehavior ) )
  2081. {
  2082. pBehavior->Disable();
  2083. }
  2084. else
  2085. {
  2086. DevMsg( "ActBusy goal entity deactivated for an NPC that doesn't have the ActBusy behavior\n" );
  2087. return;
  2088. }
  2089. }
  2090. }
  2091. }
  2092. //-----------------------------------------------------------------------------
  2093. // Purpose:
  2094. //-----------------------------------------------------------------------------
  2095. void CAI_ActBusyGoal::InputSetBusySearchRange( inputdata_t &inputdata )
  2096. {
  2097. m_flBusySearchRange = inputdata.value.Float();
  2098. for( int i = 0 ; i < NumActors() ; i++ )
  2099. {
  2100. CAI_BaseNPC *pActor = GetActor( i );
  2101. if ( pActor )
  2102. {
  2103. // Now use this actor to lookup the Behavior
  2104. CAI_ActBusyBehavior *pBehavior;
  2105. if ( pActor->GetBehavior( &pBehavior ) )
  2106. {
  2107. pBehavior->SetBusySearchRange( m_flBusySearchRange );
  2108. }
  2109. }
  2110. }
  2111. }
  2112. //-----------------------------------------------------------------------------
  2113. // Purpose:
  2114. //-----------------------------------------------------------------------------
  2115. void CAI_ActBusyGoal::InputForceNPCToActBusy( inputdata_t &inputdata )
  2116. {
  2117. char parseString[255];
  2118. Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString));
  2119. CAI_Hint *pHintNode = NULL;
  2120. float flMaxTime = NO_MAX_TIME;
  2121. bool bTeleport = false;
  2122. bool bUseNearestBusy = false;
  2123. CBaseEntity *pSeeEntity = NULL;
  2124. // Get NPC name
  2125. char *pszParam = strtok(parseString," ");
  2126. CAI_ActBusyBehavior *pBehavior = GetBusyBehaviorForNPC( pszParam, inputdata.pActivator, inputdata.pCaller, "InputForceNPCToActBusy" );
  2127. if ( !pBehavior )
  2128. return;
  2129. // Wrapped this bugfix so that it doesn't break HL2.
  2130. bool bEpisodicBugFix = hl2_episodic.GetBool();
  2131. // Do we have a specified node too?
  2132. pszParam = strtok(NULL," ");
  2133. if ( pszParam )
  2134. {
  2135. // Find the specified hintnode
  2136. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, pszParam, NULL, inputdata.pActivator, inputdata.pCaller );
  2137. if ( pEntity )
  2138. {
  2139. pHintNode = dynamic_cast<CAI_Hint*>(pEntity);
  2140. if ( !pHintNode )
  2141. {
  2142. Msg("ai_goal_actbusy input ForceNPCToActBusy fired targeting an entity that isn't a hintnode.\n");
  2143. return;
  2144. }
  2145. if ( bEpisodicBugFix )
  2146. {
  2147. pszParam = strtok(NULL," ");
  2148. }
  2149. }
  2150. }
  2151. Activity activity = ACT_INVALID;
  2152. if ( !bEpisodicBugFix )
  2153. {
  2154. pszParam = strtok(NULL," ");
  2155. }
  2156. while ( pszParam )
  2157. {
  2158. // Teleport?
  2159. if ( !Q_strncmp( pszParam, "teleport", 8 ) )
  2160. {
  2161. bTeleport = true;
  2162. }
  2163. else if ( !Q_strncmp( pszParam, "nearest", 8 ) )
  2164. {
  2165. bUseNearestBusy = true;
  2166. }
  2167. else if ( !Q_strncmp( pszParam, "see:", 4 ) )
  2168. {
  2169. pSeeEntity = gEntList.FindEntityByName( NULL, pszParam+4 );
  2170. }
  2171. else if ( pszParam[0] == '$' )
  2172. {
  2173. // $ signs prepend custom movement sequences / activities
  2174. const char *pAnimName = pszParam+1;
  2175. // Try and resolve it as an activity name
  2176. activity = (Activity)ActivityList_IndexForName( pAnimName );
  2177. if ( activity == ACT_INVALID )
  2178. {
  2179. // Try it as sequence name
  2180. pBehavior->GetOuter()->m_iszSceneCustomMoveSeq = AllocPooledString( pAnimName );
  2181. activity = ACT_SCRIPT_CUSTOM_MOVE;
  2182. }
  2183. }
  2184. else
  2185. {
  2186. // Do we have a specified time?
  2187. flMaxTime = atof( pszParam );
  2188. }
  2189. pszParam = strtok(NULL," ");
  2190. }
  2191. if ( ai_debug_actbusy.GetInt() == 4 )
  2192. {
  2193. Msg("ACTBUSY: Actbusy goal %s (%s) ForceNPCToActBusy input with data: %s.\n", GetClassname(), GetDebugName(), parseString );
  2194. }
  2195. // Tell the NPC to immediately act busy
  2196. pBehavior->SetBusySearchRange( m_flBusySearchRange );
  2197. pBehavior->ForceActBusy( this, pHintNode, flMaxTime, m_bVisibleOnly, bTeleport, bUseNearestBusy, pSeeEntity, activity );
  2198. }
  2199. //-----------------------------------------------------------------------------
  2200. // Purpose: Force the passed in NPC to actbusy
  2201. //-----------------------------------------------------------------------------
  2202. void CAI_ActBusyGoal::InputForceThisNPCToActBusy( inputdata_t &inputdata )
  2203. {
  2204. CAI_ActBusyBehavior *pBehavior = GetBusyBehaviorForNPC( inputdata.value.Entity(), "InputForceThisNPCToActBusy" );
  2205. if ( !pBehavior )
  2206. return;
  2207. // Tell the NPC to immediately act busy
  2208. pBehavior->SetBusySearchRange( m_flBusySearchRange );
  2209. pBehavior->ForceActBusy( this );
  2210. }
  2211. //-----------------------------------------------------------------------------
  2212. // Purpose: Force the passed in NPC to walk to a point and vanish
  2213. //-----------------------------------------------------------------------------
  2214. void CAI_ActBusyGoal::InputForceThisNPCToLeave( inputdata_t &inputdata )
  2215. {
  2216. CAI_ActBusyBehavior *pBehavior = GetBusyBehaviorForNPC( inputdata.value.Entity(), "InputForceThisNPCToLeave" );
  2217. if ( !pBehavior )
  2218. return;
  2219. // Tell the NPC to find a leave point and move to it
  2220. pBehavior->SetBusySearchRange( m_flBusySearchRange );
  2221. pBehavior->ForceActBusyLeave();
  2222. }
  2223. //-----------------------------------------------------------------------------
  2224. // Purpose:
  2225. // Input : *pNPC -
  2226. //-----------------------------------------------------------------------------
  2227. void CAI_ActBusyGoal::NPCMovingToBusy( CAI_BaseNPC *pNPC )
  2228. {
  2229. }
  2230. //-----------------------------------------------------------------------------
  2231. // Purpose:
  2232. // Input : *pNPC -
  2233. //-----------------------------------------------------------------------------
  2234. void CAI_ActBusyGoal::NPCStartedBusy( CAI_BaseNPC *pNPC )
  2235. {
  2236. m_OnNPCStartedBusy.Set( pNPC, pNPC, this );
  2237. }
  2238. //-----------------------------------------------------------------------------
  2239. // Purpose:
  2240. //-----------------------------------------------------------------------------
  2241. void CAI_ActBusyGoal::NPCStartedLeavingBusy( CAI_BaseNPC *pNPC )
  2242. {
  2243. }
  2244. //-----------------------------------------------------------------------------
  2245. // Purpose:
  2246. // Input : *pNPC -
  2247. //-----------------------------------------------------------------------------
  2248. void CAI_ActBusyGoal::NPCAbortedMoveTo( CAI_BaseNPC *pNPC )
  2249. {
  2250. }
  2251. //-----------------------------------------------------------------------------
  2252. // Purpose:
  2253. // Input : *pNPC -
  2254. //-----------------------------------------------------------------------------
  2255. void CAI_ActBusyGoal::NPCFinishedBusy( CAI_BaseNPC *pNPC )
  2256. {
  2257. m_OnNPCFinishedBusy.Set( pNPC, pNPC, this );
  2258. }
  2259. //-----------------------------------------------------------------------------
  2260. // Purpose:
  2261. // Input : *pNPC -
  2262. //-----------------------------------------------------------------------------
  2263. void CAI_ActBusyGoal::NPCLeft( CAI_BaseNPC *pNPC )
  2264. {
  2265. m_OnNPCLeft.Set( pNPC, pNPC, this );
  2266. }
  2267. //-----------------------------------------------------------------------------
  2268. //-----------------------------------------------------------------------------
  2269. void CAI_ActBusyGoal::NPCLostSeeEntity( CAI_BaseNPC *pNPC )
  2270. {
  2271. m_OnNPCLostSeeEntity.Set( pNPC, pNPC, this );
  2272. }
  2273. //-----------------------------------------------------------------------------
  2274. //-----------------------------------------------------------------------------
  2275. void CAI_ActBusyGoal::NPCSeeEnemy( CAI_BaseNPC *pNPC )
  2276. {
  2277. m_OnNPCSeeEnemy.Set( pNPC, pNPC, this );
  2278. }
  2279. //==========================================================================================================
  2280. // ACT BUSY QUEUE
  2281. //==========================================================================================================
  2282. //-----------------------------------------------------------------------------
  2283. // Purpose: A level tool to control the actbusy behavior to create NPC queues
  2284. //-----------------------------------------------------------------------------
  2285. LINK_ENTITY_TO_CLASS( ai_goal_actbusy_queue, CAI_ActBusyQueueGoal );
  2286. BEGIN_DATADESC( CAI_ActBusyQueueGoal )
  2287. // Keys
  2288. DEFINE_FIELD( m_iCurrentQueueCount, FIELD_INTEGER ),
  2289. DEFINE_ARRAY( m_hNodes, FIELD_EHANDLE, MAX_QUEUE_NODES ),
  2290. DEFINE_ARRAY( m_bPlayerBlockedNodes, FIELD_BOOLEAN, MAX_QUEUE_NODES ),
  2291. DEFINE_FIELD( m_hExitNode, FIELD_EHANDLE ),
  2292. DEFINE_FIELD( m_hExitingNPC, FIELD_EHANDLE ),
  2293. DEFINE_KEYFIELD( m_bForceReachFront, FIELD_BOOLEAN, "mustreachfront" ),
  2294. // DEFINE_ARRAY( m_iszNodes, FIELD_STRING, MAX_QUEUE_NODES ), // Silence Classcheck!
  2295. DEFINE_KEYFIELD( m_iszNodes[0], FIELD_STRING, "node01"),
  2296. DEFINE_KEYFIELD( m_iszNodes[1], FIELD_STRING, "node02"),
  2297. DEFINE_KEYFIELD( m_iszNodes[2], FIELD_STRING, "node03"),
  2298. DEFINE_KEYFIELD( m_iszNodes[3], FIELD_STRING, "node04"),
  2299. DEFINE_KEYFIELD( m_iszNodes[4], FIELD_STRING, "node05"),
  2300. DEFINE_KEYFIELD( m_iszNodes[5], FIELD_STRING, "node06"),
  2301. DEFINE_KEYFIELD( m_iszNodes[6], FIELD_STRING, "node07"),
  2302. DEFINE_KEYFIELD( m_iszNodes[7], FIELD_STRING, "node08"),
  2303. DEFINE_KEYFIELD( m_iszNodes[8], FIELD_STRING, "node09"),
  2304. DEFINE_KEYFIELD( m_iszNodes[9], FIELD_STRING, "node10"),
  2305. DEFINE_KEYFIELD( m_iszNodes[10], FIELD_STRING, "node11"),
  2306. DEFINE_KEYFIELD( m_iszNodes[11], FIELD_STRING, "node12"),
  2307. DEFINE_KEYFIELD( m_iszNodes[12], FIELD_STRING, "node13"),
  2308. DEFINE_KEYFIELD( m_iszNodes[13], FIELD_STRING, "node14"),
  2309. DEFINE_KEYFIELD( m_iszNodes[14], FIELD_STRING, "node15"),
  2310. DEFINE_KEYFIELD( m_iszNodes[15], FIELD_STRING, "node16"),
  2311. DEFINE_KEYFIELD( m_iszNodes[16], FIELD_STRING, "node17"),
  2312. DEFINE_KEYFIELD( m_iszNodes[17], FIELD_STRING, "node18"),
  2313. DEFINE_KEYFIELD( m_iszNodes[18], FIELD_STRING, "node19"),
  2314. DEFINE_KEYFIELD( m_iszNodes[19], FIELD_STRING, "node20"),
  2315. DEFINE_KEYFIELD( m_iszExitNode, FIELD_STRING, "node_exit"),
  2316. // Inputs
  2317. DEFINE_INPUTFUNC( FIELD_INTEGER, "PlayerStartedBlocking", InputPlayerStartedBlocking ),
  2318. DEFINE_INPUTFUNC( FIELD_INTEGER, "PlayerStoppedBlocking", InputPlayerStoppedBlocking ),
  2319. DEFINE_INPUTFUNC( FIELD_VOID, "MoveQueueUp", InputMoveQueueUp ),
  2320. // Outputs
  2321. DEFINE_OUTPUT( m_OnQueueMoved, "OnQueueMoved" ),
  2322. DEFINE_OUTPUT( m_OnNPCLeftQueue, "OnNPCLeftQueue" ),
  2323. DEFINE_OUTPUT( m_OnNPCStartedLeavingQueue, "OnNPCStartedLeavingQueue" ),
  2324. DEFINE_THINKFUNC( QueueThink ),
  2325. DEFINE_THINKFUNC( MoveQueueUpThink ),
  2326. END_DATADESC()
  2327. #define QUEUE_THINK_CONTEXT "ActBusyQueueThinkContext"
  2328. #define QUEUE_MOVEUP_THINK_CONTEXT "ActBusyQueueMoveUpThinkContext"
  2329. //-----------------------------------------------------------------------------
  2330. // Purpose:
  2331. //-----------------------------------------------------------------------------
  2332. void CAI_ActBusyQueueGoal::Spawn( void )
  2333. {
  2334. BaseClass::Spawn();
  2335. RegisterThinkContext( QUEUE_MOVEUP_THINK_CONTEXT );
  2336. }
  2337. //-----------------------------------------------------------------------------
  2338. // Purpose:
  2339. //-----------------------------------------------------------------------------
  2340. void CAI_ActBusyQueueGoal::DrawDebugGeometryOverlays( void )
  2341. {
  2342. BaseClass::DrawDebugGeometryOverlays();
  2343. // Debug for reservers
  2344. for ( int i = 0; i < MAX_QUEUE_NODES; i++ )
  2345. {
  2346. if ( !m_hNodes[i] )
  2347. continue;
  2348. if ( m_bPlayerBlockedNodes[i] )
  2349. {
  2350. NDebugOverlay::Box( m_hNodes[i]->GetAbsOrigin(), -Vector(5,5,5), Vector(5,5,5), 255, 0, 0, 0, 0.1 );
  2351. }
  2352. else
  2353. {
  2354. NDebugOverlay::Box( m_hNodes[i]->GetAbsOrigin(), -Vector(5,5,5), Vector(5,5,5), 255, 255, 255, 0, 0.1 );
  2355. }
  2356. }
  2357. }
  2358. //-----------------------------------------------------------------------------
  2359. // Purpose:
  2360. //-----------------------------------------------------------------------------
  2361. void CAI_ActBusyQueueGoal::InputActivate( inputdata_t &inputdata )
  2362. {
  2363. if ( !IsActive() )
  2364. {
  2365. // Find all our nodes
  2366. for ( int i = 0; i < MAX_QUEUE_NODES; i++ )
  2367. {
  2368. if ( m_iszNodes[i] == NULL_STRING )
  2369. {
  2370. m_hNodes[i] = NULL;
  2371. continue;
  2372. }
  2373. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_iszNodes[i] );
  2374. if ( !pEntity )
  2375. {
  2376. Warning( "Unable to find ai_goal_actbusy_queue %s's node %d: %s\n", STRING(GetEntityName()), i, STRING(m_iszNodes[i]) );
  2377. UTIL_Remove( this );
  2378. return;
  2379. }
  2380. m_hNodes[i] = dynamic_cast<CAI_Hint*>(pEntity);
  2381. if ( !m_hNodes[i] )
  2382. {
  2383. Warning( "ai_goal_actbusy_queue %s's node %d: '%s' is not an ai_hint.\n", STRING(GetEntityName()), i, STRING(m_iszNodes[i]) );
  2384. UTIL_Remove( this );
  2385. return;
  2386. }
  2387. // Disable all but the first node
  2388. if ( i == 0 )
  2389. {
  2390. m_hNodes[i]->SetDisabled( false );
  2391. }
  2392. else
  2393. {
  2394. m_hNodes[i]->SetDisabled( true );
  2395. }
  2396. }
  2397. // Find the exit node
  2398. m_hExitNode = gEntList.FindEntityByName( NULL, m_iszExitNode );
  2399. if ( !m_hExitNode )
  2400. {
  2401. Warning( "Unable to find ai_goal_actbusy_queue %s's exit node: %s\n", STRING(GetEntityName()), STRING(m_iszExitNode) );
  2402. UTIL_Remove( this );
  2403. return;
  2404. }
  2405. RecalculateQueueCount();
  2406. SetContextThink( &CAI_ActBusyQueueGoal::QueueThink, gpGlobals->curtime + 5, QUEUE_THINK_CONTEXT );
  2407. }
  2408. BaseClass::InputActivate( inputdata );
  2409. }
  2410. //-----------------------------------------------------------------------------
  2411. // Purpose:
  2412. // Input : iCount -
  2413. //-----------------------------------------------------------------------------
  2414. void CAI_ActBusyQueueGoal::RecalculateQueueCount( void )
  2415. {
  2416. // First, find the highest unused node in the queue
  2417. int iCount = 0;
  2418. for ( int i = 0; i < MAX_QUEUE_NODES; i++ )
  2419. {
  2420. if ( NodeIsOccupied(i) || m_bPlayerBlockedNodes[i] )
  2421. {
  2422. iCount = i+1;
  2423. }
  2424. }
  2425. //Msg("Count: %d (OLD %d)\n", iCount, m_iCurrentQueueCount );
  2426. // Queue hasn't changed?
  2427. if ( iCount == m_iCurrentQueueCount )
  2428. return;
  2429. for ( int i = 0; i < MAX_QUEUE_NODES; i++ )
  2430. {
  2431. if ( m_hNodes[i] )
  2432. {
  2433. // Disable nodes beyond 1 past the end of the queue
  2434. if ( i > iCount )
  2435. {
  2436. m_hNodes[i]->SetDisabled( true );
  2437. }
  2438. else
  2439. {
  2440. m_hNodes[i]->SetDisabled( false );
  2441. // To prevent NPCs outside the queue moving directly to nodes within the queue, only
  2442. // have the entry node be a valid actbusy node.
  2443. if ( i == iCount )
  2444. {
  2445. m_hNodes[i]->SetHintType( HINT_WORLD_WORK_POSITION );
  2446. }
  2447. else
  2448. {
  2449. m_hNodes[i]->SetHintType( HINT_NONE );
  2450. }
  2451. }
  2452. }
  2453. }
  2454. m_iCurrentQueueCount = iCount;
  2455. m_OnQueueMoved.Set( m_iCurrentQueueCount, this, this);
  2456. }
  2457. //-----------------------------------------------------------------------------
  2458. // Purpose:
  2459. // Input : &inputdata -
  2460. //-----------------------------------------------------------------------------
  2461. void CAI_ActBusyQueueGoal::InputPlayerStartedBlocking( inputdata_t &inputdata )
  2462. {
  2463. int iNode = inputdata.value.Int() - 1;
  2464. Assert( iNode >= 0 && iNode < MAX_QUEUE_NODES );
  2465. m_bPlayerBlockedNodes[iNode] = true;
  2466. /*
  2467. // First, find all NPCs heading to points in front of the player's blocked spot
  2468. for ( int i = 0; i < iNode; i++ )
  2469. {
  2470. CAI_BaseNPC *pNPC = GetNPCOnNode(i);
  2471. if ( !pNPC )
  2472. continue;
  2473. CAI_ActBusyBehavior *pBehavior = GetQueueBehaviorForNPC( pNPC );
  2474. if ( pBehavior->IsMovingToBusy() )
  2475. {
  2476. // We may be ahead of the player in the queue, which means we can safely
  2477. // be left alone to reach the node. Make sure we're not closer to it than the player is
  2478. float flPlayerDistToNode = (inputdata.pActivator->GetAbsOrigin() - m_hNodes[i]->GetAbsOrigin()).LengthSqr();
  2479. if ( (pNPC->GetAbsOrigin() - m_hNodes[i]->GetAbsOrigin()).LengthSqr() < flPlayerDistToNode )
  2480. continue;
  2481. // We're an NPC heading to a node past the player, and yet the player's in our way.
  2482. pBehavior->StopBusying();
  2483. }
  2484. }
  2485. */
  2486. // If an NPC was heading towards this node, tell him to go elsewhere
  2487. CAI_BaseNPC *pNPC = GetNPCOnNode(iNode);
  2488. PushNPCBackInQueue( pNPC, iNode );
  2489. RecalculateQueueCount();
  2490. }
  2491. //-----------------------------------------------------------------------------
  2492. // Purpose: Find a node back in the queue to move to, and push all NPCs beyond that backwards
  2493. //-----------------------------------------------------------------------------
  2494. void CAI_ActBusyQueueGoal::PushNPCBackInQueue( CAI_BaseNPC *pNPC, int iStartingNode )
  2495. {
  2496. // Push this guy back, and tell everyone behind him to move back too, until we find a gap
  2497. while ( pNPC )
  2498. {
  2499. CAI_ActBusyBehavior *pBehavior = GetQueueBehaviorForNPC( pNPC );
  2500. pBehavior->StopBusying();
  2501. // Find any node farther back in the queue that isn't player blocked
  2502. for ( int iNext = iStartingNode+1; iNext < MAX_QUEUE_NODES; iNext++ )
  2503. {
  2504. if ( !m_bPlayerBlockedNodes[iNext] )
  2505. {
  2506. // Kick off any NPCs on the node we're about to steal
  2507. CAI_BaseNPC *pTargetNPC = GetNPCOnNode(iNext);
  2508. if ( pTargetNPC )
  2509. {
  2510. CAI_ActBusyBehavior *pTargetBehavior = GetQueueBehaviorForNPC( pTargetNPC );
  2511. pTargetBehavior->StopBusying();
  2512. }
  2513. // Force the NPC to move up to the empty slot
  2514. pBehavior->ForceActBusy( this, m_hNodes[iNext] );
  2515. // Now look for a spot for the npc who's spot we've just stolen
  2516. pNPC = pTargetNPC;
  2517. iStartingNode = iNext;
  2518. break;
  2519. }
  2520. }
  2521. }
  2522. }
  2523. //-----------------------------------------------------------------------------
  2524. // Purpose:
  2525. // Input : &inputdata -
  2526. //-----------------------------------------------------------------------------
  2527. void CAI_ActBusyQueueGoal::InputPlayerStoppedBlocking( inputdata_t &inputdata )
  2528. {
  2529. int iNode = inputdata.value.Int() - 1;
  2530. Assert( iNode >= 0 && iNode < MAX_QUEUE_NODES );
  2531. m_bPlayerBlockedNodes[iNode] = false;
  2532. RecalculateQueueCount();
  2533. MoveQueueUp();
  2534. }
  2535. //-----------------------------------------------------------------------------
  2536. // Purpose:
  2537. // Input : &inputdata -
  2538. //-----------------------------------------------------------------------------
  2539. void CAI_ActBusyQueueGoal::InputMoveQueueUp( inputdata_t &inputdata )
  2540. {
  2541. // Find the first NPC in the queue
  2542. CAI_BaseNPC *pNPC = NULL;
  2543. for ( int i = 0; i < MAX_QUEUE_NODES; i++ )
  2544. {
  2545. pNPC = GetNPCOnNode(i);
  2546. if ( pNPC )
  2547. {
  2548. CAI_ActBusyBehavior *pBehavior = GetQueueBehaviorForNPC( pNPC );
  2549. // If we're still en-route, we're only allowed to leave if the queue
  2550. // is allowed to send NPCs away that haven't reached the front.
  2551. if ( !pBehavior->IsMovingToBusy() || !m_bForceReachFront )
  2552. break;
  2553. pNPC = NULL;
  2554. }
  2555. // If queue members have to reach the front of the queue,
  2556. // break after trying the first node.
  2557. if ( m_bForceReachFront )
  2558. break;
  2559. }
  2560. // Did we find an NPC?
  2561. if ( pNPC )
  2562. {
  2563. // Make them leave the actbusy
  2564. CAI_ActBusyBehavior *pBehavior = GetQueueBehaviorForNPC( pNPC );
  2565. pBehavior->Disable();
  2566. m_hExitingNPC = pNPC;
  2567. }
  2568. }
  2569. //-----------------------------------------------------------------------------
  2570. // Purpose:
  2571. // Input : *pNPC -
  2572. //-----------------------------------------------------------------------------
  2573. void CAI_ActBusyQueueGoal::NPCMovingToBusy( CAI_BaseNPC *pNPC )
  2574. {
  2575. BaseClass::NPCMovingToBusy( pNPC );
  2576. RecalculateQueueCount();
  2577. }
  2578. //-----------------------------------------------------------------------------
  2579. // Purpose:
  2580. // Input : *pNPC -
  2581. //-----------------------------------------------------------------------------
  2582. void CAI_ActBusyQueueGoal::NPCStartedBusy( CAI_BaseNPC *pNPC )
  2583. {
  2584. BaseClass::NPCStartedBusy( pNPC );
  2585. MoveQueueUp();
  2586. }
  2587. //-----------------------------------------------------------------------------
  2588. // Purpose: Start a short timer that'll clean up holes in the queue
  2589. //-----------------------------------------------------------------------------
  2590. void CAI_ActBusyQueueGoal::MoveQueueUp( void )
  2591. {
  2592. // Find the node the NPC has arrived at, and tell the guy behind him to move forward
  2593. if ( GetNextThink( QUEUE_MOVEUP_THINK_CONTEXT ) < gpGlobals->curtime )
  2594. {
  2595. float flTime = gpGlobals->curtime + RandomFloat( 0.3, 0.5 );
  2596. SetContextThink( &CAI_ActBusyQueueGoal::MoveQueueUpThink, flTime, QUEUE_MOVEUP_THINK_CONTEXT );
  2597. }
  2598. }
  2599. //-----------------------------------------------------------------------------
  2600. // Purpose:
  2601. //-----------------------------------------------------------------------------
  2602. void CAI_ActBusyQueueGoal::MoveQueueUpThink( void )
  2603. {
  2604. // Find empty holes in the queue, and move NPCs past them forward
  2605. for ( int iEmptyNode = 0; iEmptyNode < (MAX_QUEUE_NODES-1); iEmptyNode++ )
  2606. {
  2607. if ( !NodeIsOccupied(iEmptyNode) && !m_bPlayerBlockedNodes[iEmptyNode] )
  2608. {
  2609. // Look for NPCs farther down the queue, but not on the other side of a player
  2610. for ( int iNext = iEmptyNode+1; iNext < MAX_QUEUE_NODES; iNext++ )
  2611. {
  2612. // Is the player blocking this node? If so, we're done
  2613. if ( m_bPlayerBlockedNodes[iNext] )
  2614. break;
  2615. CAI_BaseNPC *pNPC = GetNPCOnNode(iNext);
  2616. if ( pNPC )
  2617. {
  2618. CAI_ActBusyBehavior *pBehavior = GetQueueBehaviorForNPC( pNPC );
  2619. // Force the NPC to move up to the empty slot
  2620. pBehavior->ForceActBusy( this, m_hNodes[iEmptyNode] );
  2621. break;
  2622. }
  2623. }
  2624. }
  2625. }
  2626. }
  2627. //-----------------------------------------------------------------------------
  2628. // Purpose:
  2629. // Input : *pNPC -
  2630. //-----------------------------------------------------------------------------
  2631. void CAI_ActBusyQueueGoal::NPCAbortedMoveTo( CAI_BaseNPC *pNPC )
  2632. {
  2633. BaseClass::NPCAbortedMoveTo( pNPC );
  2634. RemoveNPCFromQueue( pNPC );
  2635. }
  2636. //-----------------------------------------------------------------------------
  2637. // Purpose:
  2638. // Input : *pNPC -
  2639. //-----------------------------------------------------------------------------
  2640. void CAI_ActBusyQueueGoal::NPCFinishedBusy( CAI_BaseNPC *pNPC )
  2641. {
  2642. BaseClass::NPCFinishedBusy( pNPC );
  2643. // If this NPC was at the head of the line, move him to the exit node
  2644. if ( m_hExitingNPC == pNPC )
  2645. {
  2646. pNPC->ScheduledMoveToGoalEntity( SCHED_IDLE_WALK, m_hExitNode, ACT_WALK );
  2647. m_OnNPCLeftQueue.Set( pNPC, pNPC, this );
  2648. m_hExitingNPC = NULL;
  2649. }
  2650. RemoveNPCFromQueue( pNPC );
  2651. }
  2652. //-----------------------------------------------------------------------------
  2653. // Purpose:
  2654. // Input : *pNPC -
  2655. //-----------------------------------------------------------------------------
  2656. void CAI_ActBusyQueueGoal::NPCStartedLeavingBusy( CAI_BaseNPC *pNPC )
  2657. {
  2658. BaseClass::NPCStartedLeavingBusy( pNPC );
  2659. // If this NPC it at the head of the line, fire the output
  2660. if ( m_hExitingNPC == pNPC )
  2661. {
  2662. m_OnNPCStartedLeavingQueue.Set( pNPC, pNPC, this );
  2663. }
  2664. }
  2665. //-----------------------------------------------------------------------------
  2666. // Purpose:
  2667. //-----------------------------------------------------------------------------
  2668. void CAI_ActBusyQueueGoal::RemoveNPCFromQueue( CAI_BaseNPC *pNPC )
  2669. {
  2670. RecalculateQueueCount();
  2671. // Find the node the NPC was heading to, and tell the guy behind him to move forward
  2672. MoveQueueUp();
  2673. }
  2674. //-----------------------------------------------------------------------------
  2675. // Purpose: Move the first NPC out of the queue
  2676. //-----------------------------------------------------------------------------
  2677. void CAI_ActBusyQueueGoal::QueueThink( void )
  2678. {
  2679. if ( !GetNPCOnNode(0) )
  2680. {
  2681. MoveQueueUp();
  2682. }
  2683. SetContextThink( &CAI_ActBusyQueueGoal::QueueThink, gpGlobals->curtime + 5, QUEUE_THINK_CONTEXT );
  2684. }
  2685. //-----------------------------------------------------------------------------
  2686. // Purpose:
  2687. //-----------------------------------------------------------------------------
  2688. inline bool CAI_ActBusyQueueGoal::NodeIsOccupied( int i )
  2689. {
  2690. return ( m_hNodes[i] && !m_hNodes[i]->IsDisabled() && m_hNodes[i]->IsLocked() );
  2691. }
  2692. //-----------------------------------------------------------------------------
  2693. // Purpose:
  2694. // Input : iNode -
  2695. // Output : CAI_BaseNPC
  2696. //-----------------------------------------------------------------------------
  2697. CAI_BaseNPC *CAI_ActBusyQueueGoal::GetNPCOnNode( int iNode )
  2698. {
  2699. if ( !m_hNodes[iNode] )
  2700. return NULL;
  2701. return dynamic_cast<CAI_BaseNPC *>(m_hNodes[iNode]->User());
  2702. }
  2703. //-----------------------------------------------------------------------------
  2704. // Purpose:
  2705. // Input : iNode -
  2706. // Output : CAI_ActBusyBehavior
  2707. //-----------------------------------------------------------------------------
  2708. CAI_ActBusyBehavior *CAI_ActBusyQueueGoal::GetQueueBehaviorForNPC( CAI_BaseNPC *pNPC )
  2709. {
  2710. CAI_ActBusyBehavior *pBehavior;
  2711. pNPC->GetBehavior( &pBehavior );
  2712. Assert( pBehavior );
  2713. return pBehavior;
  2714. }