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.

1346 lines
38 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Combat behaviors for AIs in a relatively self-preservationist mode.
  4. // Lots of cover taking and attempted shots out of cover.
  5. //
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "ai_hint.h"
  9. #include "ai_node.h"
  10. #include "ai_navigator.h"
  11. #include "ai_tacticalservices.h"
  12. #include "ai_behavior_standoff.h"
  13. #include "ai_senses.h"
  14. #include "ai_squad.h"
  15. #include "ai_goalentity.h"
  16. #include "ndebugoverlay.h"
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. #define GOAL_POSITION_INVALID Vector( FLT_MAX, FLT_MAX, FLT_MAX )
  20. ConVar DrawBattleLines( "ai_drawbattlelines", "0", FCVAR_CHEAT );
  21. // XXX(JohnS): The old parameters below triggered a warning -- fPlayerIsBattleline field used to be "1.5" which
  22. // implicitly cast to true. Given that there are two floats followed by three ints, it seems all these
  23. // fields are off-by-one, but this code hasn't been touched in a very long time so I'm going to avoid
  24. // changing its behavior drastically now. It probably has never used the originally intended values. The
  25. // new values are just expanding what they would've been implicitly filled with.
  26. //
  27. // static AI_StandoffParams_t AI_DEFAULT_STANDOFF_PARAMS = { AIHCR_MOVE_ON_COVER, true, 1.5, 2.5, 1, 3, 25, 0 };
  28. static AI_StandoffParams_t AI_DEFAULT_STANDOFF_PARAMS = { AIHCR_MOVE_ON_COVER, true, true, 2.5, 1., 3, 25, 0, false, 0.f };
  29. // Suspected originally intended values:
  30. //
  31. // static AI_StandoffParams_t AI_DEFAULT_STANDOFF_PARAMS = { AIHCR_MOVE_ON_COVER, true, true(?), 1.5, 2.5, 1, 3, 25, false(?), 0.(?) };
  32. #define MAKE_ACTMAP_KEY( posture, activity ) ( (((unsigned)(posture)) << 16) | ((unsigned)(activity)) )
  33. // #define DEBUG_STANDOFF 1
  34. #ifdef DEBUG_STANDOFF
  35. #define StandoffMsg( msg ) DevMsg( GetOuter(), msg )
  36. #define StandoffMsg1( msg, a ) DevMsg( GetOuter(), msg, a )
  37. #define StandoffMsg2( msg, a, b ) DevMsg( GetOuter(), msg, a, b )
  38. #define StandoffMsg3( msg, a, b, c ) DevMsg( GetOuter(), msg, a, b, c )
  39. #define StandoffMsg4( msg, a, b, c, d ) DevMsg( GetOuter(), msg, a, b, c, d )
  40. #define StandoffMsg5( msg, a, b, c, d, e ) DevMsg( GetOuter(), msg, a, b, c, d, e )
  41. #else
  42. #define StandoffMsg( msg ) ((void)0)
  43. #define StandoffMsg1( msg, a ) ((void)0)
  44. #define StandoffMsg2( msg, a, b ) ((void)0)
  45. #define StandoffMsg3( msg, a, b, c ) ((void)0)
  46. #define StandoffMsg4( msg, a, b, c, d ) ((void)0)
  47. #define StandoffMsg5( msg, a, b, c, d, e ) ((void)0)
  48. #endif
  49. //-----------------------------------------------------------------------------
  50. //
  51. // CAI_BattleLine
  52. //
  53. //-----------------------------------------------------------------------------
  54. const float AIBL_THINK_INTERVAL = 0.3;
  55. class CAI_BattleLine : public CBaseEntity
  56. {
  57. DECLARE_CLASS( CAI_BattleLine, CBaseEntity );
  58. public:
  59. string_t m_iszActor;
  60. bool m_fActive;
  61. bool m_fStrict;
  62. void Spawn()
  63. {
  64. if ( m_fActive )
  65. {
  66. SetThink(&CAI_BattleLine::MovementThink);
  67. SetNextThink( gpGlobals->curtime + AIBL_THINK_INTERVAL );
  68. m_SelfMoveMonitor.SetMark( this, 60 );
  69. }
  70. }
  71. virtual void InputActivate( inputdata_t &inputdata )
  72. {
  73. if ( !m_fActive )
  74. {
  75. m_fActive = true;
  76. NotifyChangeTacticalConstraints();
  77. SetThink(&CAI_BattleLine::MovementThink);
  78. SetNextThink( gpGlobals->curtime + AIBL_THINK_INTERVAL );
  79. m_SelfMoveMonitor.SetMark( this, 60 );
  80. }
  81. }
  82. virtual void InputDeactivate( inputdata_t &inputdata )
  83. {
  84. if ( m_fActive )
  85. {
  86. m_fActive = false;
  87. NotifyChangeTacticalConstraints();
  88. SetThink(NULL);
  89. }
  90. }
  91. void UpdateOnRemove()
  92. {
  93. if ( m_fActive )
  94. {
  95. m_fActive = false;
  96. NotifyChangeTacticalConstraints();
  97. }
  98. BaseClass::UpdateOnRemove();
  99. }
  100. bool Affects( CAI_BaseNPC *pNpc )
  101. {
  102. const char *pszNamedActor = STRING( m_iszActor );
  103. if ( pNpc->NameMatches( pszNamedActor ) ||
  104. pNpc->ClassMatches( pszNamedActor ) ||
  105. ( pNpc->GetSquad() && stricmp( pNpc->GetSquad()->GetName(), pszNamedActor ) == 0 ) )
  106. {
  107. return true;
  108. }
  109. return false;
  110. }
  111. void MovementThink()
  112. {
  113. if ( m_SelfMoveMonitor.TargetMoved( this ) )
  114. {
  115. NotifyChangeTacticalConstraints();
  116. m_SelfMoveMonitor.SetMark( this, 60 );
  117. }
  118. SetNextThink( gpGlobals->curtime + AIBL_THINK_INTERVAL );
  119. }
  120. private:
  121. void NotifyChangeTacticalConstraints()
  122. {
  123. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  124. {
  125. CAI_BaseNPC *pNpc = (g_AI_Manager.AccessAIs())[i];
  126. if ( Affects( pNpc ) )
  127. {
  128. CAI_StandoffBehavior *pBehavior;
  129. if ( pNpc->GetBehavior( &pBehavior ) )
  130. {
  131. pBehavior->OnChangeTacticalConstraints();
  132. }
  133. }
  134. }
  135. }
  136. CAI_MoveMonitor m_SelfMoveMonitor;
  137. DECLARE_DATADESC();
  138. };
  139. //-------------------------------------
  140. LINK_ENTITY_TO_CLASS( ai_battle_line, CAI_BattleLine );
  141. BEGIN_DATADESC( CAI_BattleLine )
  142. DEFINE_KEYFIELD( m_iszActor, FIELD_STRING, "Actor" ),
  143. DEFINE_KEYFIELD( m_fActive, FIELD_BOOLEAN, "Active" ),
  144. DEFINE_KEYFIELD( m_fStrict, FIELD_BOOLEAN, "Strict" ),
  145. DEFINE_EMBEDDED( m_SelfMoveMonitor ),
  146. // Inputs
  147. DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
  148. DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
  149. DEFINE_THINKFUNC( MovementThink ),
  150. END_DATADESC()
  151. //-----------------------------------------------------------------------------
  152. //
  153. // CAI_StandoffBehavior
  154. //
  155. //-----------------------------------------------------------------------------
  156. BEGIN_SIMPLE_DATADESC( AI_StandoffParams_t )
  157. DEFINE_FIELD( hintChangeReaction, FIELD_INTEGER ),
  158. DEFINE_FIELD( fPlayerIsBattleline, FIELD_BOOLEAN ),
  159. DEFINE_FIELD( fCoverOnReload, FIELD_BOOLEAN ),
  160. DEFINE_FIELD( minTimeShots, FIELD_FLOAT ),
  161. DEFINE_FIELD( maxTimeShots, FIELD_FLOAT ),
  162. DEFINE_FIELD( minShots, FIELD_INTEGER ),
  163. DEFINE_FIELD( maxShots, FIELD_INTEGER ),
  164. DEFINE_FIELD( oddsCover, FIELD_INTEGER ),
  165. DEFINE_FIELD( fStayAtCover, FIELD_BOOLEAN ),
  166. DEFINE_FIELD( flAbandonTimeLimit, FIELD_FLOAT ),
  167. END_DATADESC();
  168. BEGIN_DATADESC( CAI_StandoffBehavior )
  169. DEFINE_FIELD( m_fActive, FIELD_BOOLEAN ),
  170. DEFINE_FIELD( m_fTestNoDamage, FIELD_BOOLEAN ),
  171. DEFINE_FIELD( m_vecStandoffGoalPosition, FIELD_POSITION_VECTOR ),
  172. DEFINE_FIELD( m_posture, FIELD_INTEGER ),
  173. DEFINE_EMBEDDED( m_params ),
  174. DEFINE_FIELD( m_hStandoffGoal, FIELD_EHANDLE ),
  175. DEFINE_FIELD( m_fTakeCover, FIELD_BOOLEAN ),
  176. DEFINE_FIELD( m_SavedDistTooFar, FIELD_FLOAT ),
  177. DEFINE_FIELD( m_fForceNewEnemy, FIELD_BOOLEAN ),
  178. DEFINE_EMBEDDED( m_PlayerMoveMonitor ),
  179. DEFINE_EMBEDDED( m_TimeForceCoverHint ),
  180. DEFINE_EMBEDDED( m_TimePreventForceNewEnemy ),
  181. DEFINE_EMBEDDED( m_RandomCoverChangeTimer ),
  182. // m_UpdateBattleLinesSemaphore (not saved, only an in-think item)
  183. // m_BattleLines (not saved, rebuilt)
  184. DEFINE_FIELD( m_fIgnoreFronts, FIELD_BOOLEAN ),
  185. // m_ActivityMap (not saved, rebuilt)
  186. // m_bHasLowCoverActivity (not saved, rebuilt)
  187. DEFINE_FIELD( m_nSavedMinShots, FIELD_INTEGER ),
  188. DEFINE_FIELD( m_nSavedMaxShots, FIELD_INTEGER ),
  189. DEFINE_FIELD( m_flSavedMinRest, FIELD_FLOAT ),
  190. DEFINE_FIELD( m_flSavedMaxRest, FIELD_FLOAT ),
  191. END_DATADESC();
  192. //-------------------------------------
  193. CAI_StandoffBehavior::CAI_StandoffBehavior( CAI_BaseNPC *pOuter )
  194. : CAI_MappedActivityBehavior_Temporary( pOuter )
  195. {
  196. m_fActive = false;
  197. SetParameters( AI_DEFAULT_STANDOFF_PARAMS );
  198. SetPosture( AIP_STANDING );
  199. m_SavedDistTooFar = FLT_MAX;
  200. m_fForceNewEnemy = false;
  201. m_TimePreventForceNewEnemy.Set( 3.0, 6.0 );
  202. m_fIgnoreFronts = false;
  203. m_bHasLowCoverActivity = false;
  204. }
  205. //-------------------------------------
  206. void CAI_StandoffBehavior::SetActive( bool fActive )
  207. {
  208. if ( fActive != m_fActive )
  209. {
  210. if ( fActive )
  211. {
  212. GetOuter()->SpeakSentence( STANDOFF_SENTENCE_BEGIN_STANDOFF );
  213. }
  214. else
  215. {
  216. GetOuter()->SpeakSentence( STANDOFF_SENTENCE_END_STANDOFF );
  217. }
  218. m_fActive = fActive;
  219. NotifyChangeBehaviorStatus();
  220. }
  221. }
  222. //-------------------------------------
  223. void CAI_StandoffBehavior::SetParameters( const AI_StandoffParams_t &params, CAI_GoalEntity *pGoalEntity )
  224. {
  225. m_params = params;
  226. m_hStandoffGoal = pGoalEntity;
  227. m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
  228. if ( GetOuter() && GetOuter()->GetShotRegulator() )
  229. {
  230. GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
  231. GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
  232. }
  233. }
  234. //-------------------------------------
  235. bool CAI_StandoffBehavior::CanSelectSchedule()
  236. {
  237. if ( !m_bHasLowCoverActivity )
  238. m_fActive = false;
  239. if ( !m_fActive )
  240. return false;
  241. return ( GetNpcState() == NPC_STATE_COMBAT && GetOuter()->GetActiveWeapon() != NULL );
  242. }
  243. //-------------------------------------
  244. void CAI_StandoffBehavior::Spawn()
  245. {
  246. BaseClass::Spawn();
  247. UpdateTranslateActivityMap();
  248. }
  249. //-------------------------------------
  250. void CAI_StandoffBehavior::BeginScheduleSelection()
  251. {
  252. m_fTakeCover = true;
  253. // FIXME: Improve!!!
  254. GetOuter()->GetShotRegulator()->GetBurstShotCountRange( &m_nSavedMinShots, &m_nSavedMaxShots );
  255. GetOuter()->GetShotRegulator()->GetRestInterval( &m_flSavedMinRest, &m_flSavedMaxRest );
  256. GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
  257. GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
  258. GetOuter()->GetShotRegulator()->Reset();
  259. m_SavedDistTooFar = GetOuter()->m_flDistTooFar;
  260. GetOuter()->m_flDistTooFar = FLT_MAX;
  261. m_TimeForceCoverHint.Set( 8, false );
  262. m_RandomCoverChangeTimer.Set( 8, 16, false );
  263. UpdateTranslateActivityMap();
  264. }
  265. void CAI_StandoffBehavior::OnUpdateShotRegulator()
  266. {
  267. GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_params.minShots, m_params.maxShots );
  268. GetOuter()->GetShotRegulator()->SetRestInterval( m_params.minTimeShots, m_params.maxTimeShots );
  269. }
  270. //-------------------------------------
  271. void CAI_StandoffBehavior::EndScheduleSelection()
  272. {
  273. UnlockHintNode();
  274. m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
  275. GetOuter()->m_flDistTooFar = m_SavedDistTooFar;
  276. // FIXME: Improve!!!
  277. GetOuter()->GetShotRegulator()->SetBurstShotCountRange( m_nSavedMinShots, m_nSavedMaxShots );
  278. GetOuter()->GetShotRegulator()->SetRestInterval( m_flSavedMinRest, m_flSavedMaxRest );
  279. }
  280. //-------------------------------------
  281. void CAI_StandoffBehavior::PrescheduleThink()
  282. {
  283. VPROF_BUDGET( "CAI_StandoffBehavior::PrescheduleThink", VPROF_BUDGETGROUP_NPCS );
  284. BaseClass::PrescheduleThink();
  285. if( DrawBattleLines.GetInt() != 0 )
  286. {
  287. CBaseEntity *pEntity = NULL;
  288. while ((pEntity = gEntList.FindEntityByClassname( pEntity, "ai_battle_line" )) != NULL)
  289. {
  290. // Visualize the battle line and its normal.
  291. CAI_BattleLine *pLine = dynamic_cast<CAI_BattleLine *>(pEntity);
  292. if( pLine->m_fActive )
  293. {
  294. Vector normal;
  295. pLine->GetVectors( &normal, NULL, NULL );
  296. NDebugOverlay::Line( pLine->GetAbsOrigin() - Vector( 0, 0, 64 ), pLine->GetAbsOrigin() + Vector(0,0,64), 0,255,0, false, 0.1 );
  297. }
  298. }
  299. }
  300. }
  301. //-------------------------------------
  302. void CAI_StandoffBehavior::GatherConditions()
  303. {
  304. CBaseEntity *pLeader = GetPlayerLeader();
  305. if ( pLeader && m_TimeForceCoverHint.Expired() )
  306. {
  307. if ( m_PlayerMoveMonitor.IsMarkSet() )
  308. {
  309. if (m_PlayerMoveMonitor.TargetMoved( pLeader ) )
  310. {
  311. OnChangeTacticalConstraints();
  312. m_PlayerMoveMonitor.ClearMark();
  313. }
  314. }
  315. else
  316. {
  317. m_PlayerMoveMonitor.SetMark( pLeader, 60 );
  318. }
  319. }
  320. if ( m_fForceNewEnemy )
  321. {
  322. m_TimePreventForceNewEnemy.Reset();
  323. GetOuter()->SetEnemy( NULL );
  324. }
  325. BaseClass::GatherConditions();
  326. m_fForceNewEnemy = false;
  327. ClearCondition( COND_ABANDON_TIME_EXPIRED );
  328. bool bAbandonStandoff = false;
  329. CAI_Squad *pSquad = GetOuter()->GetSquad();
  330. AISquadIter_t iter;
  331. if ( GetEnemy() )
  332. {
  333. AI_EnemyInfo_t *pEnemyInfo = GetOuter()->GetEnemies()->Find( GetEnemy() );
  334. if ( pEnemyInfo &&
  335. m_params.flAbandonTimeLimit > 0 &&
  336. ( ( pEnemyInfo->timeAtFirstHand != AI_INVALID_TIME &&
  337. gpGlobals->curtime - pEnemyInfo->timeLastSeen > m_params.flAbandonTimeLimit ) ||
  338. ( pEnemyInfo->timeAtFirstHand == AI_INVALID_TIME &&
  339. gpGlobals->curtime - pEnemyInfo->timeFirstSeen > m_params.flAbandonTimeLimit * 2 ) ) )
  340. {
  341. SetCondition( COND_ABANDON_TIME_EXPIRED );
  342. bAbandonStandoff = true;
  343. if ( pSquad )
  344. {
  345. for ( CAI_BaseNPC *pSquadMate = pSquad->GetFirstMember( &iter ); pSquadMate; pSquadMate = pSquad->GetNextMember( &iter ) )
  346. {
  347. if ( pSquadMate->IsAlive() && pSquadMate != GetOuter() )
  348. {
  349. CAI_StandoffBehavior *pSquadmateStandoff;
  350. pSquadMate->GetBehavior( &pSquadmateStandoff );
  351. if ( pSquadmateStandoff && pSquadmateStandoff->IsActive() &&
  352. pSquadmateStandoff->m_hStandoffGoal == m_hStandoffGoal &&
  353. !pSquadmateStandoff->HasCondition( COND_ABANDON_TIME_EXPIRED ) )
  354. {
  355. bAbandonStandoff = false;
  356. break;
  357. }
  358. }
  359. }
  360. }
  361. }
  362. }
  363. if ( bAbandonStandoff )
  364. {
  365. if ( pSquad )
  366. {
  367. for ( CAI_BaseNPC *pSquadMate = pSquad->GetFirstMember( &iter ); pSquadMate; pSquadMate = pSquad->GetNextMember( &iter ) )
  368. {
  369. CAI_StandoffBehavior *pSquadmateStandoff;
  370. pSquadMate->GetBehavior( &pSquadmateStandoff );
  371. if ( pSquadmateStandoff && pSquadmateStandoff->IsActive() && pSquadmateStandoff->m_hStandoffGoal == m_hStandoffGoal )
  372. pSquadmateStandoff->SetActive( false );
  373. }
  374. }
  375. else
  376. SetActive( false );
  377. }
  378. else if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  379. {
  380. if( DrawBattleLines.GetInt() != 0 )
  381. {
  382. if ( IsBehindBattleLines( GetAbsOrigin() ) )
  383. {
  384. NDebugOverlay::Box( GetOuter()->GetAbsOrigin(), -Vector(48,48,4), Vector(48,48,4), 255,0,0,8, 0.1 );
  385. }
  386. else
  387. {
  388. NDebugOverlay::Box( GetOuter()->GetAbsOrigin(), -Vector(48,48,4), Vector(48,48,4), 0,255,0,8, 0.1 );
  389. }
  390. }
  391. }
  392. }
  393. //-------------------------------------
  394. int CAI_StandoffBehavior::SelectScheduleUpdateWeapon( void )
  395. {
  396. // Check if need to reload
  397. if ( HasCondition ( COND_NO_PRIMARY_AMMO ) || HasCondition ( COND_LOW_PRIMARY_AMMO ))
  398. {
  399. StandoffMsg( "Out of ammo, reloading\n" );
  400. if ( m_params.fCoverOnReload )
  401. {
  402. GetOuter()->SpeakSentence( STANDOFF_SENTENCE_OUT_OF_AMMO );
  403. return SCHED_HIDE_AND_RELOAD;
  404. }
  405. return SCHED_RELOAD;
  406. }
  407. // Otherwise, update planned shots to fire before taking cover again
  408. if ( HasCondition( COND_LIGHT_DAMAGE ) )
  409. {
  410. // if hurt:
  411. int iPercent = random->RandomInt(0,99);
  412. if ( iPercent <= m_params.oddsCover && GetEnemy() != NULL )
  413. {
  414. SetReuseCurrentCover();
  415. StandoffMsg( "Hurt, firing one more shot before cover\n" );
  416. if ( !GetOuter()->GetShotRegulator()->IsInRestInterval() )
  417. {
  418. GetOuter()->GetShotRegulator()->SetBurstShotsRemaining( 1 );
  419. }
  420. }
  421. }
  422. return SCHED_NONE;
  423. }
  424. //-------------------------------------
  425. int CAI_StandoffBehavior::SelectScheduleCheckCover( void )
  426. {
  427. if ( m_fTakeCover )
  428. {
  429. m_fTakeCover = false;
  430. if ( GetEnemy() )
  431. {
  432. GetOuter()->SpeakSentence( STANDOFF_SENTENCE_FORCED_TAKE_COVER );
  433. StandoffMsg( "Taking forced cover\n" );
  434. return SCHED_TAKE_COVER_FROM_ENEMY;
  435. }
  436. }
  437. if ( GetOuter()->GetShotRegulator()->IsInRestInterval() )
  438. {
  439. StandoffMsg( "Regulated to not shoot\n" );
  440. if ( GetHintType() == HINT_TACTICAL_COVER_LOW )
  441. SetPosture( AIP_CROUCHING );
  442. else
  443. SetPosture( AIP_STANDING );
  444. if ( random->RandomInt(0,99) < 80 )
  445. SetReuseCurrentCover();
  446. return SCHED_TAKE_COVER_FROM_ENEMY;
  447. }
  448. return SCHED_NONE;
  449. }
  450. //-------------------------------------
  451. int CAI_StandoffBehavior::SelectScheduleEstablishAim( void )
  452. {
  453. if ( HasCondition( COND_ENEMY_OCCLUDED ) )
  454. {
  455. if ( GetPosture() == AIP_CROUCHING )
  456. {
  457. // force a stand up, just in case
  458. GetOuter()->SpeakSentence( STANDOFF_SENTENCE_STAND_CHECK_TARGET );
  459. StandoffMsg( "Crouching, standing up to gain LOS\n" );
  460. SetPosture( AIP_PEEKING );
  461. return SCHED_STANDOFF;
  462. }
  463. else if ( GetPosture() == AIP_PEEKING )
  464. {
  465. if ( m_TimePreventForceNewEnemy.Expired() )
  466. {
  467. // Look for a new enemy
  468. m_fForceNewEnemy = true;
  469. StandoffMsg( "Looking for enemy\n" );
  470. }
  471. }
  472. #if 0
  473. else
  474. {
  475. return SCHED_ESTABLISH_LINE_OF_FIRE;
  476. }
  477. #endif
  478. }
  479. return SCHED_NONE;
  480. }
  481. //-------------------------------------
  482. int CAI_StandoffBehavior::SelectScheduleAttack( void )
  483. {
  484. if ( GetPosture() == AIP_PEEKING || GetPosture() == AIP_STANDING )
  485. {
  486. if ( !HasCondition( COND_CAN_RANGE_ATTACK1 ) &&
  487. !HasCondition( COND_CAN_MELEE_ATTACK1 ) &&
  488. HasCondition( COND_TOO_FAR_TO_ATTACK ) )
  489. {
  490. if ( GetOuter()->GetActiveWeapon() && ( GetOuter()->GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 ) )
  491. {
  492. if ( !HasCondition( COND_ENEMY_OCCLUDED ) || random->RandomInt(0,99) < 50 )
  493. // Don't advance, just fire anyway
  494. return SCHED_RANGE_ATTACK1;
  495. }
  496. }
  497. }
  498. return SCHED_NONE;
  499. }
  500. //-------------------------------------
  501. int CAI_StandoffBehavior::SelectSchedule( void )
  502. {
  503. switch ( GetNpcState() )
  504. {
  505. case NPC_STATE_COMBAT:
  506. {
  507. int schedule = SCHED_NONE;
  508. schedule = SelectScheduleUpdateWeapon();
  509. if ( schedule != SCHED_NONE )
  510. return schedule;
  511. schedule = SelectScheduleCheckCover();
  512. if ( schedule != SCHED_NONE )
  513. return schedule;
  514. schedule = SelectScheduleEstablishAim();
  515. if ( schedule != SCHED_NONE )
  516. return schedule;
  517. schedule = SelectScheduleAttack();
  518. if ( schedule != SCHED_NONE )
  519. return schedule;
  520. break;
  521. }
  522. }
  523. return BaseClass::SelectSchedule();
  524. }
  525. //-------------------------------------
  526. int CAI_StandoffBehavior::TranslateSchedule( int schedule )
  527. {
  528. if ( schedule == SCHED_CHASE_ENEMY )
  529. {
  530. StandoffMsg( "trying SCHED_ESTABLISH_LINE_OF_FIRE\n" );
  531. return SCHED_ESTABLISH_LINE_OF_FIRE;
  532. }
  533. return BaseClass::TranslateSchedule( schedule );
  534. }
  535. //-------------------------------------
  536. void CAI_StandoffBehavior::BuildScheduleTestBits()
  537. {
  538. BaseClass::BuildScheduleTestBits();
  539. if ( IsCurSchedule( SCHED_TAKE_COVER_FROM_ENEMY ) )
  540. GetOuter()->ClearCustomInterruptCondition( COND_NEW_ENEMY );
  541. }
  542. //-------------------------------------
  543. Activity CAI_MappedActivityBehavior_Temporary::GetMappedActivity( AI_Posture_t posture, Activity activity )
  544. {
  545. if ( posture != AIP_STANDING )
  546. {
  547. unsigned short iActivityTranslation = m_ActivityMap.Find( MAKE_ACTMAP_KEY( posture, activity ) );
  548. if ( iActivityTranslation != m_ActivityMap.InvalidIndex() )
  549. {
  550. Activity result = m_ActivityMap[iActivityTranslation];
  551. return result;
  552. }
  553. }
  554. return ACT_INVALID;
  555. }
  556. //-------------------------------------
  557. Activity CAI_StandoffBehavior::NPC_TranslateActivity( Activity activity )
  558. {
  559. Activity coverActivity = GetCoverActivity();
  560. if ( coverActivity != ACT_INVALID )
  561. {
  562. if ( activity == ACT_IDLE )
  563. activity = coverActivity;
  564. if ( GetPosture() == AIP_STANDING && coverActivity == ACT_COVER_LOW )
  565. SetPosture( AIP_CROUCHING );
  566. }
  567. Activity result = GetMappedActivity( GetPosture(), activity );
  568. if ( result != ACT_INVALID)
  569. return result;
  570. return BaseClass::NPC_TranslateActivity( activity );
  571. }
  572. //-----------------------------------------------------------------------------
  573. // Purpose:
  574. // Input : &vecPos -
  575. //-----------------------------------------------------------------------------
  576. void CAI_StandoffBehavior::SetStandoffGoalPosition( const Vector &vecPos )
  577. {
  578. m_vecStandoffGoalPosition = vecPos;
  579. UpdateBattleLines();
  580. OnChangeTacticalConstraints();
  581. GetOuter()->ClearSchedule( "Standoff goal position changed" );
  582. }
  583. //-----------------------------------------------------------------------------
  584. // Purpose:
  585. // Input : &vecPos -
  586. //-----------------------------------------------------------------------------
  587. void CAI_StandoffBehavior::ClearStandoffGoalPosition()
  588. {
  589. if ( m_vecStandoffGoalPosition != GOAL_POSITION_INVALID )
  590. {
  591. m_vecStandoffGoalPosition = GOAL_POSITION_INVALID;
  592. UpdateBattleLines();
  593. OnChangeTacticalConstraints();
  594. GetOuter()->ClearSchedule( "Standoff goal position cleared" );
  595. }
  596. }
  597. //-----------------------------------------------------------------------------
  598. // Purpose:
  599. // Output : Vector
  600. //-----------------------------------------------------------------------------
  601. Vector CAI_StandoffBehavior::GetStandoffGoalPosition()
  602. {
  603. if( m_vecStandoffGoalPosition != GOAL_POSITION_INVALID )
  604. {
  605. return m_vecStandoffGoalPosition;
  606. }
  607. else if( PlayerIsLeading() )
  608. {
  609. return UTIL_GetLocalPlayer()->GetAbsOrigin();
  610. }
  611. else
  612. {
  613. CAI_BattleLine *pBattleLine = NULL;
  614. for (;;)
  615. {
  616. pBattleLine = (CAI_BattleLine *)gEntList.FindEntityByClassname( pBattleLine, "ai_battle_line" );
  617. if ( !pBattleLine )
  618. break;
  619. if ( pBattleLine->m_fActive && pBattleLine->Affects( GetOuter() ) )
  620. {
  621. StandoffMsg1( "Using battleline %s as goal\n", STRING( pBattleLine->GetEntityName() ) );
  622. return pBattleLine->GetAbsOrigin();
  623. }
  624. }
  625. }
  626. return GetAbsOrigin();
  627. }
  628. //-------------------------------------
  629. void CAI_StandoffBehavior::UpdateBattleLines()
  630. {
  631. if ( m_UpdateBattleLinesSemaphore.EnterThink() )
  632. {
  633. // @TODO (toml 06-19-03): This is the quick to code thing. Could use some optimization/caching to not recalc everything (up to) each think
  634. m_BattleLines.RemoveAll();
  635. bool bHaveGoalPosition = ( m_vecStandoffGoalPosition != GOAL_POSITION_INVALID );
  636. if ( bHaveGoalPosition )
  637. {
  638. // If we have a valid standoff goal position, it takes precendence.
  639. const float DIST_GOAL_PLANE = 180;
  640. BattleLine_t goalLine;
  641. if ( GetDirectionOfStandoff( &goalLine.normal ) )
  642. {
  643. goalLine.point = GetStandoffGoalPosition() + goalLine.normal * DIST_GOAL_PLANE;
  644. m_BattleLines.AddToTail( goalLine );
  645. }
  646. }
  647. else if ( PlayerIsLeading() && GetEnemy() )
  648. {
  649. if ( m_params.fPlayerIsBattleline )
  650. {
  651. const float DIST_PLAYER_PLANE = 180;
  652. CBaseEntity *pPlayer = UTIL_GetLocalPlayer();
  653. BattleLine_t playerLine;
  654. if ( GetDirectionOfStandoff( &playerLine.normal ) )
  655. {
  656. playerLine.point = pPlayer->GetAbsOrigin() + playerLine.normal * DIST_PLAYER_PLANE;
  657. m_BattleLines.AddToTail( playerLine );
  658. }
  659. }
  660. }
  661. CAI_BattleLine *pBattleLine = NULL;
  662. for (;;)
  663. {
  664. pBattleLine = (CAI_BattleLine *)gEntList.FindEntityByClassname( pBattleLine, "ai_battle_line" );
  665. if ( !pBattleLine )
  666. break;
  667. if ( pBattleLine->m_fActive && (pBattleLine->m_fStrict || !bHaveGoalPosition ) && pBattleLine->Affects( GetOuter() ) )
  668. {
  669. BattleLine_t battleLine;
  670. battleLine.point = pBattleLine->GetAbsOrigin();
  671. battleLine.normal = UTIL_YawToVector( pBattleLine->GetAbsAngles().y );
  672. m_BattleLines.AddToTail( battleLine );
  673. }
  674. }
  675. }
  676. }
  677. //-------------------------------------
  678. bool CAI_StandoffBehavior::IsBehindBattleLines( const Vector &point )
  679. {
  680. UpdateBattleLines();
  681. Vector vecToPoint;
  682. for ( int i = 0; i < m_BattleLines.Count(); i++ )
  683. {
  684. vecToPoint = point - m_BattleLines[i].point;
  685. VectorNormalize( vecToPoint );
  686. vecToPoint.z = 0;
  687. if ( DotProduct( m_BattleLines[i].normal, vecToPoint ) > 0 )
  688. {
  689. if( DrawBattleLines.GetInt() != 0 )
  690. {
  691. NDebugOverlay::Box( point, -Vector(48,48,4), Vector(48,48,4), 0,255,0,8, 1 );
  692. NDebugOverlay::Line( point, GetOuter()->GetAbsOrigin(), 0,255,0,true, 1 );
  693. }
  694. return false;
  695. }
  696. }
  697. if( DrawBattleLines.GetInt() != 0 )
  698. {
  699. NDebugOverlay::Box( point, -Vector(48,48,4), Vector(48,48,4), 255,0,0,8, 1 );
  700. NDebugOverlay::Line( point, GetOuter()->GetAbsOrigin(), 255,0,0,true, 1 );
  701. }
  702. return true;
  703. }
  704. //-------------------------------------
  705. bool CAI_StandoffBehavior::IsValidCover( const Vector &vecCoverLocation, const CAI_Hint *pHint )
  706. {
  707. if ( !BaseClass::IsValidCover( vecCoverLocation, pHint ) )
  708. return false;
  709. if ( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) )
  710. return true;
  711. return ( m_fIgnoreFronts || IsBehindBattleLines( vecCoverLocation ) );
  712. }
  713. //-------------------------------------
  714. bool CAI_StandoffBehavior::IsValidShootPosition( const Vector &vLocation, CAI_Node *pNode, const CAI_Hint *pHint )
  715. {
  716. if ( !BaseClass::IsValidShootPosition( vLocation, pNode, pHint ) )
  717. return false;
  718. return ( m_fIgnoreFronts || IsBehindBattleLines( vLocation ) );
  719. }
  720. //-------------------------------------
  721. void CAI_StandoffBehavior::StartTask( const Task_t *pTask )
  722. {
  723. bool fCallBase = false;
  724. switch ( pTask->iTask )
  725. {
  726. case TASK_RANGE_ATTACK1:
  727. {
  728. fCallBase = true;
  729. break;
  730. }
  731. case TASK_FIND_COVER_FROM_ENEMY:
  732. {
  733. StandoffMsg( "TASK_FIND_COVER_FROM_ENEMY\n" );
  734. // If within time window to force change
  735. if ( !m_params.fStayAtCover && (!m_TimeForceCoverHint.Expired() || m_RandomCoverChangeTimer.Expired()) )
  736. {
  737. m_TimeForceCoverHint.Force();
  738. m_RandomCoverChangeTimer.Set( 8, 16, false );
  739. // @TODO (toml 03-24-03): clean this up be tool-izing base tasks. Right now, this is here to force to not use lateral cover search
  740. CBaseEntity *pEntity = GetEnemy();
  741. if ( pEntity == NULL )
  742. {
  743. // Find cover from self if no enemy available
  744. pEntity = GetOuter();
  745. }
  746. CBaseEntity *pLeader = GetPlayerLeader();
  747. if ( pLeader )
  748. {
  749. m_PlayerMoveMonitor.SetMark( pLeader, 60 );
  750. }
  751. Vector coverPos = vec3_origin;
  752. CAI_TacticalServices * pTacticalServices = GetTacticalServices();
  753. const Vector & enemyPos = pEntity->GetAbsOrigin();
  754. Vector enemyEyePos = pEntity->EyePosition();
  755. float coverRadius = GetOuter()->CoverRadius();
  756. const Vector & goalPos = GetStandoffGoalPosition();
  757. bool bTryGoalPosFirst = true;
  758. if( pLeader && m_vecStandoffGoalPosition == GOAL_POSITION_INVALID )
  759. {
  760. if( random->RandomInt(1, 100) <= 50 )
  761. {
  762. // Half the time, if the player is leading, try to find a spot near them
  763. bTryGoalPosFirst = false;
  764. StandoffMsg( "Not trying goal pos\n" );
  765. }
  766. }
  767. if( bTryGoalPosFirst )
  768. {
  769. // Firstly, try to find cover near the goal position.
  770. pTacticalServices->FindCoverPos( goalPos, enemyPos, enemyEyePos, 0, 15*12, &coverPos );
  771. if ( coverPos == vec3_origin )
  772. pTacticalServices->FindCoverPos( goalPos, enemyPos, enemyEyePos, 15*12-0.1, 40*12, &coverPos );
  773. StandoffMsg1( "Trying goal pos, %s\n", ( coverPos == vec3_origin ) ? "failed" : "succeeded" );
  774. }
  775. if ( coverPos == vec3_origin )
  776. {
  777. // Otherwise, find a node near to self
  778. StandoffMsg( "Looking for near cover\n" );
  779. if ( !GetTacticalServices()->FindCoverPos( enemyPos, enemyEyePos, 0, coverRadius, &coverPos ) )
  780. {
  781. // Try local lateral cover
  782. if ( !GetTacticalServices()->FindLateralCover( enemyEyePos, 0, &coverPos ) )
  783. {
  784. // At this point, try again ignoring front lines. Any cover probably better than hanging out in the open
  785. m_fIgnoreFronts = true;
  786. if ( !GetTacticalServices()->FindCoverPos( enemyPos, enemyEyePos, 0, coverRadius, &coverPos ) )
  787. {
  788. if ( !GetTacticalServices()->FindLateralCover( enemyEyePos, 0, &coverPos ) )
  789. {
  790. Assert( coverPos == vec3_origin );
  791. }
  792. }
  793. m_fIgnoreFronts = false;
  794. }
  795. }
  796. }
  797. if ( coverPos != vec3_origin )
  798. {
  799. AI_NavGoal_t goal(GOALTYPE_COVER, coverPos, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS);
  800. GetNavigator()->SetGoal( goal );
  801. GetOuter()->m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
  802. TaskComplete();
  803. }
  804. else
  805. TaskFail(FAIL_NO_COVER);
  806. }
  807. else
  808. {
  809. fCallBase = true;
  810. }
  811. break;
  812. }
  813. default:
  814. {
  815. fCallBase = true;
  816. }
  817. }
  818. if ( fCallBase )
  819. BaseClass::StartTask( pTask );
  820. }
  821. //-------------------------------------
  822. void CAI_StandoffBehavior::OnChangeHintGroup( string_t oldGroup, string_t newGroup )
  823. {
  824. OnChangeTacticalConstraints();
  825. }
  826. //-------------------------------------
  827. void CAI_StandoffBehavior::OnChangeTacticalConstraints()
  828. {
  829. if ( m_params.hintChangeReaction > AIHCR_DEFAULT_AI )
  830. m_TimeForceCoverHint.Set( 8.0, false );
  831. if ( m_params.hintChangeReaction == AIHCR_MOVE_IMMEDIATE )
  832. m_fTakeCover = true;
  833. }
  834. //-------------------------------------
  835. bool CAI_StandoffBehavior::PlayerIsLeading()
  836. {
  837. CBaseEntity *pPlayer = AI_GetSinglePlayer();
  838. return ( pPlayer && GetOuter()->IRelationType( pPlayer ) == D_LI );
  839. }
  840. //-------------------------------------
  841. CBaseEntity *CAI_StandoffBehavior::GetPlayerLeader()
  842. {
  843. CBaseEntity *pPlayer = AI_GetSinglePlayer();
  844. if ( pPlayer && GetOuter()->IRelationType( pPlayer ) == D_LI )
  845. return pPlayer;
  846. return NULL;
  847. }
  848. //-------------------------------------
  849. bool CAI_StandoffBehavior::GetDirectionOfStandoff( Vector *pDir )
  850. {
  851. if ( GetEnemy() )
  852. {
  853. *pDir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
  854. VectorNormalize( *pDir );
  855. pDir->z = 0;
  856. return true;
  857. }
  858. return false;
  859. }
  860. //-------------------------------------
  861. Hint_e CAI_StandoffBehavior::GetHintType()
  862. {
  863. CAI_Hint *pHintNode = GetHintNode();
  864. if ( pHintNode )
  865. return pHintNode->HintType();
  866. return HINT_NONE;
  867. }
  868. //-------------------------------------
  869. void CAI_StandoffBehavior::SetReuseCurrentCover()
  870. {
  871. CAI_Hint *pHintNode = GetHintNode();
  872. if ( pHintNode && pHintNode->GetNode() && pHintNode->GetNode()->IsLocked() )
  873. pHintNode->GetNode()->Unlock();
  874. }
  875. //-------------------------------------
  876. void CAI_StandoffBehavior::UnlockHintNode()
  877. {
  878. CAI_Hint *pHintNode = GetHintNode();
  879. if ( pHintNode )
  880. {
  881. if ( pHintNode->IsLocked() && pHintNode->IsLockedBy( GetOuter() ) )
  882. pHintNode->Unlock();
  883. CAI_Node *pNode = pHintNode->GetNode();
  884. if ( pNode && pNode->IsLocked() )
  885. pNode->Unlock();
  886. ClearHintNode();
  887. }
  888. }
  889. //-------------------------------------
  890. Activity CAI_StandoffBehavior::GetCoverActivity()
  891. {
  892. CAI_Hint *pHintNode = GetHintNode();
  893. if ( pHintNode && pHintNode->HintType() == HINT_TACTICAL_COVER_LOW )
  894. return GetOuter()->GetCoverActivity( pHintNode );
  895. return ACT_INVALID;
  896. }
  897. //-------------------------------------
  898. struct AI_ActivityMapping_t
  899. {
  900. AI_Posture_t posture;
  901. Activity activity;
  902. const char * pszWeapon;
  903. Activity translation;
  904. };
  905. void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap()
  906. {
  907. AI_ActivityMapping_t mappings[] = // This array cannot be static, as some activity values are set on a per-map-load basis
  908. {
  909. { AIP_CROUCHING, ACT_IDLE, NULL, ACT_COVER_LOW, },
  910. { AIP_CROUCHING, ACT_IDLE_ANGRY, NULL, ACT_COVER_LOW, },
  911. { AIP_CROUCHING, ACT_WALK, NULL, ACT_WALK_CROUCH, },
  912. { AIP_CROUCHING, ACT_RUN, NULL, ACT_RUN_CROUCH, },
  913. { AIP_CROUCHING, ACT_WALK_AIM, NULL, ACT_WALK_CROUCH_AIM, },
  914. { AIP_CROUCHING, ACT_RUN_AIM, NULL, ACT_RUN_CROUCH_AIM, },
  915. { AIP_CROUCHING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, },
  916. { AIP_CROUCHING, ACT_RANGE_ATTACK_SMG1, NULL, ACT_RANGE_ATTACK_SMG1_LOW, },
  917. { AIP_CROUCHING, ACT_RANGE_ATTACK_AR2, NULL, ACT_RANGE_ATTACK_AR2_LOW, },
  918. //----
  919. { AIP_PEEKING, ACT_IDLE, NULL, ACT_RANGE_AIM_LOW, },
  920. { AIP_PEEKING, ACT_IDLE_ANGRY, NULL, ACT_RANGE_AIM_LOW, },
  921. { AIP_PEEKING, ACT_COVER_LOW, NULL, ACT_RANGE_AIM_LOW, },
  922. { AIP_PEEKING, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_LOW, },
  923. { AIP_PEEKING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, },
  924. };
  925. m_ActivityMap.RemoveAll();
  926. CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon();
  927. const char *pszWeaponClass = ( pWeapon ) ? pWeapon->GetClassname() : "";
  928. for ( int i = 0; i < ARRAYSIZE(mappings); i++ )
  929. {
  930. if ( !mappings[i].pszWeapon || stricmp( mappings[i].pszWeapon, pszWeaponClass ) == 0 )
  931. {
  932. if ( HaveSequenceForActivity( mappings[i].translation ) || HaveSequenceForActivity( GetOuter()->Weapon_TranslateActivity( mappings[i].translation ) ) )
  933. {
  934. Assert( m_ActivityMap.Find( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ) ) == m_ActivityMap.InvalidIndex() );
  935. m_ActivityMap.Insert( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ), mappings[i].translation );
  936. }
  937. }
  938. }
  939. }
  940. void CAI_StandoffBehavior::UpdateTranslateActivityMap()
  941. {
  942. BaseClass::UpdateTranslateActivityMap();
  943. Activity lowCoverActivity = GetMappedActivity( AIP_CROUCHING, ACT_COVER_LOW );
  944. if ( lowCoverActivity == ACT_INVALID )
  945. lowCoverActivity = ACT_COVER_LOW;
  946. m_bHasLowCoverActivity = ( ( CapabilitiesGet() & bits_CAP_DUCK ) && (GetOuter()->TranslateActivity( lowCoverActivity ) != ACT_INVALID));
  947. CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon();
  948. if ( pWeapon && (GetOuter()->TranslateActivity( lowCoverActivity ) == ACT_INVALID ))
  949. DevMsg( "Note: NPC class %s lacks ACT_COVER_LOW, therefore cannot participate in standoff\n", GetOuter()->GetClassname() );
  950. }
  951. //-------------------------------------
  952. void CAI_MappedActivityBehavior_Temporary::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
  953. {
  954. UpdateTranslateActivityMap();
  955. }
  956. //-------------------------------------
  957. void CAI_StandoffBehavior::OnRestore()
  958. {
  959. UpdateTranslateActivityMap();
  960. }
  961. //-------------------------------------
  962. AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER(CAI_StandoffBehavior)
  963. DECLARE_CONDITION( COND_ABANDON_TIME_EXPIRED )
  964. AI_END_CUSTOM_SCHEDULE_PROVIDER()
  965. //-----------------------------------------------------------------------------
  966. //
  967. // CAI_StandoffGoal
  968. //
  969. // Purpose: A level tool to control the standoff behavior. Use is not required
  970. // in order to use behavior.
  971. //
  972. //-----------------------------------------------------------------------------
  973. AI_StandoffParams_t g_StandoffParamsByAgression[] =
  974. {
  975. // hintChangeReaction, fCoverOnReload, PlayerBtlLn, minTimeShots, maxTimeShots, minShots, maxShots, oddsCover flAbandonTimeLimit
  976. { AIHCR_MOVE_ON_COVER, true, true, 4.0, 8.0, 2, 4, 50, false, 30 }, // AGGR_VERY_LOW
  977. { AIHCR_MOVE_ON_COVER, true, true, 2.0, 5.0, 3, 5, 25, false, 20 }, // AGGR_LOW
  978. { AIHCR_MOVE_ON_COVER, true, true, 0.6, 2.5, 3, 6, 25, false, 10 }, // AGGR_MEDIUM
  979. { AIHCR_MOVE_ON_COVER, true, true, 0.2, 1.5, 5, 8, 10, false, 10 }, // AGGR_HIGH
  980. { AIHCR_MOVE_ON_COVER, false, true, 0, 0, 100, 100, 0, false, 5 }, // AGGR_VERY_HIGH
  981. };
  982. //-------------------------------------
  983. class CAI_StandoffGoal : public CAI_GoalEntity
  984. {
  985. DECLARE_CLASS( CAI_StandoffGoal, CAI_GoalEntity );
  986. public:
  987. CAI_StandoffGoal()
  988. {
  989. m_aggressiveness = AGGR_MEDIUM;
  990. m_fPlayerIsBattleline = true;
  991. m_HintChangeReaction = AIHCR_DEFAULT_AI;
  992. m_fStayAtCover = false;
  993. m_bAbandonIfEnemyHides = false;
  994. m_customParams = AI_DEFAULT_STANDOFF_PARAMS;
  995. }
  996. //---------------------------------
  997. void EnableGoal( CAI_BaseNPC *pAI )
  998. {
  999. CAI_StandoffBehavior *pBehavior;
  1000. if ( !pAI->GetBehavior( &pBehavior ) )
  1001. return;
  1002. pBehavior->SetActive( true );
  1003. SetBehaviorParams( pBehavior);
  1004. }
  1005. void DisableGoal( CAI_BaseNPC *pAI )
  1006. {
  1007. // @TODO (toml 04-07-03): remove the no damage spawn flag once stable. The implementation isn't very good.
  1008. CAI_StandoffBehavior *pBehavior;
  1009. if ( !pAI->GetBehavior( &pBehavior ) )
  1010. return;
  1011. pBehavior->SetActive( false );
  1012. SetBehaviorParams( pBehavior);
  1013. }
  1014. void InputActivate( inputdata_t &inputdata )
  1015. {
  1016. ValidateAggression();
  1017. BaseClass::InputActivate( inputdata );
  1018. }
  1019. void InputDeactivate( inputdata_t &inputdata )
  1020. {
  1021. ValidateAggression();
  1022. BaseClass::InputDeactivate( inputdata );
  1023. }
  1024. void InputSetAggressiveness( inputdata_t &inputdata )
  1025. {
  1026. int newVal = inputdata.value.Int();
  1027. m_aggressiveness = (Aggressiveness_t)newVal;
  1028. ValidateAggression();
  1029. UpdateActors();
  1030. const CUtlVector<AIHANDLE> &actors = AccessActors();
  1031. for ( int i = 0; i < actors.Count(); i++ )
  1032. {
  1033. CAI_BaseNPC *pAI = actors[i];
  1034. CAI_StandoffBehavior *pBehavior;
  1035. if ( !pAI->GetBehavior( &pBehavior ) )
  1036. continue;
  1037. SetBehaviorParams( pBehavior);
  1038. }
  1039. }
  1040. void SetBehaviorParams( CAI_StandoffBehavior *pBehavior )
  1041. {
  1042. AI_StandoffParams_t params;
  1043. if ( m_aggressiveness != AGGR_CUSTOM )
  1044. params = g_StandoffParamsByAgression[m_aggressiveness];
  1045. else
  1046. params = m_customParams;
  1047. params.hintChangeReaction = m_HintChangeReaction;
  1048. params.fPlayerIsBattleline = m_fPlayerIsBattleline;
  1049. params.fStayAtCover = m_fStayAtCover;
  1050. if ( !m_bAbandonIfEnemyHides )
  1051. params.flAbandonTimeLimit = 0;
  1052. pBehavior->SetParameters( params, this );
  1053. pBehavior->OnChangeTacticalConstraints();
  1054. if ( pBehavior->IsRunning() )
  1055. pBehavior->GetOuter()->ClearSchedule( "Standoff behavior parms changed" );
  1056. }
  1057. void ValidateAggression()
  1058. {
  1059. if ( m_aggressiveness < AGGR_VERY_LOW || m_aggressiveness > AGGR_VERY_HIGH )
  1060. {
  1061. if ( m_aggressiveness != AGGR_CUSTOM )
  1062. {
  1063. DevMsg( "Invalid aggressiveness value %d\n", m_aggressiveness );
  1064. if ( m_aggressiveness < AGGR_VERY_LOW )
  1065. m_aggressiveness = AGGR_VERY_LOW;
  1066. else if ( m_aggressiveness > AGGR_VERY_HIGH )
  1067. m_aggressiveness = AGGR_VERY_HIGH;
  1068. }
  1069. }
  1070. }
  1071. private:
  1072. //---------------------------------
  1073. DECLARE_DATADESC();
  1074. enum Aggressiveness_t
  1075. {
  1076. AGGR_VERY_LOW,
  1077. AGGR_LOW,
  1078. AGGR_MEDIUM,
  1079. AGGR_HIGH,
  1080. AGGR_VERY_HIGH,
  1081. AGGR_CUSTOM,
  1082. };
  1083. Aggressiveness_t m_aggressiveness;
  1084. AI_HintChangeReaction_t m_HintChangeReaction;
  1085. bool m_fPlayerIsBattleline;
  1086. bool m_fStayAtCover;
  1087. bool m_bAbandonIfEnemyHides;
  1088. AI_StandoffParams_t m_customParams;
  1089. };
  1090. //-------------------------------------
  1091. LINK_ENTITY_TO_CLASS( ai_goal_standoff, CAI_StandoffGoal );
  1092. BEGIN_DATADESC( CAI_StandoffGoal )
  1093. DEFINE_KEYFIELD( m_aggressiveness, FIELD_INTEGER, "Aggressiveness" ),
  1094. // m_customParams (individually)
  1095. DEFINE_KEYFIELD( m_HintChangeReaction, FIELD_INTEGER, "HintGroupChangeReaction" ),
  1096. DEFINE_KEYFIELD( m_fPlayerIsBattleline, FIELD_BOOLEAN, "PlayerBattleline" ),
  1097. DEFINE_KEYFIELD( m_fStayAtCover, FIELD_BOOLEAN, "StayAtCover" ),
  1098. DEFINE_KEYFIELD( m_bAbandonIfEnemyHides, FIELD_BOOLEAN, "AbandonIfEnemyHides" ),
  1099. DEFINE_KEYFIELD( m_customParams.fCoverOnReload, FIELD_BOOLEAN, "CustomCoverOnReload" ),
  1100. DEFINE_KEYFIELD( m_customParams.minTimeShots, FIELD_FLOAT, "CustomMinTimeShots" ),
  1101. DEFINE_KEYFIELD( m_customParams.maxTimeShots, FIELD_FLOAT, "CustomMaxTimeShots" ),
  1102. DEFINE_KEYFIELD( m_customParams.minShots, FIELD_INTEGER, "CustomMinShots" ),
  1103. DEFINE_KEYFIELD( m_customParams.maxShots, FIELD_INTEGER, "CustomMaxShots" ),
  1104. DEFINE_KEYFIELD( m_customParams.oddsCover, FIELD_INTEGER, "CustomOddsCover" ),
  1105. // Inputs
  1106. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetAggressiveness", InputSetAggressiveness ),
  1107. END_DATADESC()
  1108. ///-----------------------------------------------------------------------------