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.

776 lines
20 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ai_behavior_functank.h"
  8. #include "ai_navigator.h"
  9. #include "ai_memory.h"
  10. #include "ai_senses.h"
  11. // memdbgon must be the last include file in a .cpp file!!!
  12. #include "tier0/memdbgon.h"
  13. // How long to fire a func tank before running schedule selection again.
  14. #define FUNCTANK_FIRE_TIME 5.0f
  15. BEGIN_DATADESC( CAI_FuncTankBehavior )
  16. DEFINE_FIELD( m_hFuncTank, FIELD_EHANDLE ),
  17. DEFINE_FIELD( m_bMounted, FIELD_BOOLEAN ),
  18. DEFINE_FIELD( m_flBusyTime, FIELD_TIME ),
  19. DEFINE_FIELD( m_bSpottedPlayerOutOfCover, FIELD_BOOLEAN ),
  20. END_DATADESC();
  21. //-----------------------------------------------------------------------------
  22. // Purpose: Constructor
  23. //--k---------------------------------------------------------------------------
  24. CAI_FuncTankBehavior::CAI_FuncTankBehavior()
  25. {
  26. m_hFuncTank = NULL;
  27. m_bMounted = false;
  28. m_flBusyTime = 0.0f;
  29. m_bSpottedPlayerOutOfCover = false;
  30. }
  31. //-----------------------------------------------------------------------------
  32. // Purpose: Deconstructor
  33. //-----------------------------------------------------------------------------
  34. CAI_FuncTankBehavior::~CAI_FuncTankBehavior()
  35. {
  36. }
  37. //-----------------------------------------------------------------------------
  38. // Purpose:
  39. //-----------------------------------------------------------------------------
  40. bool CAI_FuncTankBehavior::CanSelectSchedule()
  41. {
  42. // If we don't have a func_tank do not bother with conditions, schedules, etc.
  43. if ( !m_hFuncTank )
  44. return false;
  45. // Are you alive, in a script?
  46. if ( !GetOuter()->IsInterruptable() )
  47. return false;
  48. // Commander is giving you orders?
  49. if ( GetOuter()->HasCondition( COND_RECEIVED_ORDERS ) )
  50. return false;
  51. return true;
  52. }
  53. //-----------------------------------------------------------------------------
  54. // Purpose:
  55. //-----------------------------------------------------------------------------
  56. void CAI_FuncTankBehavior::BeginScheduleSelection()
  57. {
  58. }
  59. //-----------------------------------------------------------------------------
  60. // Purpose:
  61. //-----------------------------------------------------------------------------
  62. void CAI_FuncTankBehavior::EndScheduleSelection()
  63. {
  64. if ( m_bMounted )
  65. {
  66. Dismount();
  67. }
  68. }
  69. //-----------------------------------------------------------------------------
  70. // Purpose:
  71. //-----------------------------------------------------------------------------
  72. void CAI_FuncTankBehavior::PrescheduleThink()
  73. {
  74. BaseClass::PrescheduleThink();
  75. if ( !HasCondition(COND_SEE_PLAYER) )
  76. {
  77. m_bSpottedPlayerOutOfCover = false;
  78. }
  79. }
  80. //-----------------------------------------------------------------------------
  81. // Purpose:
  82. //-----------------------------------------------------------------------------
  83. int CAI_FuncTankBehavior::SelectSchedule()
  84. {
  85. // This shouldn't get called with an m_hFuncTank, see CanSelectSchedule.
  86. Assert( m_hFuncTank );
  87. // If we've been told to dismount, or we are out of ammo - dismount.
  88. if ( HasCondition( COND_FUNCTANK_DISMOUNT ) || m_hFuncTank->GetAmmoCount() == 0 )
  89. {
  90. if ( m_bMounted )
  91. {
  92. Dismount();
  93. }
  94. return BaseClass::SelectSchedule();
  95. }
  96. // If we are not mounted to a func_tank look for one.
  97. if ( !IsMounted() )
  98. {
  99. return SCHED_MOVE_TO_FUNCTANK;
  100. }
  101. // If we have an enemy, it's in the viewcone & we have LOS to it
  102. if ( GetEnemy() )
  103. {
  104. // Tell the func tank whenever we see the player for the first time since not seeing him for a while
  105. if ( HasCondition( COND_NEW_ENEMY ) && GetEnemy()->IsPlayer() && !m_bSpottedPlayerOutOfCover )
  106. {
  107. m_bSpottedPlayerOutOfCover = true;
  108. m_hFuncTank->NPC_JustSawPlayer( GetEnemy() );
  109. }
  110. // Fire at the enemy.
  111. return SCHED_FIRE_FUNCTANK;
  112. }
  113. else
  114. {
  115. // Scan for enemies.
  116. return SCHED_SCAN_WITH_FUNCTANK;
  117. }
  118. return SCHED_IDLE_STAND;
  119. }
  120. //-----------------------------------------------------------------------------
  121. // Purpose:
  122. // Input : activity -
  123. // Output : Activity
  124. //-----------------------------------------------------------------------------
  125. Activity CAI_FuncTankBehavior::NPC_TranslateActivity( Activity activity )
  126. {
  127. // If I'm on the gun, I play the idle manned gun animation
  128. if ( m_bMounted )
  129. return ACT_IDLE_MANNEDGUN;
  130. return BaseClass::NPC_TranslateActivity( activity );
  131. }
  132. //-----------------------------------------------------------------------------
  133. // Purpose:
  134. //-----------------------------------------------------------------------------
  135. void CAI_FuncTankBehavior::Dismount( void )
  136. {
  137. SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
  138. Assert( m_hFuncTank );
  139. if ( m_hFuncTank )
  140. {
  141. GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_DISMOUNTING );
  142. Assert( m_hFuncTank->IsMarkedForDeletion() || m_hFuncTank->GetController() == GetOuter() );
  143. m_hFuncTank->NPC_SetInRoute( false );
  144. if ( m_hFuncTank->GetController() == GetOuter() )
  145. m_hFuncTank->StopControl();
  146. SetFuncTank( NULL );
  147. }
  148. GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED );
  149. m_bMounted = false;
  150. // Set this condition to force breakout of any func_tank behavior schedules
  151. SetCondition( COND_FUNCTANK_DISMOUNT );
  152. }
  153. //-----------------------------------------------------------------------------
  154. // Purpose:
  155. // Input :
  156. // Output :
  157. //-----------------------------------------------------------------------------
  158. int CAI_FuncTankBehavior::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  159. {
  160. int iResult = BaseClass::OnTakeDamage_Alive( info );
  161. if ( !iResult )
  162. return 0;
  163. // If we've been hit by the player, and the player's not targetable
  164. // by our func_tank, get off the tank.
  165. CBaseEntity *pAttacker = info.GetAttacker();
  166. bool bValidDismountAttacker = (pAttacker && pAttacker->IsPlayer());
  167. #ifdef HL2_EPISODIC
  168. bValidDismountAttacker = true;
  169. #endif
  170. if ( m_hFuncTank && bValidDismountAttacker == true )
  171. {
  172. if ( !m_hFuncTank->IsEntityInViewCone( pAttacker ) )
  173. {
  174. SetCondition( COND_FUNCTANK_DISMOUNT );
  175. }
  176. }
  177. return iResult;
  178. }
  179. //-----------------------------------------------------------------------------
  180. // Purpose:
  181. //-----------------------------------------------------------------------------
  182. void CAI_FuncTankBehavior::StartTask( const Task_t *pTask )
  183. {
  184. switch ( pTask->iTask )
  185. {
  186. case TASK_FUNCTANK_ANNOUNCE_SCAN:
  187. {
  188. if ( random->RandomInt( 0, 3 ) == 0 )
  189. {
  190. GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_SCAN_FOR_ENEMIES );
  191. }
  192. TaskComplete();
  193. }
  194. break;
  195. case TASK_GET_PATH_TO_FUNCTANK:
  196. {
  197. if ( !m_hFuncTank )
  198. {
  199. TaskFail( FAIL_NO_TARGET );
  200. return;
  201. }
  202. Vector vecManPos;
  203. m_hFuncTank->NPC_FindManPoint( vecManPos );
  204. AI_NavGoal_t goal( vecManPos );
  205. goal.pTarget = m_hFuncTank;
  206. if ( GetNavigator()->SetGoal( goal ) )
  207. {
  208. GetNavigator()->SetArrivalDirection( m_hFuncTank->GetAbsAngles() );
  209. TaskComplete();
  210. }
  211. else
  212. {
  213. TaskFail("NO PATH");
  214. // Don't try and use me again for a while
  215. SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
  216. }
  217. break;
  218. }
  219. case TASK_FACE_FUNCTANK:
  220. {
  221. if ( !m_hFuncTank )
  222. {
  223. TaskFail( FAIL_NO_TARGET );
  224. return;
  225. }
  226. // Ensure we've reached the func_tank
  227. Vector vecManPos;
  228. m_hFuncTank->NPC_FindManPoint( vecManPos );
  229. // More leniency in Z.
  230. Vector vecDelta = (vecManPos - GetAbsOrigin());
  231. if ( fabs(vecDelta.x) > 16 || fabs(vecDelta.y) > 16 || fabs(vecDelta.z) > 48 )
  232. {
  233. TaskFail( "Not correctly on func_tank man point" );
  234. m_hFuncTank->NPC_InterruptRoute();
  235. return;
  236. }
  237. GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );
  238. GetOuter()->SetTurnActivity();
  239. break;
  240. }
  241. case TASK_HOLSTER_WEAPON:
  242. {
  243. if ( !m_hFuncTank )
  244. {
  245. TaskFail( FAIL_NO_TARGET );
  246. return;
  247. }
  248. if ( GetOuter()->IsWeaponHolstered() || !GetOuter()->CanHolsterWeapon() )
  249. {
  250. GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_JUST_MOUNTED );
  251. // We are at the correct position and facing for the func_tank, mount it.
  252. m_hFuncTank->StartControl( GetOuter() );
  253. GetOuter()->ClearEnemyMemory();
  254. m_bMounted = true;
  255. TaskComplete();
  256. GetOuter()->SetIdealActivity( ACT_IDLE_MANNEDGUN );
  257. }
  258. else
  259. {
  260. GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED );
  261. }
  262. break;
  263. }
  264. case TASK_FIRE_FUNCTANK:
  265. {
  266. if ( !m_hFuncTank )
  267. {
  268. TaskFail( FAIL_NO_TARGET );
  269. return;
  270. }
  271. GetOuter()->m_flWaitFinished = gpGlobals->curtime + FUNCTANK_FIRE_TIME;
  272. break;
  273. }
  274. case TASK_SCAN_LEFT_FUNCTANK:
  275. {
  276. if ( !m_hFuncTank )
  277. {
  278. TaskFail( FAIL_NO_TARGET );
  279. return;
  280. }
  281. GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );
  282. float flCenterYaw = m_hFuncTank->YawCenterWorld();
  283. float flYawRange = m_hFuncTank->YawRange();
  284. float flScanAmount = random->RandomFloat( 0, flYawRange );
  285. QAngle vecTargetAngles( 0, UTIL_AngleMod( flCenterYaw + flScanAmount ), 0 );
  286. /*
  287. float flCenterPitch = m_hFuncTank->YawCenterWorld();
  288. float flPitchRange = m_hFuncTank->PitchRange();
  289. float flPitch = random->RandomFloat( -flPitchRange, flPitchRange );
  290. QAngle vecTargetAngles( flCenterPitch + flPitch, UTIL_AngleMod( flCenterYaw + flScanAmount ), 0 );
  291. */
  292. Vector vecTargetForward;
  293. AngleVectors( vecTargetAngles, &vecTargetForward );
  294. Vector vecTarget = GetOuter()->EyePosition() + (vecTargetForward * 256);
  295. GetOuter()->AddLookTarget( vecTarget, 1.0, 2.0, 0.2 );
  296. m_hFuncTank->NPC_SetIdleAngle( vecTarget );
  297. break;
  298. }
  299. case TASK_SCAN_RIGHT_FUNCTANK:
  300. {
  301. if ( !m_hFuncTank )
  302. {
  303. TaskFail( FAIL_NO_TARGET );
  304. return;
  305. }
  306. GetMotor()->SetIdealYawToTarget( m_hFuncTank->GetAbsOrigin() );
  307. float flCenterYaw = m_hFuncTank->YawCenterWorld();
  308. float flYawRange = m_hFuncTank->YawRange();
  309. float flScanAmount = random->RandomFloat( 0, flYawRange );
  310. QAngle vecTargetAngles( 0, UTIL_AngleMod( flCenterYaw - flScanAmount ), 0 );
  311. /*
  312. float flCenterPitch = m_hFuncTank->YawCenterWorld();
  313. float flPitchRange = m_hFuncTank->PitchRange();
  314. float flPitch = random->RandomFloat( -flPitchRange, flPitchRange );
  315. QAngle vecTargetAngles( flCenterPitch + flPitch, UTIL_AngleMod( flCenterYaw - flScanAmount ), 0 );
  316. */
  317. Vector vecTargetForward;
  318. AngleVectors( vecTargetAngles, &vecTargetForward );
  319. Vector vecTarget = GetOuter()->EyePosition() + (vecTargetForward * 256);
  320. GetOuter()->AddLookTarget( vecTarget, 1.0, 2.0, 0.2 );
  321. m_hFuncTank->NPC_SetIdleAngle( vecTarget );
  322. break;
  323. }
  324. case TASK_FORGET_ABOUT_FUNCTANK:
  325. {
  326. if ( !m_hFuncTank )
  327. {
  328. TaskFail( FAIL_NO_TARGET );
  329. return;
  330. }
  331. break;
  332. }
  333. default:
  334. {
  335. BaseClass::StartTask( pTask );
  336. break;
  337. }
  338. }
  339. }
  340. //-----------------------------------------------------------------------------
  341. // Purpose:
  342. //-----------------------------------------------------------------------------
  343. void CAI_FuncTankBehavior::RunTask( const Task_t *pTask )
  344. {
  345. switch ( pTask->iTask )
  346. {
  347. case TASK_FACE_FUNCTANK:
  348. {
  349. Assert( m_hFuncTank );
  350. GetMotor()->UpdateYaw();
  351. if ( GetOuter()->FacingIdeal() )
  352. {
  353. TaskComplete();
  354. }
  355. break;
  356. }
  357. case TASK_HOLSTER_WEAPON:
  358. {
  359. Assert( m_hFuncTank );
  360. if ( GetOuter()->IsWeaponHolstered() )
  361. {
  362. GetOuter()->SpeakSentence( FUNCTANK_SENTENCE_JUST_MOUNTED );
  363. // We are at the correct position and facing for the func_tank, mount it.
  364. m_hFuncTank->StartControl( GetOuter() );
  365. GetOuter()->ClearEnemyMemory();
  366. m_bMounted = true;
  367. TaskComplete();
  368. GetOuter()->SetIdealActivity( ACT_IDLE_MANNEDGUN );
  369. }
  370. break;
  371. }
  372. case TASK_FIRE_FUNCTANK:
  373. {
  374. Assert( m_hFuncTank );
  375. if( GetOuter()->m_flWaitFinished < gpGlobals->curtime )
  376. {
  377. TaskComplete();
  378. }
  379. if ( m_hFuncTank->NPC_HasEnemy() )
  380. {
  381. GetOuter()->SetLastAttackTime( gpGlobals->curtime );
  382. m_hFuncTank->NPC_Fire();
  383. // The NPC may have decided to stop using the func_tank, because it's out of ammo.
  384. if ( !m_hFuncTank )
  385. {
  386. TaskComplete();
  387. break;
  388. }
  389. }
  390. else
  391. {
  392. TaskComplete();
  393. }
  394. Assert( m_hFuncTank );
  395. if ( m_hFuncTank->GetAmmoCount() == 0 )
  396. {
  397. TaskComplete();
  398. }
  399. break;
  400. }
  401. case TASK_SCAN_LEFT_FUNCTANK:
  402. case TASK_SCAN_RIGHT_FUNCTANK:
  403. {
  404. GetMotor()->UpdateYaw();
  405. if ( GetOuter()->FacingIdeal() )
  406. {
  407. TaskComplete();
  408. }
  409. break;
  410. }
  411. case TASK_FORGET_ABOUT_FUNCTANK:
  412. {
  413. m_hFuncTank->NPC_InterruptRoute();
  414. SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
  415. TaskComplete();
  416. break;
  417. }
  418. default:
  419. {
  420. BaseClass::RunTask( pTask );
  421. break;
  422. }
  423. }
  424. }
  425. //-----------------------------------------------------------------------------
  426. // Purpose:
  427. //-----------------------------------------------------------------------------
  428. void CAI_FuncTankBehavior::Event_Killed( const CTakeDamageInfo &info )
  429. {
  430. if ( m_hFuncTank )
  431. {
  432. Dismount();
  433. }
  434. Assert( !m_hFuncTank );
  435. BaseClass::Event_Killed( info );
  436. }
  437. //-----------------------------------------------------------------------------
  438. // Purpose:
  439. //-----------------------------------------------------------------------------
  440. void CAI_FuncTankBehavior::UpdateOnRemove( void )
  441. {
  442. if ( m_hFuncTank )
  443. {
  444. Dismount();
  445. }
  446. BaseClass::UpdateOnRemove();
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose:
  450. //-----------------------------------------------------------------------------
  451. void CAI_FuncTankBehavior::SetFuncTank( CHandle<CFuncTank> hFuncTank )
  452. {
  453. if ( m_hFuncTank && !hFuncTank )
  454. {
  455. SetBusy( gpGlobals->curtime + AI_FUNCTANK_BEHAVIOR_BUSYTIME );
  456. SetCondition( COND_FUNCTANK_DISMOUNT );
  457. }
  458. m_hFuncTank = hFuncTank;
  459. GetOuter()->ClearSchedule( "Setting a new func_tank" );
  460. }
  461. //-----------------------------------------------------------------------------
  462. // Purpose:
  463. //-----------------------------------------------------------------------------
  464. void CAI_FuncTankBehavior::AimGun( void )
  465. {
  466. if ( m_bMounted && m_hFuncTank)
  467. {
  468. Vector vecForward;
  469. AngleVectors( m_hFuncTank->GetAbsAngles(), &vecForward );
  470. GetOuter()->SetAim( vecForward );
  471. return;
  472. }
  473. BaseClass::AimGun();
  474. }
  475. //-----------------------------------------------------------------------------
  476. // Purpose:
  477. //-----------------------------------------------------------------------------
  478. void CAI_FuncTankBehavior::GatherConditions()
  479. {
  480. BaseClass::GatherConditions();
  481. // Since we can't pathfind, if we can't see the enemy, he's eluded us
  482. // So we deliberately ignore unreachability
  483. if ( GetEnemy() && !HasCondition(COND_SEE_ENEMY) )
  484. {
  485. if ( gpGlobals->curtime - GetOuter()->GetEnemyLastTimeSeen() >= 3.0f )
  486. {
  487. GetOuter()->MarkEnemyAsEluded();
  488. }
  489. }
  490. if ( !m_hFuncTank )
  491. {
  492. m_bMounted = false;
  493. GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED );
  494. }
  495. }
  496. //-----------------------------------------------------------------------------
  497. // Purpose:
  498. //-----------------------------------------------------------------------------
  499. CBaseEntity *CAI_FuncTankBehavior::BestEnemy( void )
  500. {
  501. // Only use this BestEnemy call when we are on the manned gun.
  502. if ( !m_hFuncTank ||!IsMounted() )
  503. return BaseClass::BestEnemy();
  504. CBaseEntity *pBestEnemy = NULL;
  505. int iBestDistSq = MAX_COORD_RANGE * MAX_COORD_RANGE; // so first visible entity will become the closest.
  506. int iBestPriority = -1000;
  507. bool bBestUnreachable = false; // Forces initial check
  508. bool bBestSeen = false;
  509. bool bUnreachable = false;
  510. int iDistSq;
  511. AIEnemiesIter_t iter;
  512. // Get the current npc for checking from.
  513. CAI_BaseNPC *pNPC = GetOuter();
  514. if ( !pNPC )
  515. return NULL;
  516. for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) )
  517. {
  518. CBaseEntity *pEnemy = pEMemory->hEnemy;
  519. if ( !pEnemy || !pEnemy->IsAlive() )
  520. continue;
  521. // UNDONE: Move relationship checks into IsValidEnemy?
  522. if ( ( pEnemy->GetFlags() & FL_NOTARGET ) ||
  523. ( pNPC->IRelationType( pEnemy ) != D_HT && pNPC->IRelationType( pEnemy ) != D_FR ) ||
  524. !IsValidEnemy( pEnemy ) )
  525. continue;
  526. if ( pEMemory->timeLastSeen < pNPC->GetAcceptableTimeSeenEnemy() )
  527. continue;
  528. if ( pEMemory->timeValidEnemy > gpGlobals->curtime )
  529. continue;
  530. // Skip enemies that have eluded me to prevent infinite loops
  531. if ( GetEnemies()->HasEludedMe( pEnemy ) )
  532. continue;
  533. // Establish the reachability of this enemy
  534. bUnreachable = pNPC->IsUnreachable( pEnemy );
  535. // Check view cone of the view tank here.
  536. bUnreachable = !m_hFuncTank->IsEntityInViewCone( pEnemy );
  537. if ( !bUnreachable )
  538. {
  539. // It's in the viewcone. Now make sure we have LOS to it.
  540. bUnreachable = !m_hFuncTank->HasLOSTo( pEnemy );
  541. }
  542. // If best is reachable and current is unreachable, skip the unreachable enemy regardless of priority
  543. if ( !bBestUnreachable && bUnreachable )
  544. continue;
  545. // If best is unreachable and current is reachable, always pick the current regardless of priority
  546. if ( bBestUnreachable && !bUnreachable )
  547. {
  548. bBestSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) ); // @TODO (toml 04-02-03): Need to optimize CanSeeEntity() so multiple calls in frame do not recalculate, rather cache
  549. iBestPriority = pNPC->IRelationPriority( pEnemy );
  550. iBestDistSq = (pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  551. pBestEnemy = pEnemy;
  552. bBestUnreachable = bUnreachable;
  553. }
  554. // If both are unreachable or both are reachable, chose enemy based on priority and distance
  555. else if ( pNPC->IRelationPriority( pEnemy ) > iBestPriority )
  556. {
  557. // this entity is disliked MORE than the entity that we
  558. // currently think is the best visible enemy. No need to do
  559. // a distance check, just get mad at this one for now.
  560. iBestPriority = pNPC->IRelationPriority ( pEnemy );
  561. iBestDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  562. pBestEnemy = pEnemy;
  563. bBestUnreachable = bUnreachable;
  564. }
  565. else if ( pNPC->IRelationPriority( pEnemy ) == iBestPriority )
  566. {
  567. // this entity is disliked just as much as the entity that
  568. // we currently think is the best visible enemy, so we only
  569. // get mad at it if it is closer.
  570. iDistSq = ( pEnemy->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  571. bool bCloser = ( iDistSq < iBestDistSq ) ;
  572. if ( bCloser || !bBestSeen )
  573. {
  574. // @TODO (toml 04-02-03): Need to optimize FVisible() so multiple calls in frame do not recalculate, rather cache
  575. bool fSeen = ( pNPC->GetSenses()->DidSeeEntity( pEnemy ) || pNPC->FVisible( pEnemy ) );
  576. if ( ( bCloser && ( fSeen || !bBestSeen ) ) || ( !bCloser && !bBestSeen && fSeen ) )
  577. {
  578. bBestSeen = fSeen;
  579. iBestDistSq = iDistSq;
  580. iBestPriority = pNPC->IRelationPriority( pEnemy );
  581. pBestEnemy = pEnemy;
  582. bBestUnreachable = bUnreachable;
  583. }
  584. }
  585. }
  586. }
  587. return pBestEnemy;
  588. }
  589. //=============================================================================
  590. //
  591. // Custom AI schedule data
  592. //
  593. AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FuncTankBehavior )
  594. DECLARE_TASK( TASK_GET_PATH_TO_FUNCTANK )
  595. DECLARE_TASK( TASK_FACE_FUNCTANK )
  596. DECLARE_TASK( TASK_HOLSTER_WEAPON )
  597. DECLARE_TASK( TASK_FIRE_FUNCTANK )
  598. DECLARE_TASK( TASK_SCAN_LEFT_FUNCTANK )
  599. DECLARE_TASK( TASK_SCAN_RIGHT_FUNCTANK )
  600. DECLARE_TASK( TASK_FORGET_ABOUT_FUNCTANK )
  601. DECLARE_TASK( TASK_FUNCTANK_ANNOUNCE_SCAN )
  602. DECLARE_CONDITION( COND_FUNCTANK_DISMOUNT )
  603. //=========================================================
  604. //=========================================================
  605. DEFINE_SCHEDULE
  606. (
  607. SCHED_MOVE_TO_FUNCTANK,
  608. " Tasks"
  609. " TASK_SET_FAIL_SCHEDULE SCHEDULE: SCHED_FAIL_MOVE_TO_FUNCTANK"
  610. " TASK_GET_PATH_TO_FUNCTANK 0"
  611. " TASK_SPEAK_SENTENCE 1000" // FUNCTANK_SENTENCE_MOVE_TO_MOUNT
  612. " TASK_RUN_PATH 0"
  613. " TASK_WAIT_FOR_MOVEMENT 0"
  614. " TASK_STOP_MOVING 0"
  615. " TASK_FACE_FUNCTANK 0"
  616. " TASK_HOLSTER_WEAPON 0"
  617. " "
  618. " Interrupts"
  619. " COND_FUNCTANK_DISMOUNT"
  620. )
  621. //=========================================================
  622. //=========================================================
  623. DEFINE_SCHEDULE
  624. (
  625. SCHED_FIRE_FUNCTANK,
  626. " Tasks"
  627. " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
  628. " TASK_FIRE_FUNCTANK 0"
  629. " "
  630. " Interrupts"
  631. " COND_NEW_ENEMY"
  632. " COND_ENEMY_DEAD"
  633. " COND_LOST_ENEMY"
  634. " COND_ENEMY_OCCLUDED"
  635. " COND_WEAPON_BLOCKED_BY_FRIEND"
  636. " COND_WEAPON_SIGHT_OCCLUDED"
  637. " COND_FUNCTANK_DISMOUNT"
  638. )
  639. DEFINE_SCHEDULE
  640. (
  641. SCHED_SCAN_WITH_FUNCTANK,
  642. " Tasks"
  643. " TASK_FUNCTANK_ANNOUNCE_SCAN 0"
  644. " TASK_STOP_MOVING 0"
  645. " TASK_WAIT 4"
  646. " TASK_SCAN_LEFT_FUNCTANK 0"
  647. " TASK_WAIT 4"
  648. " TASK_SCAN_RIGHT_FUNCTANK 0"
  649. ""
  650. " Interrupts"
  651. " COND_NEW_ENEMY"
  652. " COND_PROVOKED"
  653. " COND_FUNCTANK_DISMOUNT"
  654. )
  655. DEFINE_SCHEDULE
  656. (
  657. SCHED_FAIL_MOVE_TO_FUNCTANK,
  658. " Tasks"
  659. " TASK_FORGET_ABOUT_FUNCTANK 0"
  660. ""
  661. " Interrupts"
  662. )
  663. AI_END_CUSTOM_SCHEDULE_PROVIDER()