Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1928 lines
56 KiB

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