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.

1841 lines
53 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ai_behavior_assault.h"
  8. #include "ai_navigator.h"
  9. #include "ai_memory.h"
  10. #include "ai_squad.h"
  11. // memdbgon must be the last include file in a .cpp file!!!
  12. #include "tier0/memdbgon.h"
  13. ConVar ai_debug_assault("ai_debug_assault", "0");
  14. BEGIN_DATADESC( CRallyPoint )
  15. DEFINE_KEYFIELD( m_AssaultPointName, FIELD_STRING, "assaultpoint" ),
  16. DEFINE_KEYFIELD( m_RallySequenceName, FIELD_STRING, "rallysequence" ),
  17. DEFINE_KEYFIELD( m_flAssaultDelay, FIELD_FLOAT, "assaultdelay" ),
  18. DEFINE_KEYFIELD( m_iPriority, FIELD_INTEGER, "priority" ),
  19. DEFINE_KEYFIELD( m_iStrictness, FIELD_INTEGER, "strict" ),
  20. DEFINE_KEYFIELD( m_bForceCrouch, FIELD_BOOLEAN, "forcecrouch" ),
  21. DEFINE_KEYFIELD( m_bIsUrgent, FIELD_BOOLEAN, "urgent" ),
  22. DEFINE_FIELD( m_hLockedBy, FIELD_EHANDLE ),
  23. DEFINE_FIELD( m_sExclusivity, FIELD_SHORT ),
  24. DEFINE_OUTPUT( m_OnArrival, "OnArrival" ),
  25. END_DATADESC();
  26. //---------------------------------------------------------
  27. // Purpose: Communicate exclusivity
  28. //---------------------------------------------------------
  29. int CRallyPoint::DrawDebugTextOverlays()
  30. {
  31. int offset;
  32. offset = BaseClass::DrawDebugTextOverlays();
  33. if ( (m_debugOverlays & OVERLAY_TEXT_BIT) )
  34. {
  35. switch( m_sExclusivity )
  36. {
  37. case RALLY_EXCLUSIVE_NOT_EVALUATED:
  38. EntityText( offset, "Exclusive: Not Evaluated", 0 );
  39. break;
  40. case RALLY_EXCLUSIVE_YES:
  41. EntityText( offset, "Exclusive: YES", 0 );
  42. break;
  43. case RALLY_EXCLUSIVE_NO:
  44. EntityText( offset, "Exclusive: NO", 0 );
  45. break;
  46. default:
  47. EntityText( offset, "Exclusive: !?INVALID?!", 0 );
  48. break;
  49. }
  50. offset++;
  51. if( IsLocked() )
  52. EntityText( offset, "LOCKED.", 0 );
  53. else
  54. EntityText( offset, "Available", 0 );
  55. offset++;
  56. }
  57. return offset;
  58. }
  59. //---------------------------------------------------------
  60. // Purpose: If a rally point is 'exclusive' that means that
  61. // anytime an NPC is anywhere on the assault chain that
  62. // begins with this rally point, the assault is considered
  63. // 'exclusive' and no other NPCs will be allowed to use it
  64. // until the current NPC clears the entire assault chain
  65. // or dies.
  66. //
  67. // If exclusivity has not been determined the first time
  68. // this function is called, it will be computed and cached
  69. //---------------------------------------------------------
  70. bool CRallyPoint::IsExclusive()
  71. {
  72. #ifndef HL2_EPISODIC // IF NOT EPISODIC
  73. // This 'exclusivity' concept is new to EP2. We're only willing to
  74. // risk causing problems in EP1, so emulate the old behavior if
  75. // we are not EPISODIC. We must do this by setting m_sExclusivity
  76. // so that ent_text will properly report the state.
  77. m_sExclusivity = RALLY_EXCLUSIVE_NO;
  78. #else
  79. if( m_sExclusivity == RALLY_EXCLUSIVE_NOT_EVALUATED )
  80. {
  81. // We need to evaluate! Walk the chain of assault points
  82. // and if *ANY* assault points on this assault chain
  83. // are set to Never Time Out then set this rally point to
  84. // be exclusive to stop other NPC's walking down the chain
  85. // and ending up clumped up at the infinite rally point.
  86. CAssaultPoint *pAssaultEnt = (CAssaultPoint *)gEntList.FindEntityByName( NULL, m_AssaultPointName );
  87. if( !pAssaultEnt )
  88. {
  89. // Well, this is awkward. Leave it up to other assault code to tattle on the missing assault point.
  90. // We will just assume this assault is not exclusive.
  91. m_sExclusivity = RALLY_EXCLUSIVE_NO;
  92. return false;
  93. }
  94. // Otherwise, we start by assuming this assault chain is not exclusive.
  95. m_sExclusivity = RALLY_EXCLUSIVE_NO;
  96. if( pAssaultEnt )
  97. {
  98. CAssaultPoint *pFirstAssaultEnt = pAssaultEnt; //some assault chains are circularly linked
  99. do
  100. {
  101. if( pAssaultEnt->m_bNeverTimeout )
  102. {
  103. // We found a never timeout assault point! That makes this whole chain exclusive.
  104. m_sExclusivity = RALLY_EXCLUSIVE_YES;
  105. break;
  106. }
  107. pAssaultEnt = (CAssaultPoint *)gEntList.FindEntityByName( NULL, pAssaultEnt->m_NextAssaultPointName );
  108. } while( (pAssaultEnt != NULL) && (pAssaultEnt != pFirstAssaultEnt) );
  109. }
  110. }
  111. #endif// HL2_EPISODIC
  112. return (m_sExclusivity == RALLY_EXCLUSIVE_YES);
  113. }
  114. BEGIN_DATADESC( CAssaultPoint )
  115. DEFINE_KEYFIELD( m_AssaultHintGroup, FIELD_STRING, "assaultgroup" ),
  116. DEFINE_KEYFIELD( m_NextAssaultPointName, FIELD_STRING, "nextassaultpoint" ),
  117. DEFINE_KEYFIELD( m_flAssaultTimeout, FIELD_FLOAT, "assaulttimeout" ),
  118. DEFINE_KEYFIELD( m_bClearOnContact, FIELD_BOOLEAN, "clearoncontact" ),
  119. DEFINE_KEYFIELD( m_bAllowDiversion, FIELD_BOOLEAN, "allowdiversion" ),
  120. DEFINE_KEYFIELD( m_flAllowDiversionRadius, FIELD_FLOAT, "allowdiversionradius" ),
  121. DEFINE_KEYFIELD( m_bNeverTimeout, FIELD_BOOLEAN, "nevertimeout" ),
  122. DEFINE_KEYFIELD( m_iStrictness, FIELD_INTEGER, "strict" ),
  123. DEFINE_KEYFIELD( m_bForceCrouch, FIELD_BOOLEAN, "forcecrouch" ),
  124. DEFINE_KEYFIELD( m_bIsUrgent, FIELD_BOOLEAN, "urgent" ),
  125. DEFINE_FIELD( m_bInputForcedClear, FIELD_BOOLEAN ),
  126. DEFINE_KEYFIELD( m_flAssaultPointTolerance, FIELD_FLOAT, "assaulttolerance" ),
  127. DEFINE_FIELD( m_flTimeLastUsed, FIELD_TIME ),
  128. // Inputs
  129. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetClearOnContact", InputSetClearOnContact ),
  130. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowDiversion", InputSetAllowDiversion ),
  131. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetForceClear", InputSetForceClear ),
  132. // Outputs
  133. DEFINE_OUTPUT( m_OnArrival, "OnArrival" ),
  134. DEFINE_OUTPUT( m_OnAssaultClear, "OnAssaultClear" ),
  135. END_DATADESC();
  136. LINK_ENTITY_TO_CLASS( assault_rallypoint, CRallyPoint ); // just a copy of info_target for now
  137. LINK_ENTITY_TO_CLASS( assault_assaultpoint, CAssaultPoint ); // has its own class because it needs the entity I/O
  138. BEGIN_DATADESC( CAI_AssaultBehavior )
  139. DEFINE_FIELD( m_hAssaultPoint, FIELD_EHANDLE ),
  140. DEFINE_FIELD( m_hRallyPoint, FIELD_EHANDLE ),
  141. DEFINE_FIELD( m_AssaultCue, FIELD_INTEGER ),
  142. DEFINE_FIELD( m_ReceivedAssaultCue, FIELD_INTEGER ),
  143. DEFINE_FIELD( m_bHitRallyPoint, FIELD_BOOLEAN ),
  144. DEFINE_FIELD( m_bHitAssaultPoint, FIELD_BOOLEAN ),
  145. DEFINE_FIELD( m_bDiverting, FIELD_BOOLEAN ),
  146. DEFINE_FIELD( m_flLastSawAnEnemyAt, FIELD_FLOAT ),
  147. DEFINE_FIELD( m_flTimeDeferScheduleSelection, FIELD_TIME ),
  148. DEFINE_FIELD( m_AssaultPointName, FIELD_STRING )
  149. END_DATADESC();
  150. //-----------------------------------------------------------------------------
  151. // Purpose:
  152. //-----------------------------------------------------------------------------
  153. bool CAI_AssaultBehavior::CanRunAScriptedNPCInteraction( bool bForced )
  154. {
  155. if ( m_AssaultCue == CUE_NO_ASSAULT )
  156. {
  157. // It's OK with the assault behavior, so leave it up to the base class.
  158. return BaseClass::CanRunAScriptedNPCInteraction( bForced );
  159. }
  160. return false;
  161. }
  162. //-----------------------------------------------------------------------------
  163. // Purpose:
  164. //-----------------------------------------------------------------------------
  165. CAI_AssaultBehavior::CAI_AssaultBehavior()
  166. {
  167. m_AssaultCue = CUE_NO_ASSAULT;
  168. }
  169. //-----------------------------------------------------------------------------
  170. // Purpose: Draw any text overlays
  171. // Input : Previous text offset from the top
  172. // Output : Current text offset from the top
  173. //-----------------------------------------------------------------------------
  174. int CAI_AssaultBehavior::DrawDebugTextOverlays( int text_offset )
  175. {
  176. char tempstr[ 512 ];
  177. int offset;
  178. offset = BaseClass::DrawDebugTextOverlays( text_offset );
  179. if ( GetOuter()->m_debugOverlays & OVERLAY_TEXT_BIT )
  180. {
  181. Q_snprintf( tempstr, sizeof(tempstr), "Assault Point: %s %s", STRING( m_AssaultPointName ), VecToString( m_hAssaultPoint->GetAbsOrigin() ) );
  182. GetOuter()->EntityText( offset, tempstr, 0 );
  183. offset++;
  184. }
  185. return offset;
  186. }
  187. //-----------------------------------------------------------------------------
  188. // Purpose:
  189. // Input : cue -
  190. //-----------------------------------------------------------------------------
  191. void CAI_AssaultBehavior::ReceiveAssaultCue( AssaultCue_t cue )
  192. {
  193. if ( GetOuter() )
  194. GetOuter()->ForceDecisionThink();
  195. m_ReceivedAssaultCue = cue;
  196. SetCondition( COND_PROVOKED );
  197. }
  198. //-----------------------------------------------------------------------------
  199. //-----------------------------------------------------------------------------
  200. bool CAI_AssaultBehavior::AssaultHasBegun()
  201. {
  202. if( m_AssaultCue == CUE_DONT_WAIT && IsRunning() && m_bHitRallyPoint )
  203. {
  204. return true;
  205. }
  206. return m_ReceivedAssaultCue == m_AssaultCue;
  207. }
  208. //-----------------------------------------------------------------------------
  209. // Purpose: Find an assaultpoint matching the iszAssaultPointName.
  210. // If more than one assault point of this type is found, randomly
  211. // use any of them EXCEPT the one most recently used.
  212. //-----------------------------------------------------------------------------
  213. CAssaultPoint *CAI_AssaultBehavior::FindAssaultPoint( string_t iszAssaultPointName )
  214. {
  215. CUtlVector<CAssaultPoint*>pAssaultPoints;
  216. CUtlVector<CAssaultPoint*>pClearAssaultPoints;
  217. CAssaultPoint *pAssaultEnt = (CAssaultPoint *)gEntList.FindEntityByName( NULL, iszAssaultPointName );
  218. while( pAssaultEnt != NULL )
  219. {
  220. pAssaultPoints.AddToTail( pAssaultEnt );
  221. pAssaultEnt = (CAssaultPoint *)gEntList.FindEntityByName( pAssaultEnt, iszAssaultPointName );
  222. }
  223. // Didn't find any?!
  224. if( pAssaultPoints.Count() < 1 )
  225. return NULL;
  226. // Only found one, just return it.
  227. if( pAssaultPoints.Count() == 1 )
  228. return pAssaultPoints[0];
  229. // Throw out any nodes that I cannot fit my bounding box on.
  230. for( int i = 0 ; i < pAssaultPoints.Count() ; i++ )
  231. {
  232. trace_t tr;
  233. CAI_BaseNPC *pNPC = GetOuter();
  234. CAssaultPoint *pAssaultPoint = pAssaultPoints[i];
  235. AI_TraceHull ( pAssaultPoint->GetAbsOrigin(), pAssaultPoint->GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs(), MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr );
  236. if ( tr.fraction == 1.0 )
  237. {
  238. // Copy this into the list of clear points.
  239. pClearAssaultPoints.AddToTail(pAssaultPoint);
  240. }
  241. }
  242. // Only one clear assault point left!
  243. if( pClearAssaultPoints.Count() == 1 )
  244. return pClearAssaultPoints[0];
  245. // NONE left. Just return a random assault point, knowing that it's blocked. This is the old behavior, anyway.
  246. if( pClearAssaultPoints.Count() < 1 )
  247. return pAssaultPoints[ random->RandomInt(0, (pAssaultPoints.Count() - 1)) ];
  248. // We found several! First throw out the one most recently used.
  249. // This prevents picking the same point at this branch twice in a row.
  250. float flMostRecentTime = -1.0f; // Impossibly old
  251. int iMostRecentIndex = -1;
  252. for( int i = 0 ; i < pClearAssaultPoints.Count() ; i++ )
  253. {
  254. if( pClearAssaultPoints[i]->m_flTimeLastUsed > flMostRecentTime )
  255. {
  256. flMostRecentTime = pClearAssaultPoints[i]->m_flTimeLastUsed;
  257. iMostRecentIndex = i;
  258. }
  259. }
  260. Assert( iMostRecentIndex > -1 );
  261. // Remove the most recently used
  262. pClearAssaultPoints.Remove( iMostRecentIndex );
  263. return pClearAssaultPoints[ random->RandomInt(0, (pClearAssaultPoints.Count() - 1)) ];
  264. }
  265. //-----------------------------------------------------------------------------
  266. //-----------------------------------------------------------------------------
  267. void CAI_AssaultBehavior::SetAssaultPoint( CAssaultPoint *pAssaultPoint )
  268. {
  269. m_hAssaultPoint = pAssaultPoint;
  270. pAssaultPoint->m_flTimeLastUsed = gpGlobals->curtime;
  271. }
  272. //-----------------------------------------------------------------------------
  273. // Purpose:
  274. //-----------------------------------------------------------------------------
  275. void CAI_AssaultBehavior::ClearAssaultPoint( void )
  276. {
  277. // To announce clear means that this NPC hasn't seen any live targets in
  278. // the assault area for the length of time the level designer has
  279. // specified that they should be vigilant. This effectively deactivates
  280. // the assault behavior.
  281. // This can also be happen if an assault point has ClearOnContact set, and
  282. // an NPC assaulting to this point has seen an enemy.
  283. // keep track of the name of the assault point
  284. m_AssaultPointName = m_hAssaultPoint->m_NextAssaultPointName;
  285. // Do we need to move to another assault point?
  286. if( m_hAssaultPoint->m_NextAssaultPointName != NULL_STRING )
  287. {
  288. CAssaultPoint *pNextPoint = FindAssaultPoint( m_hAssaultPoint->m_NextAssaultPointName );
  289. if( pNextPoint )
  290. {
  291. SetAssaultPoint( pNextPoint );
  292. // Send our NPC to the next assault point!
  293. m_bHitAssaultPoint = false;
  294. return;
  295. }
  296. else
  297. {
  298. DevMsg("**ERROR: Can't find next assault point: %s\n", STRING(m_hAssaultPoint->m_NextAssaultPointName) );
  299. // Bomb out of assault behavior.
  300. m_AssaultCue = CUE_NO_ASSAULT;
  301. ClearSchedule( "Can't find next assault point" );
  302. return;
  303. }
  304. }
  305. // Just set the cue back to NO_ASSAULT. This disables the behavior.
  306. m_AssaultCue = CUE_NO_ASSAULT;
  307. // Exclusive or not, we unlock here. The assault is done.
  308. UnlockRallyPoint();
  309. // If this assault behavior has changed the NPC's hint group,
  310. // slam that NPC's hint group back to null.
  311. // !!!TODO: if the NPC had a different hint group before the
  312. // assault began, we're slamming that, too! We might want
  313. // to cache it off if this becomes a problem (sjb)
  314. if( m_hAssaultPoint->m_AssaultHintGroup != NULL_STRING )
  315. {
  316. GetOuter()->SetHintGroup( NULL_STRING );
  317. }
  318. m_hAssaultPoint->m_OnAssaultClear.FireOutput( GetOuter(), GetOuter(), 0 );
  319. }
  320. //-----------------------------------------------------------------------------
  321. //-----------------------------------------------------------------------------
  322. void CAI_AssaultBehavior::OnHitAssaultPoint( void )
  323. {
  324. GetOuter()->SpeakSentence( ASSAULT_SENTENCE_HIT_ASSAULT_POINT );
  325. m_bHitAssaultPoint = true;
  326. m_hAssaultPoint->m_OnArrival.FireOutput( GetOuter(), m_hAssaultPoint, 0 );
  327. // Set the assault hint group
  328. if( m_hAssaultPoint->m_AssaultHintGroup != NULL_STRING )
  329. {
  330. SetHintGroup( m_hAssaultPoint->m_AssaultHintGroup );
  331. }
  332. }
  333. //-----------------------------------------------------------------------------
  334. //-----------------------------------------------------------------------------
  335. void CAI_AssaultBehavior::GatherConditions( void )
  336. {
  337. BaseClass::GatherConditions();
  338. // If this NPC is moving towards an assault point which
  339. // a) Has a Next Assault Point, and
  340. // b) Is flagged to Clear On Arrival,
  341. // then hit and clear the assault point (fire all entity I/O) and move on to the next one without
  342. // interrupting the NPC's schedule. This provides a more fluid movement from point to point.
  343. if( IsCurSchedule( SCHED_MOVE_TO_ASSAULT_POINT ) && hl2_episodic.GetBool() )
  344. {
  345. if( m_hAssaultPoint && m_hAssaultPoint->HasSpawnFlags(SF_ASSAULTPOINT_CLEARONARRIVAL) && m_hAssaultPoint->m_NextAssaultPointName != NULL_STRING )
  346. {
  347. float flDist = GetAbsOrigin().DistTo( m_hAssaultPoint->GetAbsOrigin() );
  348. if( flDist <= GetOuter()->GetMotor()->MinStoppingDist() )
  349. {
  350. OnHitAssaultPoint();
  351. ClearAssaultPoint();
  352. AI_NavGoal_t goal( m_hAssaultPoint->GetAbsOrigin() );
  353. goal.pTarget = m_hAssaultPoint;
  354. if ( GetNavigator()->SetGoal( goal ) == false )
  355. {
  356. TaskFail( "Can't refresh assault path" );
  357. }
  358. }
  359. }
  360. }
  361. if ( IsForcingCrouch() && GetOuter()->IsCrouching() )
  362. {
  363. ClearCondition( COND_HEAR_BULLET_IMPACT );
  364. }
  365. if( OnStrictAssault() )
  366. {
  367. // Don't get distracted. Die trying if you have to.
  368. ClearCondition( COND_HEAR_DANGER );
  369. }
  370. }
  371. //-----------------------------------------------------------------------------
  372. // Purpose:
  373. // Input : *pTask -
  374. //-----------------------------------------------------------------------------
  375. void CAI_AssaultBehavior::StartTask( const Task_t *pTask )
  376. {
  377. switch( pTask->iTask )
  378. {
  379. case TASK_RANGE_ATTACK1:
  380. BaseClass::StartTask( pTask );
  381. break;
  382. case TASK_ASSAULT_DEFER_SCHEDULE_SELECTION:
  383. m_flTimeDeferScheduleSelection = gpGlobals->curtime + pTask->flTaskData;
  384. TaskComplete();
  385. break;
  386. case TASK_ASSAULT_MOVE_AWAY_PATH:
  387. break;
  388. case TASK_ANNOUNCE_CLEAR:
  389. {
  390. // If we're at an assault point that can never be cleared, keep waiting forever (if it's the last point in the assault)
  391. if ( m_hAssaultPoint &&
  392. !m_hAssaultPoint->HasSpawnFlags( SF_ASSAULTPOINT_CLEARONARRIVAL ) &&
  393. m_hAssaultPoint->m_bNeverTimeout &&
  394. m_hAssaultPoint->m_NextAssaultPointName == NULL_STRING )
  395. {
  396. TaskComplete();
  397. return;
  398. }
  399. ClearAssaultPoint();
  400. TaskComplete();
  401. }
  402. break;
  403. case TASK_WAIT_ASSAULT_DELAY:
  404. {
  405. if( m_hRallyPoint )
  406. {
  407. GetOuter()->SetWait( m_hRallyPoint->m_flAssaultDelay );
  408. }
  409. else
  410. {
  411. TaskComplete();
  412. }
  413. }
  414. break;
  415. case TASK_AWAIT_ASSAULT_TIMEOUT:
  416. // Maintain vigil for as long as the level designer has asked. Wait
  417. // and look for targets.
  418. GetOuter()->SetWait( m_hAssaultPoint->m_flAssaultTimeout );
  419. break;
  420. case TASK_GET_PATH_TO_RALLY_POINT:
  421. {
  422. AI_NavGoal_t goal( m_hRallyPoint->GetAbsOrigin() );
  423. goal.pTarget = m_hRallyPoint;
  424. if ( GetNavigator()->SetGoal( goal ) == false )
  425. {
  426. // Try and get as close as possible otherwise
  427. AI_NavGoal_t nearGoal( GOALTYPE_LOCATION_NEAREST_NODE, m_hRallyPoint->GetAbsOrigin(), AIN_DEF_ACTIVITY, 256 );
  428. if ( GetNavigator()->SetGoal( nearGoal, AIN_CLEAR_PREVIOUS_STATE ) )
  429. {
  430. //FIXME: HACK! The internal pathfinding is setting this without our consent, so override it!
  431. ClearCondition( COND_TASK_FAILED );
  432. GetNavigator()->SetArrivalDirection( m_hRallyPoint->GetAbsAngles() );
  433. TaskComplete();
  434. return;
  435. }
  436. }
  437. GetNavigator()->SetArrivalDirection( m_hRallyPoint->GetAbsAngles() );
  438. }
  439. break;
  440. case TASK_FACE_RALLY_POINT:
  441. {
  442. UpdateForceCrouch();
  443. GetMotor()->SetIdealYaw( m_hRallyPoint->GetAbsAngles().y );
  444. GetOuter()->SetTurnActivity();
  445. }
  446. break;
  447. case TASK_GET_PATH_TO_ASSAULT_POINT:
  448. {
  449. AI_NavGoal_t goal( m_hAssaultPoint->GetAbsOrigin() );
  450. goal.pTarget = m_hAssaultPoint;
  451. if ( GetNavigator()->SetGoal( goal ) == false )
  452. {
  453. // Try and get as close as possible otherwise
  454. AI_NavGoal_t nearGoal( GOALTYPE_LOCATION_NEAREST_NODE, m_hAssaultPoint->GetAbsOrigin(), AIN_DEF_ACTIVITY, 256 );
  455. if ( GetNavigator()->SetGoal( nearGoal, AIN_CLEAR_PREVIOUS_STATE ) )
  456. {
  457. //FIXME: HACK! The internal pathfinding is setting this without our consent, so override it!
  458. ClearCondition( COND_TASK_FAILED );
  459. GetNavigator()->SetArrivalDirection( m_hAssaultPoint->GetAbsAngles() );
  460. TaskComplete();
  461. return;
  462. }
  463. }
  464. GetNavigator()->SetArrivalDirection( m_hAssaultPoint->GetAbsAngles() );
  465. }
  466. break;
  467. case TASK_FACE_ASSAULT_POINT:
  468. {
  469. UpdateForceCrouch();
  470. if( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  471. {
  472. // If I can already fight when I arrive, don't bother running any facing code. Let
  473. // The combat AI do that. Turning here will only make the NPC look dumb in a combat
  474. // situation because it will take time to turn before attacking.
  475. TaskComplete();
  476. }
  477. else
  478. {
  479. GetMotor()->SetIdealYaw( m_hAssaultPoint->GetAbsAngles().y );
  480. GetOuter()->SetTurnActivity();
  481. }
  482. }
  483. break;
  484. case TASK_HIT_ASSAULT_POINT:
  485. OnHitAssaultPoint();
  486. TaskComplete();
  487. break;
  488. case TASK_HIT_RALLY_POINT:
  489. // Once we're stading on it and facing the correct direction,
  490. // we have arrived at rally point.
  491. GetOuter()->SpeakSentence( ASSAULT_SENTENCE_HIT_RALLY_POINT );
  492. m_bHitRallyPoint = true;
  493. m_hRallyPoint->m_OnArrival.FireOutput( GetOuter(), m_hRallyPoint, 0 );
  494. TaskComplete();
  495. break;
  496. case TASK_AWAIT_CUE:
  497. if( PollAssaultCue() )
  498. {
  499. TaskComplete();
  500. }
  501. else
  502. {
  503. // Don't do anything if we've been told to crouch
  504. if ( IsForcingCrouch() )
  505. break;
  506. else if( m_hRallyPoint->m_RallySequenceName != NULL_STRING )
  507. {
  508. // The cue hasn't been given yet, so set to the rally sequence.
  509. int sequence = GetOuter()->LookupSequence( STRING( m_hRallyPoint->m_RallySequenceName ) );
  510. if( sequence != -1 )
  511. {
  512. GetOuter()->ResetSequence( sequence );
  513. GetOuter()->SetIdealActivity( ACT_DO_NOT_DISTURB );
  514. }
  515. }
  516. else
  517. {
  518. // Only chain this task if I'm not playing a custom animation
  519. if( GetOuter()->GetEnemy() )
  520. {
  521. ChainStartTask( TASK_FACE_ENEMY, 0 );
  522. }
  523. }
  524. }
  525. break;
  526. default:
  527. BaseClass::StartTask( pTask );
  528. break;
  529. }
  530. }
  531. //-----------------------------------------------------------------------------
  532. // Purpose:
  533. // Input : *pTask -
  534. //-----------------------------------------------------------------------------
  535. void CAI_AssaultBehavior::RunTask( const Task_t *pTask )
  536. {
  537. switch( pTask->iTask )
  538. {
  539. case TASK_WAIT_ASSAULT_DELAY:
  540. case TASK_AWAIT_ASSAULT_TIMEOUT:
  541. if ( m_hAssaultPoint )
  542. {
  543. if ( m_hAssaultPoint->m_bInputForcedClear || (m_hAssaultPoint->m_bClearOnContact && HasCondition( COND_SEE_ENEMY )) )
  544. {
  545. // If we're on an assault that should clear on contact, clear when we see an enemy
  546. TaskComplete();
  547. }
  548. }
  549. if( GetOuter()->IsWaitFinished() && ( pTask->iTask == TASK_WAIT_ASSAULT_DELAY || !m_hAssaultPoint->m_bNeverTimeout ) )
  550. {
  551. TaskComplete();
  552. }
  553. break;
  554. case TASK_FACE_RALLY_POINT:
  555. case TASK_FACE_ASSAULT_POINT:
  556. GetMotor()->UpdateYaw();
  557. if( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  558. {
  559. // Out early if the NPC can attack.
  560. TaskComplete();
  561. }
  562. if ( GetOuter()->FacingIdeal() )
  563. {
  564. TaskComplete();
  565. }
  566. break;
  567. case TASK_AWAIT_CUE:
  568. // If we've lost our rally point, abort
  569. if ( !m_hRallyPoint )
  570. {
  571. TaskFail("No rally point.");
  572. break;
  573. }
  574. if( PollAssaultCue() )
  575. {
  576. TaskComplete();
  577. }
  578. if ( IsForcingCrouch() )
  579. break;
  580. if( GetOuter()->GetEnemy() && m_hRallyPoint->m_RallySequenceName == NULL_STRING && !HasCondition(COND_ENEMY_OCCLUDED) )
  581. {
  582. // I have an enemy and I'm NOT playing a custom animation.
  583. ChainRunTask( TASK_FACE_ENEMY, 0 );
  584. }
  585. break;
  586. case TASK_WAIT_FOR_MOVEMENT:
  587. if ( ai_debug_assault.GetBool() )
  588. {
  589. if ( IsCurSchedule( SCHED_MOVE_TO_ASSAULT_POINT ) )
  590. {
  591. NDebugOverlay::Line( WorldSpaceCenter(), GetNavigator()->GetGoalPos(), 255,0,0, true,0.1);
  592. NDebugOverlay::Box( GetNavigator()->GetGoalPos(), -Vector(10,10,10), Vector(10,10,10), 255,0,0, 8, 0.1 );
  593. }
  594. else if ( IsCurSchedule( SCHED_MOVE_TO_RALLY_POINT ) )
  595. {
  596. NDebugOverlay::Line( WorldSpaceCenter(), GetNavigator()->GetGoalPos(), 0,255,0, true,0.1);
  597. NDebugOverlay::Box( GetNavigator()->GetGoalPos(), -Vector(10,10,10), Vector(10,10,10), 0,255,0, 8, 0.1 );
  598. }
  599. }
  600. if ( m_hAssaultPoint && (m_hAssaultPoint->m_bInputForcedClear || (m_hAssaultPoint->m_bClearOnContact && HasCondition( COND_SEE_ENEMY ))) )
  601. {
  602. DevMsg( "Assault Cleared due to Contact or Input!\n" );
  603. ClearAssaultPoint();
  604. TaskComplete();
  605. return;
  606. }
  607. if ( ( ( !GetOuter()->DidChooseEnemy() && gpGlobals->curtime - GetOuter()->GetTimeEnemyAcquired() > 1 ) || !GetOuter()->GetEnemy() ) )
  608. {
  609. CBaseEntity *pNewEnemy = GetOuter()->BestEnemy();
  610. if( pNewEnemy != NULL && pNewEnemy != GetOuter()->GetEnemy() )
  611. {
  612. GetOuter()->SetEnemy( pNewEnemy );
  613. GetOuter()->SetState( NPC_STATE_COMBAT );
  614. }
  615. }
  616. BaseClass::RunTask( pTask );
  617. break;
  618. default:
  619. BaseClass::RunTask( pTask );
  620. break;
  621. }
  622. }
  623. //-----------------------------------------------------------------------------
  624. //-----------------------------------------------------------------------------
  625. CRallyPoint *CAI_AssaultBehavior::FindBestRallyPointInRadius( const Vector &vecCenter, float flRadius )
  626. {
  627. VPROF_BUDGET( "CAI_AssaultBehavior::FindBestRallyPointInRadius", VPROF_BUDGETGROUP_NPCS );
  628. const int RALLY_SEARCH_ENTS = 30;
  629. CBaseEntity *pEntities[RALLY_SEARCH_ENTS];
  630. int iNumEntities = UTIL_EntitiesInSphere( pEntities, RALLY_SEARCH_ENTS, vecCenter, flRadius, 0 );
  631. CRallyPoint *pBest = NULL;
  632. int iBestPriority = -1;
  633. for ( int i = 0; i < iNumEntities; i++ )
  634. {
  635. CRallyPoint *pRallyEnt = dynamic_cast<CRallyPoint *>(pEntities[i]);
  636. if( pRallyEnt )
  637. {
  638. if( !pRallyEnt->IsLocked() )
  639. {
  640. // Consider this point.
  641. if( pRallyEnt->m_iPriority > iBestPriority )
  642. {
  643. pBest = pRallyEnt;
  644. iBestPriority = pRallyEnt->m_iPriority;
  645. }
  646. }
  647. }
  648. }
  649. return pBest;
  650. }
  651. //-----------------------------------------------------------------------------
  652. //-----------------------------------------------------------------------------
  653. bool CAI_AssaultBehavior::IsValidShootPosition( const Vector &vLocation, CAI_Node *pNode, CAI_Hint const *pHint )
  654. {
  655. CBaseEntity *pCuePoint = NULL;
  656. float flTolerance = 0.0f;
  657. if( m_bHitRallyPoint && !m_bHitAssaultPoint && !AssaultHasBegun() )
  658. {
  659. if( m_hRallyPoint != NULL )
  660. {
  661. pCuePoint = m_hRallyPoint;
  662. flTolerance = CUE_POINT_TOLERANCE;
  663. }
  664. }
  665. else if( m_bHitAssaultPoint )
  666. {
  667. if( m_hAssaultPoint != NULL )
  668. {
  669. pCuePoint = m_hAssaultPoint;
  670. flTolerance = m_hAssaultPoint->m_flAssaultPointTolerance;
  671. }
  672. }
  673. if ( pCuePoint && (vLocation - pCuePoint->GetAbsOrigin()).Length2DSqr() > Square( flTolerance - 0.1 ) )
  674. return false;
  675. return BaseClass::IsValidShootPosition( vLocation, pNode, pHint );
  676. }
  677. //-----------------------------------------------------------------------------
  678. //-----------------------------------------------------------------------------
  679. float CAI_AssaultBehavior::GetMaxTacticalLateralMovement( void )
  680. {
  681. return CUE_POINT_TOLERANCE - 0.1;
  682. }
  683. //-----------------------------------------------------------------------------
  684. //-----------------------------------------------------------------------------
  685. void CAI_AssaultBehavior::UpdateOnRemove()
  686. {
  687. // Ignore exclusivity. Our NPC just died.
  688. UnlockRallyPoint();
  689. }
  690. //-----------------------------------------------------------------------------
  691. // Purpose:
  692. // Output : Returns true on success, false on failure.
  693. //-----------------------------------------------------------------------------
  694. bool CAI_AssaultBehavior::OnStrictAssault( void )
  695. {
  696. return (m_hAssaultPoint && m_hAssaultPoint->m_iStrictness);
  697. }
  698. //-----------------------------------------------------------------------------
  699. // Purpose:
  700. // Output : Returns true on success, false on failure.
  701. //-----------------------------------------------------------------------------
  702. bool CAI_AssaultBehavior::UpdateForceCrouch( void )
  703. {
  704. if ( IsForcingCrouch() )
  705. {
  706. // Only force crouch when we're near the point we're supposed to crouch at
  707. float flDistanceToTargetSqr = GetOuter()->GetAbsOrigin().DistToSqr( AssaultHasBegun() ? m_hAssaultPoint->GetAbsOrigin() : m_hRallyPoint->GetAbsOrigin() );
  708. if ( flDistanceToTargetSqr < (64*64) )
  709. {
  710. GetOuter()->ForceCrouch();
  711. }
  712. else
  713. {
  714. GetOuter()->ClearForceCrouch();
  715. }
  716. return true;
  717. }
  718. return false;
  719. }
  720. //-----------------------------------------------------------------------------
  721. // Purpose:
  722. // Output : Returns true on success, false on failure.
  723. //-----------------------------------------------------------------------------
  724. bool CAI_AssaultBehavior::IsForcingCrouch( void )
  725. {
  726. if ( AssaultHasBegun() )
  727. return (m_hAssaultPoint && m_hAssaultPoint->m_bForceCrouch);
  728. return (m_hRallyPoint && m_hRallyPoint->m_bForceCrouch);
  729. }
  730. //-----------------------------------------------------------------------------
  731. // Purpose:
  732. // Output : Returns true on success, false on failure.
  733. //-----------------------------------------------------------------------------
  734. bool CAI_AssaultBehavior::IsUrgent( void )
  735. {
  736. if ( AssaultHasBegun() )
  737. return (m_hAssaultPoint && m_hAssaultPoint->m_bIsUrgent);
  738. return (m_hRallyPoint && m_hRallyPoint->m_bIsUrgent);
  739. }
  740. //-----------------------------------------------------------------------------
  741. // Purpose: Unlock any rally points the behavior is currently locking
  742. //-----------------------------------------------------------------------------
  743. void CAI_AssaultBehavior::UnlockRallyPoint( void )
  744. {
  745. CAI_AssaultBehavior *pBehavior;
  746. if ( GetOuter()->GetBehavior( &pBehavior ) )
  747. {
  748. if( pBehavior->m_hRallyPoint )
  749. {
  750. pBehavior->m_hRallyPoint->Unlock( GetOuter() );
  751. }
  752. }
  753. }
  754. //-----------------------------------------------------------------------------
  755. // Purpose:
  756. // Input : *pRallyPoint -
  757. // assaultcue -
  758. //-----------------------------------------------------------------------------
  759. void CAI_AssaultBehavior::SetParameters( CBaseEntity *pRallyEnt, AssaultCue_t assaultcue )
  760. {
  761. VPROF_BUDGET( "CAI_AssaultBehavior::SetParameters", VPROF_BUDGETGROUP_NPCS );
  762. // Clean up any soon to be dangling rally points
  763. UnlockRallyPoint();
  764. // Firstly, find a rally point.
  765. CRallyPoint *pRallyPoint = dynamic_cast<CRallyPoint *>(pRallyEnt);
  766. if( pRallyPoint )
  767. {
  768. if( !pRallyPoint->IsLocked() )
  769. {
  770. // Claim it.
  771. m_hRallyPoint = pRallyPoint;
  772. m_hRallyPoint->Lock( GetOuter() );
  773. m_AssaultCue = assaultcue;
  774. InitializeBehavior();
  775. return;
  776. }
  777. else
  778. {
  779. DevMsg("**ERROR: Specified a rally point that is LOCKED!\n" );
  780. }
  781. }
  782. else
  783. {
  784. DevMsg("**ERROR: Bad RallyPoint in SetParameters\n" );
  785. // Bomb out of assault behavior.
  786. m_AssaultCue = CUE_NO_ASSAULT;
  787. ClearSchedule( "Bad rally point" );
  788. }
  789. }
  790. //-----------------------------------------------------------------------------
  791. // Purpose:
  792. // Input : rallypointname -
  793. // assaultcue -
  794. //-----------------------------------------------------------------------------
  795. void CAI_AssaultBehavior::SetParameters( string_t rallypointname, AssaultCue_t assaultcue, int rallySelectMethod )
  796. {
  797. VPROF_BUDGET( "CAI_AssaultBehavior::SetParameters", VPROF_BUDGETGROUP_NPCS );
  798. // Clean up any soon to be dangling rally points
  799. UnlockRallyPoint();
  800. // Firstly, find a rally point.
  801. CRallyPoint *pRallyEnt = dynamic_cast<CRallyPoint *>(gEntList.FindEntityByName( NULL, rallypointname ) );
  802. CRallyPoint *pBest = NULL;
  803. int iBestPriority = -1;
  804. switch( rallySelectMethod )
  805. {
  806. case RALLY_POINT_SELECT_DEFAULT:
  807. {
  808. while( pRallyEnt )
  809. {
  810. if( !pRallyEnt->IsLocked() )
  811. {
  812. // Consider this point.
  813. if( pRallyEnt->m_iPriority > iBestPriority )
  814. {
  815. // This point is higher priority. I must take it.
  816. pBest = pRallyEnt;
  817. iBestPriority = pRallyEnt->m_iPriority;
  818. }
  819. else if ( pRallyEnt->m_iPriority == iBestPriority )
  820. {
  821. // This point is the same priority as my current best.
  822. // I must take it if it is closer.
  823. Vector vecStart = GetOuter()->GetAbsOrigin();
  824. float flNewDist, flBestDist;
  825. flNewDist = ( pRallyEnt->GetAbsOrigin() - vecStart ).LengthSqr();
  826. flBestDist = ( pBest->GetAbsOrigin() - vecStart ).LengthSqr();
  827. if( flNewDist < flBestDist )
  828. {
  829. // Priority is already identical. Just take this point.
  830. pBest = pRallyEnt;
  831. }
  832. }
  833. }
  834. pRallyEnt = dynamic_cast<CRallyPoint *>(gEntList.FindEntityByName( pRallyEnt, rallypointname, NULL ) );
  835. }
  836. }
  837. break;
  838. case RALLY_POINT_SELECT_RANDOM:
  839. {
  840. // Gather all available points into a utilvector, then pick one at random.
  841. CUtlVector<CRallyPoint *> rallyPoints; // List of rally points that are available to choose from.
  842. while( pRallyEnt )
  843. {
  844. if( !pRallyEnt->IsLocked() )
  845. {
  846. rallyPoints.AddToTail( pRallyEnt );
  847. }
  848. pRallyEnt = dynamic_cast<CRallyPoint *>(gEntList.FindEntityByName( pRallyEnt, rallypointname ) );
  849. }
  850. if( rallyPoints.Count() > 0 )
  851. {
  852. pBest = rallyPoints[ random->RandomInt(0, rallyPoints.Count()- 1) ];
  853. }
  854. }
  855. break;
  856. default:
  857. DevMsg( "ERROR: INVALID RALLY POINT SELECTION METHOD. Assault will not function.\n");
  858. break;
  859. }
  860. if( !pBest )
  861. {
  862. DevMsg("%s Didn't find a best rally point!\n", GetOuter()->GetEntityName().ToCStr() );
  863. return;
  864. }
  865. pBest->Lock( GetOuter() );
  866. m_hRallyPoint = pBest;
  867. if( !m_hRallyPoint )
  868. {
  869. DevMsg("**ERROR: Can't find a rally point named '%s'\n", STRING( rallypointname ));
  870. // Bomb out of assault behavior.
  871. m_AssaultCue = CUE_NO_ASSAULT;
  872. ClearSchedule( "Can't find rally point" );
  873. return;
  874. }
  875. m_AssaultCue = assaultcue;
  876. InitializeBehavior();
  877. }
  878. //-----------------------------------------------------------------------------
  879. // Purpose:
  880. //-----------------------------------------------------------------------------
  881. void CAI_AssaultBehavior::InitializeBehavior()
  882. {
  883. // initialize the variables that track whether the NPC has reached (hit)
  884. // his rally and assault points already. Be advised, having hit the point
  885. // only means you have been to it at some point. Doesn't mean you're standing
  886. // there still. Mainly used to understand which 'phase' of the assault an NPC
  887. // is in.
  888. m_bHitRallyPoint = false;
  889. m_bHitAssaultPoint = false;
  890. m_hAssaultPoint = 0;
  891. m_bDiverting = false;
  892. m_flLastSawAnEnemyAt = 0;
  893. // Also reset the status of externally received assault cues
  894. m_ReceivedAssaultCue = CUE_NO_ASSAULT;
  895. CAssaultPoint *pAssaultEnt = FindAssaultPoint( m_hRallyPoint->m_AssaultPointName );
  896. if( pAssaultEnt )
  897. {
  898. SetAssaultPoint(pAssaultEnt);
  899. }
  900. else
  901. {
  902. DevMsg("**ERROR: Can't find any assault points named: %s\n", STRING( m_hRallyPoint->m_AssaultPointName ));
  903. // Bomb out of assault behavior.
  904. m_AssaultCue = CUE_NO_ASSAULT;
  905. ClearSchedule( "Can't find assault point" );
  906. return;
  907. }
  908. // Slam the NPC's schedule so that he starts picking Assault schedules right now.
  909. ClearSchedule( "Initializing assault behavior" );
  910. }
  911. //-----------------------------------------------------------------------------
  912. // Purpose: Check conditions and see if the cue to being an assault has come.
  913. // Output : Returns true on success, false on failure.
  914. //-----------------------------------------------------------------------------
  915. bool CAI_AssaultBehavior::PollAssaultCue( void )
  916. {
  917. // right now, always go when the commander says.
  918. if( m_ReceivedAssaultCue == CUE_COMMANDER )
  919. {
  920. return true;
  921. }
  922. switch( m_AssaultCue )
  923. {
  924. case CUE_NO_ASSAULT:
  925. // NO_ASSAULT never ever triggers.
  926. return false;
  927. break;
  928. case CUE_ENTITY_INPUT:
  929. return m_ReceivedAssaultCue == CUE_ENTITY_INPUT;
  930. break;
  931. case CUE_PLAYER_GUNFIRE:
  932. // Any gunfire will trigger this right now (sjb)
  933. if( HasCondition( COND_HEAR_COMBAT ) )
  934. {
  935. return true;
  936. }
  937. break;
  938. case CUE_DONT_WAIT:
  939. // Just keep going!
  940. m_ReceivedAssaultCue = CUE_DONT_WAIT;
  941. return true;
  942. break;
  943. case CUE_COMMANDER:
  944. // Player told me to go, so go!
  945. return m_ReceivedAssaultCue == CUE_COMMANDER;
  946. break;
  947. }
  948. return false;
  949. }
  950. //-----------------------------------------------------------------------------
  951. //
  952. //-----------------------------------------------------------------------------
  953. void CAI_AssaultBehavior::OnRestore()
  954. {
  955. if ( !m_hAssaultPoint || !m_hRallyPoint )
  956. {
  957. Disable();
  958. NotifyChangeBehaviorStatus();
  959. }
  960. }
  961. //-----------------------------------------------------------------------------
  962. // Purpose:
  963. // Output : Returns true on success, false on failure.
  964. //-----------------------------------------------------------------------------
  965. bool CAI_AssaultBehavior::CanSelectSchedule()
  966. {
  967. if ( !GetOuter()->IsInterruptable() )
  968. return false;
  969. if ( GetOuter()->HasCondition( COND_RECEIVED_ORDERS ) )
  970. return false;
  971. // We're letting other AI run for a little while because the assault AI failed recently.
  972. if ( m_flTimeDeferScheduleSelection > gpGlobals->curtime )
  973. return false;
  974. // No schedule selection if no assault is being conducted.
  975. if( m_AssaultCue == CUE_NO_ASSAULT )
  976. return false;
  977. if ( !m_hAssaultPoint || !m_hRallyPoint )
  978. {
  979. Disable();
  980. return false;
  981. }
  982. // Remember when we last saw an enemy
  983. if ( GetEnemy() )
  984. {
  985. m_flLastSawAnEnemyAt = gpGlobals->curtime;
  986. }
  987. // If we've seen an enemy in the last few seconds, and we're allowed to divert,
  988. // let the base AI decide what I should do.
  989. if ( IsAllowedToDivert() )
  990. {
  991. // Return true, but remember that we're actually allowing them to divert
  992. // This is done because we don't want the assault behaviour to think it's finished with the assault.
  993. m_bDiverting = true;
  994. }
  995. else if ( m_bDiverting )
  996. {
  997. // If we were diverting, provoke us to make a new schedule selection
  998. SetCondition( COND_PROVOKED );
  999. m_bDiverting = false;
  1000. }
  1001. // If we're diverting, let the base AI decide everything
  1002. if ( m_bDiverting )
  1003. return false;
  1004. return true;
  1005. }
  1006. //-----------------------------------------------------------------------------
  1007. // Purpose:
  1008. //-----------------------------------------------------------------------------
  1009. void CAI_AssaultBehavior::BeginScheduleSelection()
  1010. {
  1011. }
  1012. //-----------------------------------------------------------------------------
  1013. // Purpose:
  1014. //-----------------------------------------------------------------------------
  1015. void CAI_AssaultBehavior::EndScheduleSelection()
  1016. {
  1017. m_bHitAssaultPoint = false;
  1018. if( m_hRallyPoint != NULL )
  1019. {
  1020. if( !m_hRallyPoint->IsExclusive() )
  1021. m_bHitRallyPoint = false;
  1022. if( !hl2_episodic.GetBool() || !m_hRallyPoint->IsExclusive() || !GetOuter()->IsAlive() )
  1023. {
  1024. // Here we unlock the rally point if it is NOT EXCLUSIVE
  1025. // -OR- the Outer is DEAD. (This gives us a head-start on
  1026. // preparing the point to take new NPCs right away. Otherwise
  1027. // we have to wait two seconds until the behavior is destroyed.)
  1028. // NOTICE that the legacy (non-episodic) support calls UnlockRallyPoint
  1029. // unconditionally on EndScheduleSelection()
  1030. UnlockRallyPoint();
  1031. }
  1032. }
  1033. GetOuter()->ClearForceCrouch();
  1034. }
  1035. //-----------------------------------------------------------------------------
  1036. // Purpose:
  1037. // Input : scheduleType -
  1038. // Output : int
  1039. //-----------------------------------------------------------------------------
  1040. int CAI_AssaultBehavior::TranslateSchedule( int scheduleType )
  1041. {
  1042. switch( scheduleType )
  1043. {
  1044. case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
  1045. // This nasty schedule can allow the NPC to violate their position near
  1046. // the assault point. Translate it away to something stationary. (sjb)
  1047. return SCHED_COMBAT_FACE;
  1048. break;
  1049. case SCHED_RANGE_ATTACK1:
  1050. if ( GetOuter()->GetShotRegulator()->IsInRestInterval() )
  1051. {
  1052. if ( GetOuter()->HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  1053. GetOuter()->VacateStrategySlot();
  1054. return SCHED_COMBAT_FACE; // @TODO (toml 07-02-03): Should do something more tactically sensible
  1055. }
  1056. break;
  1057. case SCHED_MOVE_TO_WEAPON_RANGE:
  1058. case SCHED_CHASE_ENEMY:
  1059. if( m_bHitAssaultPoint )
  1060. {
  1061. return SCHED_WAIT_AND_CLEAR;
  1062. }
  1063. else
  1064. {
  1065. return SCHED_MOVE_TO_ASSAULT_POINT;
  1066. }
  1067. break;
  1068. case SCHED_HOLD_RALLY_POINT:
  1069. if( HasCondition(COND_NO_PRIMARY_AMMO) | HasCondition(COND_LOW_PRIMARY_AMMO) )
  1070. {
  1071. return SCHED_RELOAD;
  1072. }
  1073. break;
  1074. case SCHED_MOVE_TO_ASSAULT_POINT:
  1075. {
  1076. float flDist = ( m_hAssaultPoint->GetAbsOrigin() - GetAbsOrigin() ).Length();
  1077. if ( flDist <= 12.0f )
  1078. return SCHED_AT_ASSAULT_POINT;
  1079. }
  1080. break;
  1081. }
  1082. return BaseClass::TranslateSchedule( scheduleType );
  1083. }
  1084. //-----------------------------------------------------------------------------
  1085. // Purpose:
  1086. //-----------------------------------------------------------------------------
  1087. void CAI_AssaultBehavior::OnStartSchedule( int scheduleType )
  1088. {
  1089. if ( scheduleType == SCHED_HIDE_AND_RELOAD ) //!!!HACKHACK
  1090. {
  1091. // Dirty the assault point flag so that we return to assault point
  1092. m_bHitAssaultPoint = false;
  1093. }
  1094. }
  1095. //-----------------------------------------------------------------------------
  1096. // Purpose:
  1097. //-----------------------------------------------------------------------------
  1098. void CAI_AssaultBehavior::ClearSchedule( const char *szReason )
  1099. {
  1100. // HACKHACK: In reality, we shouldn't be clearing the schedule ever if the assault
  1101. // behavior isn't actually in charge of the NPC. Fix after ship. For now, hacking
  1102. // a fix to Grigori failing to make it over the fence of the graveyard in d1_town_02a
  1103. if ( GetOuter()->ClassMatches( "npc_monk" ) && GetOuter()->GetState() == NPC_STATE_SCRIPT )
  1104. return;
  1105. // Don't allow it if we're in a vehicle
  1106. if ( GetOuter()->IsInAVehicle() )
  1107. return;
  1108. GetOuter()->ClearSchedule( szReason );
  1109. }
  1110. //-----------------------------------------------------------------------------
  1111. // Purpose:
  1112. //-----------------------------------------------------------------------------
  1113. bool CAI_AssaultBehavior::IsAllowedToDivert( void )
  1114. {
  1115. if ( m_hAssaultPoint && m_hAssaultPoint->m_bAllowDiversion )
  1116. {
  1117. if ( m_hAssaultPoint->m_flAllowDiversionRadius == 0.0f || (m_bHitAssaultPoint && GetEnemy() != NULL && GetEnemy()->GetAbsOrigin().DistToSqr(m_hAssaultPoint->GetAbsOrigin()) <= Square(m_hAssaultPoint->m_flAllowDiversionRadius)) )
  1118. {
  1119. if ( m_flLastSawAnEnemyAt && ((gpGlobals->curtime - m_flLastSawAnEnemyAt) < ASSAULT_DIVERSION_TIME) )
  1120. return true;
  1121. }
  1122. }
  1123. return false;
  1124. }
  1125. //-----------------------------------------------------------------------------
  1126. // Purpose:
  1127. //-----------------------------------------------------------------------------
  1128. void CAI_AssaultBehavior::BuildScheduleTestBits()
  1129. {
  1130. BaseClass::BuildScheduleTestBits();
  1131. // If we're allowed to divert, add the appropriate interrupts to our movement schedules
  1132. if ( IsAllowedToDivert() )
  1133. {
  1134. if ( IsCurSchedule( SCHED_MOVE_TO_ASSAULT_POINT ) ||
  1135. IsCurSchedule( SCHED_MOVE_TO_RALLY_POINT ) ||
  1136. IsCurSchedule( SCHED_HOLD_RALLY_POINT ) )
  1137. {
  1138. GetOuter()->SetCustomInterruptCondition( COND_NEW_ENEMY );
  1139. GetOuter()->SetCustomInterruptCondition( COND_SEE_ENEMY );
  1140. }
  1141. }
  1142. }
  1143. //-----------------------------------------------------------------------------
  1144. //-----------------------------------------------------------------------------
  1145. void CAI_AssaultBehavior::OnScheduleChange()
  1146. {
  1147. if( IsCurSchedule(SCHED_WAIT_AND_CLEAR, false) )
  1148. {
  1149. if( m_hAssaultPoint && m_hAssaultPoint->m_bClearOnContact )
  1150. {
  1151. if( HasCondition(COND_SEE_ENEMY) )
  1152. {
  1153. ClearAssaultPoint();
  1154. }
  1155. }
  1156. }
  1157. BaseClass::OnScheduleChange();
  1158. }
  1159. //-----------------------------------------------------------------------------
  1160. // Purpose:
  1161. // Output : int
  1162. //-----------------------------------------------------------------------------
  1163. int CAI_AssaultBehavior::SelectSchedule()
  1164. {
  1165. if ( !OnStrictAssault() )
  1166. {
  1167. if( HasCondition( COND_PLAYER_PUSHING ) )
  1168. return SCHED_ASSAULT_MOVE_AWAY;
  1169. if( HasCondition( COND_HEAR_DANGER ) )
  1170. return SCHED_TAKE_COVER_FROM_BEST_SOUND;
  1171. }
  1172. if( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
  1173. return SCHED_MELEE_ATTACK1;
  1174. // If you're empty, reload before trying to carry out any assault functions.
  1175. if( HasCondition( COND_NO_PRIMARY_AMMO ) )
  1176. return SCHED_RELOAD;
  1177. if( m_bHitRallyPoint && !m_bHitAssaultPoint && !AssaultHasBegun() )
  1178. {
  1179. // If I have hit my rally point, but I haven't hit my assault point yet,
  1180. // Make sure I'm still on my rally point, cause another behavior may have moved me.
  1181. // 2D check to be within 32 units of my rallypoint.
  1182. Vector vecDiff = GetAbsOrigin() - m_hRallyPoint->GetAbsOrigin();
  1183. vecDiff.z = 0.0;
  1184. if( vecDiff.LengthSqr() > Square(CUE_POINT_TOLERANCE) )
  1185. {
  1186. // Someone moved me away. Get back to rally point.
  1187. m_bHitRallyPoint = false;
  1188. return SCHED_MOVE_TO_RALLY_POINT;
  1189. }
  1190. }
  1191. else if( m_bHitAssaultPoint )
  1192. {
  1193. // Likewise. If I have hit my assault point, make sure I'm still there. Another
  1194. // behavior (hide and reload) may have moved me away.
  1195. Vector vecDiff = GetAbsOrigin() - m_hAssaultPoint->GetAbsOrigin();
  1196. vecDiff.z = 0.0;
  1197. if( vecDiff.LengthSqr() > Square(CUE_POINT_TOLERANCE) )
  1198. {
  1199. // Someone moved me away.
  1200. m_bHitAssaultPoint = false;
  1201. }
  1202. }
  1203. // Go to my rally point, unless the assault's begun.
  1204. if( !m_bHitRallyPoint && !AssaultHasBegun() )
  1205. {
  1206. GetOuter()->SpeakSentence( ASSAULT_SENTENCE_SQUAD_ADVANCE_TO_RALLY );
  1207. return SCHED_MOVE_TO_RALLY_POINT;
  1208. }
  1209. if( !m_bHitAssaultPoint )
  1210. {
  1211. if( m_ReceivedAssaultCue == m_AssaultCue || m_ReceivedAssaultCue == CUE_COMMANDER || m_AssaultCue == CUE_DONT_WAIT )
  1212. {
  1213. GetOuter()->SpeakSentence( ASSAULT_SENTENCE_SQUAD_ADVANCE_TO_ASSAULT );
  1214. if ( m_hRallyPoint && !m_hRallyPoint->IsExclusive() )
  1215. {
  1216. // If this assault chain is not exclusive, then free up the rallypoint so that others can follow me
  1217. // Otherwise, we do not unlock this rally point until we are FINISHED or DEAD. It's exclusively our chain of assault
  1218. UnlockRallyPoint();// Here we go! Free up the rally point since I'm moving to assault.
  1219. }
  1220. if ( !UpdateForceCrouch() )
  1221. {
  1222. GetOuter()->ClearForceCrouch();
  1223. }
  1224. return SCHED_MOVE_TO_ASSAULT_POINT;
  1225. }
  1226. else if( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  1227. {
  1228. return SCHED_RANGE_ATTACK1;
  1229. }
  1230. else if( HasCondition( COND_NO_PRIMARY_AMMO ) )
  1231. {
  1232. // Don't run off to reload.
  1233. return SCHED_RELOAD;
  1234. }
  1235. else if( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
  1236. {
  1237. GetOuter()->SpeakSentence( ASSAULT_SENTENCE_UNDER_ATTACK );
  1238. return SCHED_ALERT_FACE;
  1239. }
  1240. else if( GetOuter()->GetEnemy() && !HasCondition( COND_CAN_RANGE_ATTACK1 ) && !HasCondition( COND_CAN_RANGE_ATTACK2) && !HasCondition(COND_ENEMY_OCCLUDED) )
  1241. {
  1242. return SCHED_COMBAT_FACE;
  1243. }
  1244. else
  1245. {
  1246. UpdateForceCrouch();
  1247. return SCHED_HOLD_RALLY_POINT;
  1248. }
  1249. }
  1250. if( HasCondition( COND_NO_PRIMARY_AMMO ) )
  1251. {
  1252. GetOuter()->SpeakSentence( ASSAULT_SENTENCE_COVER_NO_AMMO );
  1253. return SCHED_HIDE_AND_RELOAD;
  1254. }
  1255. if( m_hAssaultPoint->HasSpawnFlags( SF_ASSAULTPOINT_CLEARONARRIVAL ) )
  1256. {
  1257. return SCHED_CLEAR_ASSAULT_POINT;
  1258. }
  1259. if ( (!GetEnemy() || HasCondition(COND_ENEMY_OCCLUDED)) && !GetOuter()->HasConditionsToInterruptSchedule( SCHED_WAIT_AND_CLEAR ) )
  1260. {
  1261. // Don't have an enemy. Just keep an eye on things.
  1262. return SCHED_WAIT_AND_CLEAR;
  1263. }
  1264. if ( OnStrictAssault() )
  1265. {
  1266. // Don't allow the base class to select a schedule cause it will probably move the NPC.
  1267. if( !HasCondition(COND_CAN_RANGE_ATTACK1) &&
  1268. !HasCondition(COND_CAN_RANGE_ATTACK2) &&
  1269. !HasCondition(COND_CAN_MELEE_ATTACK1) &&
  1270. !HasCondition(COND_CAN_MELEE_ATTACK2) &&
  1271. !HasCondition(COND_TOO_CLOSE_TO_ATTACK) &&
  1272. !HasCondition(COND_NOT_FACING_ATTACK) )
  1273. {
  1274. return SCHED_WAIT_AND_CLEAR;
  1275. }
  1276. }
  1277. #ifdef HL2_EPISODIC
  1278. // This ugly patch fixes a bug where Combine Soldiers on an assault would not shoot through glass, because of the way
  1279. // that shooting through glass is implemented in their AI. (sjb)
  1280. if( HasCondition(COND_SEE_ENEMY) && HasCondition(COND_WEAPON_SIGHT_OCCLUDED) && !HasCondition(COND_LOW_PRIMARY_AMMO) )
  1281. {
  1282. // If they are hiding behind something that we can destroy, start shooting at it.
  1283. CBaseEntity *pBlocker = GetOuter()->GetEnemyOccluder();
  1284. if ( pBlocker && pBlocker->GetHealth() > 0 )
  1285. {
  1286. if( GetOuter()->Classify() == CLASS_COMBINE && FClassnameIs(GetOuter(), "npc_combine_s") )
  1287. {
  1288. return SCHED_SHOOT_ENEMY_COVER;
  1289. }
  1290. }
  1291. }
  1292. #endif//HL2_EPISODIC
  1293. return BaseClass::SelectSchedule();
  1294. }
  1295. //-----------------------------------------------------------------------------
  1296. //
  1297. // CAI_AssaultGoal
  1298. //
  1299. // Purpose:
  1300. //
  1301. //
  1302. //-----------------------------------------------------------------------------
  1303. class CAI_AssaultGoal : public CAI_GoalEntity
  1304. {
  1305. typedef CAI_GoalEntity BaseClass;
  1306. virtual void EnableGoal( CAI_BaseNPC *pAI );
  1307. virtual void DisableGoal( CAI_BaseNPC *pAI );
  1308. string_t m_RallyPoint;
  1309. int m_AssaultCue;
  1310. int m_RallySelectMethod;
  1311. void InputBeginAssault( inputdata_t &inputdata );
  1312. DECLARE_DATADESC();
  1313. };
  1314. BEGIN_DATADESC( CAI_AssaultGoal )
  1315. DEFINE_KEYFIELD( m_RallyPoint, FIELD_STRING, "rallypoint" ),
  1316. DEFINE_KEYFIELD( m_AssaultCue, FIELD_INTEGER, "AssaultCue" ),
  1317. DEFINE_KEYFIELD( m_RallySelectMethod, FIELD_INTEGER, "RallySelectMethod" ),
  1318. DEFINE_INPUTFUNC( FIELD_VOID, "BeginAssault", InputBeginAssault ),
  1319. END_DATADESC();
  1320. //-------------------------------------
  1321. LINK_ENTITY_TO_CLASS( ai_goal_assault, CAI_AssaultGoal );
  1322. //-------------------------------------
  1323. //-----------------------------------------------------------------------------
  1324. // Purpose:
  1325. //-----------------------------------------------------------------------------
  1326. void CAI_AssaultGoal::EnableGoal( CAI_BaseNPC *pAI )
  1327. {
  1328. CAI_AssaultBehavior *pBehavior;
  1329. if ( !pAI->GetBehavior( &pBehavior ) )
  1330. return;
  1331. pBehavior->SetParameters( m_RallyPoint, (AssaultCue_t)m_AssaultCue, m_RallySelectMethod );
  1332. // Duplicate the output
  1333. }
  1334. //-----------------------------------------------------------------------------
  1335. // Purpose:
  1336. //-----------------------------------------------------------------------------
  1337. void CAI_AssaultGoal::DisableGoal( CAI_BaseNPC *pAI )
  1338. {
  1339. CAI_AssaultBehavior *pBehavior;
  1340. if ( pAI->GetBehavior( &pBehavior ) )
  1341. {
  1342. pBehavior->Disable();
  1343. // Don't leave any hanging rally points locked.
  1344. pBehavior->UnlockRallyPoint();
  1345. pBehavior->ClearSchedule( "Assault goal disabled" );
  1346. }
  1347. }
  1348. //-----------------------------------------------------------------------------
  1349. // Purpose: ENTITY I/O method for telling the assault behavior to cue assault
  1350. // Input : &inputdata -
  1351. //-----------------------------------------------------------------------------
  1352. void CAI_AssaultGoal::InputBeginAssault( inputdata_t &inputdata )
  1353. {
  1354. int i;
  1355. for( i = 0 ; i < NumActors() ; i++ )
  1356. {
  1357. CAI_BaseNPC *pActor = GetActor( i );
  1358. if( pActor )
  1359. {
  1360. // Now use this actor to lookup the Behavior
  1361. CAI_AssaultBehavior *pBehavior;
  1362. if( pActor->GetBehavior( &pBehavior ) )
  1363. {
  1364. // GOT IT! Now tell the behavior that entity i/o wants to cue the assault.
  1365. pBehavior->ReceiveAssaultCue( CUE_ENTITY_INPUT );
  1366. }
  1367. }
  1368. }
  1369. }
  1370. AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER(CAI_AssaultBehavior)
  1371. DECLARE_TASK(TASK_GET_PATH_TO_RALLY_POINT)
  1372. DECLARE_TASK(TASK_FACE_RALLY_POINT)
  1373. DECLARE_TASK(TASK_GET_PATH_TO_ASSAULT_POINT)
  1374. DECLARE_TASK(TASK_FACE_ASSAULT_POINT)
  1375. DECLARE_TASK(TASK_AWAIT_CUE)
  1376. DECLARE_TASK(TASK_AWAIT_ASSAULT_TIMEOUT)
  1377. DECLARE_TASK(TASK_ANNOUNCE_CLEAR)
  1378. DECLARE_TASK(TASK_WAIT_ASSAULT_DELAY)
  1379. DECLARE_TASK(TASK_HIT_ASSAULT_POINT)
  1380. DECLARE_TASK(TASK_HIT_RALLY_POINT)
  1381. DECLARE_TASK(TASK_ASSAULT_DEFER_SCHEDULE_SELECTION)
  1382. //=========================================================
  1383. //=========================================================
  1384. DEFINE_SCHEDULE
  1385. (
  1386. SCHED_MOVE_TO_RALLY_POINT,
  1387. " Tasks"
  1388. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSAULT_FAILED_TO_MOVE"
  1389. " TASK_GET_PATH_TO_RALLY_POINT 0"
  1390. " TASK_RUN_PATH 0"
  1391. " TASK_WAIT_FOR_MOVEMENT 0"
  1392. " TASK_STOP_MOVING 0"
  1393. " TASK_FACE_RALLY_POINT 0"
  1394. " TASK_HIT_RALLY_POINT 0"
  1395. " TASK_SET_SCHEDULE SCHEDULE:SCHED_HOLD_RALLY_POINT"
  1396. " "
  1397. " Interrupts"
  1398. " COND_HEAR_DANGER"
  1399. " COND_PROVOKED"
  1400. " COND_NO_PRIMARY_AMMO"
  1401. " COND_PLAYER_PUSHING"
  1402. )
  1403. //=========================================================
  1404. //=========================================================
  1405. DEFINE_SCHEDULE
  1406. (
  1407. SCHED_ASSAULT_FAILED_TO_MOVE,
  1408. " Tasks"
  1409. " TASK_ASSAULT_DEFER_SCHEDULE_SELECTION 1"
  1410. " "
  1411. " Interrupts"
  1412. )
  1413. //=========================================================
  1414. //=========================================================
  1415. DEFINE_SCHEDULE
  1416. (
  1417. SCHED_FAIL_MOVE_TO_RALLY_POINT,
  1418. " Tasks"
  1419. " TASK_WAIT 1"
  1420. " "
  1421. " Interrupts"
  1422. " COND_HEAR_DANGER"
  1423. " COND_CAN_RANGE_ATTACK1"
  1424. " COND_CAN_MELEE_ATTACK1"
  1425. )
  1426. #ifdef HL2_EPISODIC
  1427. //=========================================================
  1428. //=========================================================
  1429. DEFINE_SCHEDULE
  1430. (
  1431. SCHED_HOLD_RALLY_POINT,
  1432. " Tasks"
  1433. " TASK_FACE_RALLY_POINT 0"
  1434. " TASK_AWAIT_CUE 0"
  1435. " TASK_WAIT_ASSAULT_DELAY 0"
  1436. " "
  1437. " Interrupts"
  1438. //" COND_NEW_ENEMY"
  1439. " COND_CAN_RANGE_ATTACK1"
  1440. " COND_CAN_MELEE_ATTACK1"
  1441. " COND_LIGHT_DAMAGE"
  1442. " COND_HEAVY_DAMAGE"
  1443. " COND_PLAYER_PUSHING"
  1444. " COND_HEAR_DANGER"
  1445. " COND_HEAR_BULLET_IMPACT"
  1446. " COND_NO_PRIMARY_AMMO"
  1447. )
  1448. #else
  1449. //=========================================================
  1450. //=========================================================
  1451. DEFINE_SCHEDULE
  1452. (
  1453. SCHED_HOLD_RALLY_POINT,
  1454. " Tasks"
  1455. " TASK_FACE_RALLY_POINT 0"
  1456. " TASK_AWAIT_CUE 0"
  1457. " TASK_WAIT_ASSAULT_DELAY 0"
  1458. " "
  1459. " Interrupts"
  1460. " COND_NEW_ENEMY"
  1461. " COND_CAN_RANGE_ATTACK1"
  1462. " COND_CAN_MELEE_ATTACK1"
  1463. " COND_LIGHT_DAMAGE"
  1464. " COND_HEAVY_DAMAGE"
  1465. " COND_PLAYER_PUSHING"
  1466. " COND_HEAR_DANGER"
  1467. " COND_HEAR_BULLET_IMPACT"
  1468. " COND_NO_PRIMARY_AMMO"
  1469. " COND_TOO_CLOSE_TO_ATTACK"
  1470. )
  1471. #endif//HL2_EPISODIC
  1472. //=========================================================
  1473. //=========================================================
  1474. DEFINE_SCHEDULE
  1475. (
  1476. SCHED_HOLD_ASSAULT_POINT,
  1477. " Tasks"
  1478. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1479. " TASK_WAIT 3"
  1480. ""
  1481. " Interrupts"
  1482. " COND_NEW_ENEMY"
  1483. " COND_ENEMY_DEAD"
  1484. " COND_CAN_RANGE_ATTACK1"
  1485. " COND_CAN_MELEE_ATTACK1"
  1486. " COND_CAN_RANGE_ATTACK2"
  1487. " COND_CAN_MELEE_ATTACK2"
  1488. " COND_TOO_CLOSE_TO_ATTACK"
  1489. " COND_LOST_ENEMY"
  1490. " COND_HEAR_DANGER"
  1491. " COND_HEAR_BULLET_IMPACT"
  1492. " COND_NO_PRIMARY_AMMO"
  1493. )
  1494. //=========================================================
  1495. //=========================================================
  1496. DEFINE_SCHEDULE
  1497. (
  1498. SCHED_MOVE_TO_ASSAULT_POINT,
  1499. " Tasks"
  1500. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSAULT_FAILED_TO_MOVE"
  1501. " TASK_GATHER_CONDITIONS 0"
  1502. " TASK_GET_PATH_TO_ASSAULT_POINT 0"
  1503. " TASK_RUN_PATH 0"
  1504. " TASK_WAIT_FOR_MOVEMENT 0"
  1505. " TASK_FACE_ASSAULT_POINT 0"
  1506. " TASK_HIT_ASSAULT_POINT 0"
  1507. " "
  1508. " Interrupts"
  1509. " COND_NO_PRIMARY_AMMO"
  1510. " COND_HEAR_DANGER"
  1511. )
  1512. //=========================================================
  1513. //=========================================================
  1514. DEFINE_SCHEDULE
  1515. (
  1516. SCHED_AT_ASSAULT_POINT,
  1517. " Tasks"
  1518. " TASK_FACE_ASSAULT_POINT 0"
  1519. " TASK_HIT_ASSAULT_POINT 0"
  1520. " "
  1521. " Interrupts"
  1522. " COND_NO_PRIMARY_AMMO"
  1523. " COND_HEAR_DANGER"
  1524. )
  1525. //=========================================================
  1526. //=========================================================
  1527. DEFINE_SCHEDULE
  1528. (
  1529. SCHED_WAIT_AND_CLEAR,
  1530. " Tasks"
  1531. " TASK_FACE_ASSAULT_POINT 0"
  1532. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1533. " TASK_AWAIT_ASSAULT_TIMEOUT 0"
  1534. " TASK_ANNOUNCE_CLEAR 0"
  1535. " "
  1536. " Interrupts"
  1537. " COND_NEW_ENEMY"
  1538. " COND_LIGHT_DAMAGE"
  1539. " COND_HEAVY_DAMAGE"
  1540. " COND_CAN_RANGE_ATTACK1"
  1541. " COND_CAN_MELEE_ATTACK1"
  1542. " COND_CAN_RANGE_ATTACK2"
  1543. " COND_CAN_MELEE_ATTACK2"
  1544. " COND_HEAR_DANGER"
  1545. " COND_HEAR_BULLET_IMPACT"
  1546. " COND_TOO_CLOSE_TO_ATTACK"
  1547. " COND_NOT_FACING_ATTACK"
  1548. " COND_PLAYER_PUSHING"
  1549. )
  1550. //=========================================================
  1551. //=========================================================
  1552. DEFINE_SCHEDULE
  1553. (
  1554. SCHED_CLEAR_ASSAULT_POINT,
  1555. " Tasks"
  1556. " TASK_ANNOUNCE_CLEAR 0"
  1557. " "
  1558. " Interrupts"
  1559. )
  1560. //=========================================================
  1561. //=========================================================
  1562. DEFINE_SCHEDULE
  1563. (
  1564. SCHED_ASSAULT_MOVE_AWAY,
  1565. " Tasks"
  1566. " TASK_MOVE_AWAY_PATH 120"
  1567. " TASK_RUN_PATH 0"
  1568. " TASK_WAIT_FOR_MOVEMENT 0"
  1569. " "
  1570. " Interrupts"
  1571. )
  1572. AI_END_CUSTOM_SCHEDULE_PROVIDER()