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.

1338 lines
37 KiB

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