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.

3997 lines
120 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "npc_playercompanion.h"
  8. #include "combine_mine.h"
  9. #include "fire.h"
  10. #include "func_tank.h"
  11. #include "globalstate.h"
  12. #include "npcevent.h"
  13. #include "props.h"
  14. #include "BasePropDoor.h"
  15. #include "ai_hint.h"
  16. #include "ai_localnavigator.h"
  17. #include "ai_memory.h"
  18. #include "ai_pathfinder.h"
  19. #include "ai_route.h"
  20. #include "ai_senses.h"
  21. #include "ai_squad.h"
  22. #include "ai_squadslot.h"
  23. #include "ai_tacticalservices.h"
  24. #include "ai_interactions.h"
  25. #include "filesystem.h"
  26. #include "collisionutils.h"
  27. #include "grenade_frag.h"
  28. #include <KeyValues.h>
  29. #include "physics_npc_solver.h"
  30. ConVar ai_debug_readiness("ai_debug_readiness", "0" );
  31. ConVar ai_use_readiness("ai_use_readiness", "1" ); // 0 = off, 1 = on, 2 = on for player squad only
  32. ConVar ai_readiness_decay( "ai_readiness_decay", "120" );// How many seconds it takes to relax completely
  33. ConVar ai_new_aiming( "ai_new_aiming", "1" );
  34. #define GetReadinessUse() ai_use_readiness.GetInt()
  35. extern ConVar g_debug_transitions;
  36. #define PLAYERCOMPANION_TRANSITION_SEARCH_DISTANCE (100*12)
  37. int AE_COMPANION_PRODUCE_FLARE;
  38. int AE_COMPANION_LIGHT_FLARE;
  39. int AE_COMPANION_RELEASE_FLARE;
  40. #define MAX_TIME_BETWEEN_BARRELS_EXPLODING 5.0f
  41. #define MAX_TIME_BETWEEN_CONSECUTIVE_PLAYER_KILLS 3.0f
  42. //-----------------------------------------------------------------------------
  43. // An aimtarget becomes invalid if it gets this close
  44. //-----------------------------------------------------------------------------
  45. #define COMPANION_AIMTARGET_NEAREST 24.0f
  46. #define COMPANION_AIMTARGET_NEAREST_SQR 576.0f
  47. //-----------------------------------------------------------------------------
  48. //-----------------------------------------------------------------------------
  49. BEGIN_DATADESC( CNPC_PlayerCompanion )
  50. DEFINE_FIELD( m_bMovingAwayFromPlayer, FIELD_BOOLEAN ),
  51. DEFINE_EMBEDDED( m_SpeechWatch_PlayerLooking ),
  52. DEFINE_EMBEDDED( m_FakeOutMortarTimer ),
  53. // (recomputed)
  54. // m_bWeightPathsInCover
  55. // These are auto-saved by AI
  56. // DEFINE_FIELD( m_AssaultBehavior, CAI_AssaultBehavior ),
  57. // DEFINE_FIELD( m_FollowBehavior, CAI_FollowBehavior ),
  58. // DEFINE_FIELD( m_StandoffBehavior, CAI_StandoffBehavior ),
  59. // DEFINE_FIELD( m_LeadBehavior, CAI_LeadBehavior ),
  60. // DEFINE_FIELD( m_OperatorBehavior, FIELD_EMBEDDED ),
  61. // m_ActBusyBehavior
  62. // m_PassengerBehavior
  63. // m_FearBehavior
  64. DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
  65. DEFINE_INPUTFUNC( FIELD_VOID, "SetReadinessPanic", InputSetReadinessPanic ),
  66. DEFINE_INPUTFUNC( FIELD_VOID, "SetReadinessStealth", InputSetReadinessStealth ),
  67. DEFINE_INPUTFUNC( FIELD_VOID, "SetReadinessLow", InputSetReadinessLow ),
  68. DEFINE_INPUTFUNC( FIELD_VOID, "SetReadinessMedium", InputSetReadinessMedium ),
  69. DEFINE_INPUTFUNC( FIELD_VOID, "SetReadinessHigh", InputSetReadinessHigh ),
  70. DEFINE_INPUTFUNC( FIELD_FLOAT, "LockReadiness", InputLockReadiness ),
  71. //------------------------------------------------------------------------------
  72. #ifdef HL2_EPISODIC
  73. DEFINE_FIELD( m_hFlare, FIELD_EHANDLE ),
  74. DEFINE_INPUTFUNC( FIELD_STRING, "EnterVehicle", InputEnterVehicle ),
  75. DEFINE_INPUTFUNC( FIELD_STRING, "EnterVehicleImmediately", InputEnterVehicleImmediately ),
  76. DEFINE_INPUTFUNC( FIELD_VOID, "ExitVehicle", InputExitVehicle ),
  77. DEFINE_INPUTFUNC( FIELD_VOID, "CancelEnterVehicle", InputCancelEnterVehicle ),
  78. #endif // HL2_EPISODIC
  79. //------------------------------------------------------------------------------
  80. DEFINE_INPUTFUNC( FIELD_STRING, "GiveWeapon", InputGiveWeapon ),
  81. DEFINE_FIELD( m_flReadiness, FIELD_FLOAT ),
  82. DEFINE_FIELD( m_flReadinessSensitivity, FIELD_FLOAT ),
  83. DEFINE_FIELD( m_bReadinessCapable, FIELD_BOOLEAN ),
  84. DEFINE_FIELD( m_flReadinessLockedUntil, FIELD_TIME ),
  85. DEFINE_FIELD( m_fLastBarrelExploded, FIELD_TIME ),
  86. DEFINE_FIELD( m_iNumConsecutiveBarrelsExploded, FIELD_INTEGER ),
  87. DEFINE_FIELD( m_fLastPlayerKill, FIELD_TIME ),
  88. DEFINE_FIELD( m_iNumConsecutivePlayerKills, FIELD_INTEGER ),
  89. // m_flBoostSpeed (recomputed)
  90. DEFINE_EMBEDDED( m_AnnounceAttackTimer ),
  91. DEFINE_FIELD( m_hAimTarget, FIELD_EHANDLE ),
  92. DEFINE_KEYFIELD( m_bAlwaysTransition, FIELD_BOOLEAN, "AlwaysTransition" ),
  93. DEFINE_KEYFIELD( m_bDontPickupWeapons, FIELD_BOOLEAN, "DontPickupWeapons" ),
  94. DEFINE_INPUTFUNC( FIELD_VOID, "EnableAlwaysTransition", InputEnableAlwaysTransition ),
  95. DEFINE_INPUTFUNC( FIELD_VOID, "DisableAlwaysTransition", InputDisableAlwaysTransition ),
  96. DEFINE_INPUTFUNC( FIELD_VOID, "EnableWeaponPickup", InputEnableWeaponPickup ),
  97. DEFINE_INPUTFUNC( FIELD_VOID, "DisableWeaponPickup", InputDisableWeaponPickup ),
  98. #if HL2_EPISODIC
  99. DEFINE_INPUTFUNC( FIELD_VOID, "ClearAllOutputs", InputClearAllOuputs ),
  100. #endif
  101. DEFINE_OUTPUT( m_OnWeaponPickup, "OnWeaponPickup" ),
  102. END_DATADESC()
  103. //-----------------------------------------------------------------------------
  104. //-----------------------------------------------------------------------------
  105. CNPC_PlayerCompanion::eCoverType CNPC_PlayerCompanion::gm_fCoverSearchType;
  106. bool CNPC_PlayerCompanion::gm_bFindingCoverFromAllEnemies;
  107. string_t CNPC_PlayerCompanion::gm_iszMortarClassname;
  108. string_t CNPC_PlayerCompanion::gm_iszFloorTurretClassname;
  109. string_t CNPC_PlayerCompanion::gm_iszGroundTurretClassname;
  110. string_t CNPC_PlayerCompanion::gm_iszShotgunClassname;
  111. string_t CNPC_PlayerCompanion::gm_iszRollerMineClassname;
  112. //-----------------------------------------------------------------------------
  113. //-----------------------------------------------------------------------------
  114. bool CNPC_PlayerCompanion::CreateBehaviors()
  115. {
  116. #ifdef HL2_EPISODIC
  117. AddBehavior( &m_FearBehavior );
  118. AddBehavior( &m_PassengerBehavior );
  119. #endif // HL2_EPISODIC
  120. AddBehavior( &m_ActBusyBehavior );
  121. #ifdef HL2_EPISODIC
  122. AddBehavior( &m_OperatorBehavior );
  123. AddBehavior( &m_StandoffBehavior );
  124. AddBehavior( &m_AssaultBehavior );
  125. AddBehavior( &m_FollowBehavior );
  126. AddBehavior( &m_LeadBehavior );
  127. #else
  128. AddBehavior( &m_AssaultBehavior );
  129. AddBehavior( &m_StandoffBehavior );
  130. AddBehavior( &m_FollowBehavior );
  131. AddBehavior( &m_LeadBehavior );
  132. #endif//HL2_EPISODIC
  133. return BaseClass::CreateBehaviors();
  134. }
  135. //-----------------------------------------------------------------------------
  136. //-----------------------------------------------------------------------------
  137. void CNPC_PlayerCompanion::Precache()
  138. {
  139. gm_iszMortarClassname = AllocPooledString( "func_tankmortar" );
  140. gm_iszFloorTurretClassname = AllocPooledString( "npc_turret_floor" );
  141. gm_iszGroundTurretClassname = AllocPooledString( "npc_turret_ground" );
  142. gm_iszShotgunClassname = AllocPooledString( "weapon_shotgun" );
  143. gm_iszRollerMineClassname = AllocPooledString( "npc_rollermine" );
  144. PrecacheModel( STRING( GetModelName() ) );
  145. #ifdef HL2_EPISODIC
  146. // The flare we're able to pull out
  147. PrecacheModel( "models/props_junk/flare.mdl" );
  148. #endif // HL2_EPISODIC
  149. BaseClass::Precache();
  150. }
  151. //-----------------------------------------------------------------------------
  152. //-----------------------------------------------------------------------------
  153. void CNPC_PlayerCompanion::Spawn()
  154. {
  155. SelectModel();
  156. Precache();
  157. SetModel( STRING( GetModelName() ) );
  158. SetHullType(HULL_HUMAN);
  159. SetHullSizeNormal();
  160. SetSolid( SOLID_BBOX );
  161. AddSolidFlags( FSOLID_NOT_STANDABLE );
  162. SetBloodColor( BLOOD_COLOR_RED );
  163. m_flFieldOfView = 0.02;
  164. m_NPCState = NPC_STATE_NONE;
  165. CapabilitiesClear();
  166. CapabilitiesAdd( bits_CAP_SQUAD );
  167. if ( !HasSpawnFlags( SF_NPC_START_EFFICIENT ) )
  168. {
  169. CapabilitiesAdd( bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD );
  170. CapabilitiesAdd( bits_CAP_USE_WEAPONS | bits_CAP_AIM_GUN | bits_CAP_MOVE_SHOOT );
  171. CapabilitiesAdd( bits_CAP_DUCK | bits_CAP_DOORS_GROUP );
  172. CapabilitiesAdd( bits_CAP_USE_SHOT_REGULATOR );
  173. }
  174. CapabilitiesAdd( bits_CAP_NO_HIT_PLAYER | bits_CAP_NO_HIT_SQUADMATES | bits_CAP_FRIENDLY_DMG_IMMUNE );
  175. CapabilitiesAdd( bits_CAP_MOVE_GROUND );
  176. SetMoveType( MOVETYPE_STEP );
  177. m_HackedGunPos = Vector( 0, 0, 55 );
  178. SetAimTarget(NULL);
  179. m_bReadinessCapable = IsReadinessCapable();
  180. SetReadinessValue( 0.0f );
  181. SetReadinessSensitivity( random->RandomFloat( 0.7, 1.3 ) );
  182. m_flReadinessLockedUntil = 0.0f;
  183. m_AnnounceAttackTimer.Set( 10, 30 );
  184. #ifdef HL2_EPISODIC
  185. // We strip this flag because it's been made obsolete by the StartScripting behavior
  186. if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) )
  187. {
  188. Warning( "NPC %s using alternate collision! -- DISABLED\n", STRING( GetEntityName() ) );
  189. RemoveSpawnFlags( SF_NPC_ALTCOLLISION );
  190. }
  191. m_hFlare = NULL;
  192. #endif // HL2_EPISODIC
  193. BaseClass::Spawn();
  194. }
  195. //-----------------------------------------------------------------------------
  196. //-----------------------------------------------------------------------------
  197. int CNPC_PlayerCompanion::Restore( IRestore &restore )
  198. {
  199. int baseResult = BaseClass::Restore( restore );
  200. if ( gpGlobals->eLoadType == MapLoad_Transition )
  201. {
  202. m_StandoffBehavior.SetActive( false );
  203. }
  204. #ifdef HL2_EPISODIC
  205. // We strip this flag because it's been made obsolete by the StartScripting behavior
  206. if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) )
  207. {
  208. Warning( "NPC %s using alternate collision! -- DISABLED\n", STRING( GetEntityName() ) );
  209. RemoveSpawnFlags( SF_NPC_ALTCOLLISION );
  210. }
  211. #endif // HL2_EPISODIC
  212. return baseResult;
  213. }
  214. //-----------------------------------------------------------------------------
  215. //-----------------------------------------------------------------------------
  216. int CNPC_PlayerCompanion::ObjectCaps()
  217. {
  218. int caps = UsableNPCObjectCaps( BaseClass::ObjectCaps() );
  219. return caps;
  220. }
  221. //-----------------------------------------------------------------------------
  222. //-----------------------------------------------------------------------------
  223. bool CNPC_PlayerCompanion::ShouldAlwaysThink()
  224. {
  225. return ( BaseClass::ShouldAlwaysThink() || ( GetFollowBehavior().GetFollowTarget() && GetFollowBehavior().GetFollowTarget()->IsPlayer() ) );
  226. }
  227. //-----------------------------------------------------------------------------
  228. //-----------------------------------------------------------------------------
  229. Disposition_t CNPC_PlayerCompanion::IRelationType( CBaseEntity *pTarget )
  230. {
  231. if ( !pTarget )
  232. return D_NU;
  233. Disposition_t baseRelationship = BaseClass::IRelationType( pTarget );
  234. if ( baseRelationship != D_LI )
  235. {
  236. if ( IsTurret( pTarget ) )
  237. {
  238. // Citizens are afeared of turrets, so long as the turret
  239. // is active... that is, not classifying itself as CLASS_NONE
  240. if( pTarget->Classify() != CLASS_NONE )
  241. {
  242. if( !hl2_episodic.GetBool() && IsSafeFromFloorTurret(GetAbsOrigin(), pTarget) )
  243. {
  244. return D_NU;
  245. }
  246. return D_FR;
  247. }
  248. }
  249. else if ( baseRelationship == D_HT &&
  250. pTarget->IsNPC() &&
  251. ((CAI_BaseNPC *)pTarget)->GetActiveWeapon() &&
  252. ((CAI_BaseNPC *)pTarget)->GetActiveWeapon()->ClassMatches( gm_iszShotgunClassname ) &&
  253. ( !GetActiveWeapon() || !GetActiveWeapon()->ClassMatches( gm_iszShotgunClassname ) ) )
  254. {
  255. if ( (pTarget->GetAbsOrigin() - GetAbsOrigin()).LengthSqr() < Square( 25 * 12 ) )
  256. {
  257. // Ignore enemies on the floor above us
  258. if ( fabs(pTarget->GetAbsOrigin().z - GetAbsOrigin().z) < 100 )
  259. return D_FR;
  260. }
  261. }
  262. }
  263. return baseRelationship;
  264. }
  265. //-----------------------------------------------------------------------------
  266. //-----------------------------------------------------------------------------
  267. bool CNPC_PlayerCompanion::IsSilentSquadMember() const
  268. {
  269. if ( (const_cast<CNPC_PlayerCompanion *>(this))->Classify() == CLASS_PLAYER_ALLY_VITAL && m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() )
  270. {
  271. return true;
  272. }
  273. return false;
  274. }
  275. //-----------------------------------------------------------------------------
  276. //-----------------------------------------------------------------------------
  277. void CNPC_PlayerCompanion::GatherConditions()
  278. {
  279. BaseClass::GatherConditions();
  280. if ( AI_IsSinglePlayer() )
  281. {
  282. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  283. if ( Classify() == CLASS_PLAYER_ALLY_VITAL )
  284. {
  285. bool bInPlayerSquad = ( m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() );
  286. if ( bInPlayerSquad )
  287. {
  288. if ( GetState() == NPC_STATE_SCRIPT || ( !HasCondition( COND_SEE_PLAYER ) && (GetAbsOrigin() - pPlayer->GetAbsOrigin()).LengthSqr() > Square(50 * 12) ) )
  289. {
  290. RemoveFromSquad();
  291. }
  292. }
  293. else if ( GetState() != NPC_STATE_SCRIPT )
  294. {
  295. if ( HasCondition( COND_SEE_PLAYER ) && (GetAbsOrigin() - pPlayer->GetAbsOrigin()).LengthSqr() < Square(25 * 12) )
  296. {
  297. if ( hl2_episodic.GetBool() )
  298. {
  299. // Don't stomp our squad if we're in one
  300. if ( GetSquad() == NULL )
  301. {
  302. AddToSquad( GetPlayerSquadName() );
  303. }
  304. }
  305. else
  306. {
  307. AddToSquad( GetPlayerSquadName() );
  308. }
  309. }
  310. }
  311. }
  312. m_flBoostSpeed = 0;
  313. if ( m_AnnounceAttackTimer.Expired() &&
  314. ( GetLastEnemyTime() == 0.0 || gpGlobals->curtime - GetLastEnemyTime() > 20 ) )
  315. {
  316. // Always delay when an encounter begins
  317. m_AnnounceAttackTimer.Set( 4, 8 );
  318. }
  319. if ( GetFollowBehavior().GetFollowTarget() &&
  320. ( GetFollowBehavior().GetFollowTarget()->IsPlayer() || GetCommandGoal() != vec3_invalid ) &&
  321. GetFollowBehavior().IsMovingToFollowTarget() &&
  322. GetFollowBehavior().GetGoalRange() > 0.1 &&
  323. BaseClass::GetIdealSpeed() > 0.1 )
  324. {
  325. Vector vPlayerToFollower = GetAbsOrigin() - pPlayer->GetAbsOrigin();
  326. float dist = vPlayerToFollower.NormalizeInPlace();
  327. bool bDoSpeedBoost = false;
  328. if ( !HasCondition( COND_IN_PVS ) )
  329. bDoSpeedBoost = true;
  330. else if ( GetFollowBehavior().GetFollowTarget()->IsPlayer() )
  331. {
  332. if ( dist > GetFollowBehavior().GetGoalRange() * 2 )
  333. {
  334. float dot = vPlayerToFollower.Dot( pPlayer->EyeDirection3D() );
  335. if ( dot < 0 )
  336. {
  337. bDoSpeedBoost = true;
  338. }
  339. }
  340. }
  341. if ( bDoSpeedBoost )
  342. {
  343. float lag = dist / GetFollowBehavior().GetGoalRange();
  344. float mult;
  345. if ( lag > 10.0 )
  346. mult = 2.0;
  347. else if ( lag > 5.0 )
  348. mult = 1.5;
  349. else if ( lag > 3.0 )
  350. mult = 1.25;
  351. else
  352. mult = 1.1;
  353. m_flBoostSpeed = pPlayer->GetSmoothedVelocity().Length();
  354. if ( m_flBoostSpeed < BaseClass::GetIdealSpeed() )
  355. m_flBoostSpeed = BaseClass::GetIdealSpeed();
  356. m_flBoostSpeed *= mult;
  357. }
  358. }
  359. }
  360. // Update our readiness if we're
  361. if ( IsReadinessCapable() )
  362. {
  363. UpdateReadiness();
  364. }
  365. PredictPlayerPush();
  366. // Grovel through memories, don't forget enemies parented to func_tankmortar entities.
  367. // !!!LATER - this should really call out and ask if I want to forget the enemy in question.
  368. AIEnemiesIter_t iter;
  369. for( AI_EnemyInfo_t *pMemory = GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetEnemies()->GetNext(&iter) )
  370. {
  371. if ( IsMortar( pMemory->hEnemy ) || IsSniper( pMemory->hEnemy ) )
  372. {
  373. pMemory->bUnforgettable = ( IRelationType( pMemory->hEnemy ) < D_LI );
  374. pMemory->bEludedMe = false;
  375. }
  376. }
  377. if ( GetMotor()->IsDeceleratingToGoal() && IsCurTaskContinuousMove() &&
  378. HasCondition( COND_PLAYER_PUSHING) && IsCurSchedule( SCHED_MOVE_AWAY ) )
  379. {
  380. ClearSchedule( "Being pushed by player" );
  381. }
  382. CBaseEntity *pEnemy = GetEnemy();
  383. m_bWeightPathsInCover = false;
  384. if ( pEnemy )
  385. {
  386. if ( IsMortar( pEnemy ) || IsSniper( pEnemy ) )
  387. {
  388. m_bWeightPathsInCover = true;
  389. }
  390. }
  391. ClearCondition( COND_PC_SAFE_FROM_MORTAR );
  392. if ( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) )
  393. {
  394. CSound *pSound = GetBestSound( SOUND_DANGER );
  395. if ( pSound && (pSound->SoundType() & SOUND_CONTEXT_MORTAR) )
  396. {
  397. float flDistSq = (pSound->GetSoundOrigin() - GetAbsOrigin() ).LengthSqr();
  398. if ( flDistSq > Square( MORTAR_BLAST_RADIUS + GetHullWidth() * 2 ) )
  399. SetCondition( COND_PC_SAFE_FROM_MORTAR );
  400. }
  401. }
  402. // Handle speech AI. Don't do AI speech if we're in scripts unless permitted by the EnableSpeakWhileScripting input.
  403. if ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT || m_NPCState == NPC_STATE_COMBAT ||
  404. ( ( m_NPCState == NPC_STATE_SCRIPT ) && CanSpeakWhileScripting() ) )
  405. {
  406. DoCustomSpeechAI();
  407. }
  408. if ( AI_IsSinglePlayer() && hl2_episodic.GetBool() && !GetEnemy() && HasCondition( COND_HEAR_PLAYER ) )
  409. {
  410. Vector los = ( UTIL_GetLocalPlayer()->EyePosition() - EyePosition() );
  411. los.z = 0;
  412. VectorNormalize( los );
  413. if ( DotProduct( los, EyeDirection2D() ) > DOT_45DEGREE )
  414. {
  415. ClearCondition( COND_HEAR_PLAYER );
  416. }
  417. }
  418. }
  419. //-----------------------------------------------------------------------------
  420. // Purpose:
  421. //-----------------------------------------------------------------------------
  422. void CNPC_PlayerCompanion::DoCustomSpeechAI( void )
  423. {
  424. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  425. // Don't allow this when we're getting in the car
  426. #ifdef HL2_EPISODIC
  427. bool bPassengerInTransition = ( IsInAVehicle() && ( m_PassengerBehavior.GetPassengerState() == PASSENGER_STATE_ENTERING || m_PassengerBehavior.GetPassengerState() == PASSENGER_STATE_EXITING ) );
  428. #else
  429. bool bPassengerInTransition = false;
  430. #endif
  431. Vector vecEyePosition = EyePosition();
  432. if ( bPassengerInTransition == false && pPlayer && pPlayer->FInViewCone( vecEyePosition ) && pPlayer->FVisible( vecEyePosition ) )
  433. {
  434. if ( m_SpeechWatch_PlayerLooking.Expired() )
  435. {
  436. SpeakIfAllowed( TLK_LOOK );
  437. m_SpeechWatch_PlayerLooking.Stop();
  438. }
  439. }
  440. else
  441. {
  442. m_SpeechWatch_PlayerLooking.Start( 1.0f );
  443. }
  444. // Mention the player is dead
  445. if ( HasCondition( COND_TALKER_PLAYER_DEAD ) )
  446. {
  447. SpeakIfAllowed( TLK_PLDEAD );
  448. }
  449. }
  450. //-----------------------------------------------------------------------------
  451. void CNPC_PlayerCompanion::PredictPlayerPush()
  452. {
  453. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  454. if ( pPlayer && pPlayer->GetSmoothedVelocity().LengthSqr() >= Square(140))
  455. {
  456. Vector predictedPosition = pPlayer->WorldSpaceCenter() + pPlayer->GetSmoothedVelocity() * .4;
  457. Vector delta = WorldSpaceCenter() - predictedPosition;
  458. if ( delta.z < GetHullHeight() * .5 && delta.Length2DSqr() < Square(GetHullWidth() * 1.414) )
  459. TestPlayerPushing( pPlayer );
  460. }
  461. }
  462. //-----------------------------------------------------------------------------
  463. // Purpose: Allows for modification of the interrupt mask for the current schedule.
  464. // In the most cases the base implementation should be called first.
  465. //-----------------------------------------------------------------------------
  466. void CNPC_PlayerCompanion::BuildScheduleTestBits()
  467. {
  468. BaseClass::BuildScheduleTestBits();
  469. // Always interrupt to get into the car
  470. SetCustomInterruptCondition( COND_PC_BECOMING_PASSENGER );
  471. if ( IsCurSchedule(SCHED_RANGE_ATTACK1) )
  472. {
  473. SetCustomInterruptCondition( COND_PLAYER_PUSHING );
  474. }
  475. if ( ( ConditionInterruptsCurSchedule( COND_GIVE_WAY ) ||
  476. IsCurSchedule(SCHED_HIDE_AND_RELOAD ) ||
  477. IsCurSchedule(SCHED_RELOAD ) ||
  478. IsCurSchedule(SCHED_STANDOFF ) ||
  479. IsCurSchedule(SCHED_TAKE_COVER_FROM_ENEMY ) ||
  480. IsCurSchedule(SCHED_COMBAT_FACE ) ||
  481. IsCurSchedule(SCHED_ALERT_FACE ) ||
  482. IsCurSchedule(SCHED_COMBAT_STAND ) ||
  483. IsCurSchedule(SCHED_ALERT_FACE_BESTSOUND) ||
  484. IsCurSchedule(SCHED_ALERT_STAND) ) )
  485. {
  486. SetCustomInterruptCondition( COND_HEAR_MOVE_AWAY );
  487. SetCustomInterruptCondition( COND_PLAYER_PUSHING );
  488. SetCustomInterruptCondition( COND_PC_HURTBYFIRE );
  489. }
  490. }
  491. //-----------------------------------------------------------------------------
  492. //-----------------------------------------------------------------------------
  493. CSound *CNPC_PlayerCompanion::GetBestSound( int validTypes )
  494. {
  495. AISoundIter_t iter;
  496. CSound *pCurrentSound = GetSenses()->GetFirstHeardSound( &iter );
  497. while ( pCurrentSound )
  498. {
  499. // the npc cares about this sound, and it's close enough to hear.
  500. if ( pCurrentSound->FIsSound() )
  501. {
  502. if( pCurrentSound->SoundContext() & SOUND_CONTEXT_MORTAR )
  503. {
  504. return pCurrentSound;
  505. }
  506. }
  507. pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
  508. }
  509. return BaseClass::GetBestSound( validTypes );
  510. }
  511. //-----------------------------------------------------------------------------
  512. //-----------------------------------------------------------------------------
  513. bool CNPC_PlayerCompanion::QueryHearSound( CSound *pSound )
  514. {
  515. if( !BaseClass::QueryHearSound(pSound) )
  516. return false;
  517. switch( pSound->SoundTypeNoContext() )
  518. {
  519. case SOUND_READINESS_LOW:
  520. SetReadinessLevel( AIRL_RELAXED, false, true );
  521. return false;
  522. case SOUND_READINESS_MEDIUM:
  523. SetReadinessLevel( AIRL_STIMULATED, false, true );
  524. return false;
  525. case SOUND_READINESS_HIGH:
  526. SetReadinessLevel( AIRL_AGITATED, false, true );
  527. return false;
  528. default:
  529. return true;
  530. }
  531. }
  532. //-----------------------------------------------------------------------------
  533. bool CNPC_PlayerCompanion::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
  534. {
  535. CAI_BaseNPC *pOther = pEntity->MyNPCPointer();
  536. if ( pOther &&
  537. ( pOther->GetState() == NPC_STATE_ALERT || GetState() == NPC_STATE_ALERT || pOther->GetState() == NPC_STATE_COMBAT || GetState() == NPC_STATE_COMBAT ) &&
  538. pOther->IsPlayerAlly() )
  539. {
  540. return true;
  541. }
  542. return BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC );
  543. }
  544. //-----------------------------------------------------------------------------
  545. //-----------------------------------------------------------------------------
  546. bool CNPC_PlayerCompanion::ShouldIgnoreSound( CSound *pSound )
  547. {
  548. if ( !BaseClass::ShouldIgnoreSound( pSound ) )
  549. {
  550. if ( pSound->IsSoundType( SOUND_DANGER ) && !SoundIsVisible(pSound) )
  551. return true;
  552. #ifdef HL2_EPISODIC
  553. // Ignore vehicle sounds when we're driving in them
  554. if ( pSound->m_hOwner && pSound->m_hOwner->GetServerVehicle() != NULL )
  555. {
  556. if ( m_PassengerBehavior.GetPassengerState() == PASSENGER_STATE_INSIDE &&
  557. m_PassengerBehavior.GetTargetVehicle() == pSound->m_hOwner->GetServerVehicle()->GetVehicleEnt() )
  558. return true;
  559. }
  560. #endif // HL2_EPISODIC
  561. }
  562. return false;
  563. }
  564. //-----------------------------------------------------------------------------
  565. //-----------------------------------------------------------------------------
  566. int CNPC_PlayerCompanion::SelectSchedule()
  567. {
  568. m_bMovingAwayFromPlayer = false;
  569. #ifdef HL2_EPISODIC
  570. // Always defer to passenger if it's running
  571. if ( ShouldDeferToPassengerBehavior() )
  572. {
  573. DeferSchedulingToBehavior( &m_PassengerBehavior );
  574. return BaseClass::SelectSchedule();
  575. }
  576. #endif // HL2_EPISODIC
  577. if ( m_ActBusyBehavior.IsRunning() && m_ActBusyBehavior.NeedsToPlayExitAnim() )
  578. {
  579. trace_t tr;
  580. Vector vUp = GetAbsOrigin();
  581. vUp.z += .25;
  582. AI_TraceHull( GetAbsOrigin(), vUp, GetHullMins(),
  583. GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  584. if ( tr.startsolid )
  585. {
  586. if ( HasCondition( COND_HEAR_DANGER ) )
  587. {
  588. m_ActBusyBehavior.StopBusying();
  589. }
  590. DeferSchedulingToBehavior( &m_ActBusyBehavior );
  591. return BaseClass::SelectSchedule();
  592. }
  593. }
  594. int nSched = SelectFlinchSchedule();
  595. if ( nSched != SCHED_NONE )
  596. return nSched;
  597. int schedule = SelectScheduleDanger();
  598. if ( schedule != SCHED_NONE )
  599. return schedule;
  600. schedule = SelectSchedulePriorityAction();
  601. if ( schedule != SCHED_NONE )
  602. return schedule;
  603. if ( ShouldDeferToFollowBehavior() )
  604. {
  605. DeferSchedulingToBehavior( &(GetFollowBehavior()) );
  606. }
  607. else if ( !BehaviorSelectSchedule() )
  608. {
  609. if ( m_NPCState == NPC_STATE_IDLE || m_NPCState == NPC_STATE_ALERT )
  610. {
  611. schedule = SelectScheduleNonCombat();
  612. if ( schedule != SCHED_NONE )
  613. return schedule;
  614. }
  615. else if ( m_NPCState == NPC_STATE_COMBAT )
  616. {
  617. schedule = SelectScheduleCombat();
  618. if ( schedule != SCHED_NONE )
  619. return schedule;
  620. }
  621. }
  622. return BaseClass::SelectSchedule();
  623. }
  624. //-----------------------------------------------------------------------------
  625. //-----------------------------------------------------------------------------
  626. int CNPC_PlayerCompanion::SelectScheduleDanger()
  627. {
  628. if( HasCondition( COND_HEAR_DANGER ) )
  629. {
  630. CSound *pSound;
  631. pSound = GetBestSound( SOUND_DANGER );
  632. ASSERT( pSound != NULL );
  633. if ( pSound && (pSound->m_iType & SOUND_DANGER) )
  634. {
  635. if ( !(pSound->SoundContext() & (SOUND_CONTEXT_MORTAR|SOUND_CONTEXT_FROM_SNIPER)) || IsOkToCombatSpeak() )
  636. SpeakIfAllowed( TLK_DANGER );
  637. if ( HasCondition( COND_PC_SAFE_FROM_MORTAR ) )
  638. {
  639. // Just duck and cover if far away from the explosion, or in cover.
  640. return SCHED_COWER;
  641. }
  642. #if 1
  643. else if( pSound && (pSound->m_iType & SOUND_CONTEXT_FROM_SNIPER) )
  644. {
  645. return SCHED_COWER;
  646. }
  647. #endif
  648. return SCHED_TAKE_COVER_FROM_BEST_SOUND;
  649. }
  650. }
  651. if ( GetEnemy() &&
  652. m_FakeOutMortarTimer.Expired() &&
  653. GetFollowBehavior().GetFollowTarget() &&
  654. IsMortar( GetEnemy() ) &&
  655. assert_cast<CAI_BaseNPC *>(GetEnemy())->GetEnemy() == this &&
  656. assert_cast<CAI_BaseNPC *>(GetEnemy())->FInViewCone( this ) &&
  657. assert_cast<CAI_BaseNPC *>(GetEnemy())->FVisible( this ) )
  658. {
  659. m_FakeOutMortarTimer.Set( 7 );
  660. return SCHED_PC_FAKEOUT_MORTAR;
  661. }
  662. if ( HasCondition( COND_HEAR_MOVE_AWAY ) )
  663. return SCHED_MOVE_AWAY;
  664. if ( HasCondition( COND_PC_HURTBYFIRE ) )
  665. {
  666. ClearCondition( COND_PC_HURTBYFIRE );
  667. return SCHED_MOVE_AWAY;
  668. }
  669. return SCHED_NONE;
  670. }
  671. //-----------------------------------------------------------------------------
  672. //-----------------------------------------------------------------------------
  673. int CNPC_PlayerCompanion::SelectSchedulePriorityAction()
  674. {
  675. if ( GetGroundEntity() && !IsInAScript() )
  676. {
  677. if ( GetGroundEntity()->IsPlayer() )
  678. {
  679. return SCHED_PC_GET_OFF_COMPANION;
  680. }
  681. if ( GetGroundEntity()->IsNPC() &&
  682. IRelationType( GetGroundEntity() ) == D_LI &&
  683. WorldSpaceCenter().z - GetGroundEntity()->WorldSpaceCenter().z > GetHullHeight() * .5 )
  684. {
  685. return SCHED_PC_GET_OFF_COMPANION;
  686. }
  687. }
  688. int schedule = SelectSchedulePlayerPush();
  689. if ( schedule != SCHED_NONE )
  690. {
  691. if ( GetFollowBehavior().IsRunning() )
  692. KeepRunningBehavior();
  693. return schedule;
  694. }
  695. return SCHED_NONE;
  696. }
  697. //-----------------------------------------------------------------------------
  698. //-----------------------------------------------------------------------------
  699. int CNPC_PlayerCompanion::SelectSchedulePlayerPush()
  700. {
  701. if ( HasCondition( COND_PLAYER_PUSHING ) && !IsInAScript() && !IgnorePlayerPushing() )
  702. {
  703. // Ignore move away before gordon becomes the man
  704. if ( GlobalEntity_GetState("gordon_precriminal") != GLOBAL_ON )
  705. {
  706. m_bMovingAwayFromPlayer = true;
  707. return SCHED_MOVE_AWAY;
  708. }
  709. }
  710. return SCHED_NONE;
  711. }
  712. //-----------------------------------------------------------------------------
  713. //-----------------------------------------------------------------------------
  714. bool CNPC_PlayerCompanion::IgnorePlayerPushing( void )
  715. {
  716. if ( hl2_episodic.GetBool() )
  717. {
  718. // Ignore player pushes if we're leading him
  719. if ( m_LeadBehavior.IsRunning() && m_LeadBehavior.HasGoal() )
  720. return true;
  721. if ( m_AssaultBehavior.IsRunning() && m_AssaultBehavior.OnStrictAssault() )
  722. return true;
  723. }
  724. return false;
  725. }
  726. //-----------------------------------------------------------------------------
  727. //-----------------------------------------------------------------------------
  728. int CNPC_PlayerCompanion::SelectScheduleCombat()
  729. {
  730. if ( CanReload() && (HasCondition ( COND_NO_PRIMARY_AMMO ) || HasCondition(COND_LOW_PRIMARY_AMMO)) )
  731. {
  732. return SCHED_HIDE_AND_RELOAD;
  733. }
  734. return SCHED_NONE;
  735. }
  736. //-----------------------------------------------------------------------------
  737. // Purpose:
  738. //-----------------------------------------------------------------------------
  739. bool CNPC_PlayerCompanion::CanReload( void )
  740. {
  741. if ( IsRunningDynamicInteraction() )
  742. return false;
  743. return true;
  744. }
  745. //-----------------------------------------------------------------------------
  746. //-----------------------------------------------------------------------------
  747. bool CNPC_PlayerCompanion::ShouldDeferToFollowBehavior()
  748. {
  749. if ( !GetFollowBehavior().CanSelectSchedule() || !GetFollowBehavior().FarFromFollowTarget() )
  750. return false;
  751. if ( m_StandoffBehavior.CanSelectSchedule() && !m_StandoffBehavior.IsBehindBattleLines( GetFollowBehavior().GetFollowTarget()->GetAbsOrigin() ) )
  752. return false;
  753. if ( HasCondition(COND_BETTER_WEAPON_AVAILABLE) && !GetActiveWeapon() )
  754. {
  755. // Unarmed allies should arm themselves as soon as the opportunity presents itself.
  756. return false;
  757. }
  758. // Even though assault and act busy are placed ahead of the follow behavior in precedence, the below
  759. // code is necessary because we call ShouldDeferToFollowBehavior BEFORE we call the generic
  760. // BehaviorSelectSchedule, which tries the behaviors in priority order.
  761. if ( m_AssaultBehavior.CanSelectSchedule() && hl2_episodic.GetBool() )
  762. {
  763. return false;
  764. }
  765. if ( hl2_episodic.GetBool() )
  766. {
  767. if ( m_ActBusyBehavior.CanSelectSchedule() && m_ActBusyBehavior.IsCombatActBusy() )
  768. {
  769. return false;
  770. }
  771. }
  772. return true;
  773. }
  774. //-----------------------------------------------------------------------------
  775. // CalcReasonableFacing() is asking us if there's any reason why we wouldn't
  776. // want to look in this direction.
  777. //
  778. // Right now this is used to help prevent citizens aiming their guns at each other
  779. //-----------------------------------------------------------------------------
  780. bool CNPC_PlayerCompanion::IsValidReasonableFacing( const Vector &vecSightDir, float sightDist )
  781. {
  782. if( !GetActiveWeapon() )
  783. {
  784. // If I'm not armed, it doesn't matter if I'm looking at another citizen.
  785. return true;
  786. }
  787. if( ai_new_aiming.GetBool() )
  788. {
  789. Vector vecEyePositionCentered = GetAbsOrigin();
  790. vecEyePositionCentered.z = EyePosition().z;
  791. if( IsSquadmateInSpread(vecEyePositionCentered, vecEyePositionCentered + vecSightDir * 240.0f, VECTOR_CONE_15DEGREES.x, 12.0f * 3.0f) )
  792. {
  793. return false;
  794. }
  795. }
  796. return true;
  797. }
  798. //-----------------------------------------------------------------------------
  799. //-----------------------------------------------------------------------------
  800. int CNPC_PlayerCompanion::TranslateSchedule( int scheduleType )
  801. {
  802. switch( scheduleType )
  803. {
  804. case SCHED_IDLE_STAND:
  805. case SCHED_ALERT_STAND:
  806. if( GetActiveWeapon() )
  807. {
  808. // Everyone with less than half a clip takes turns reloading when not fighting.
  809. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  810. if( CanReload() && pWeapon->UsesClipsForAmmo1() && pWeapon->Clip1() < ( pWeapon->GetMaxClip1() * .5 ) && OccupyStrategySlot( SQUAD_SLOT_EXCLUSIVE_RELOAD ) )
  811. {
  812. if ( AI_IsSinglePlayer() )
  813. {
  814. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  815. pWeapon = pPlayer->GetActiveWeapon();
  816. if( pWeapon && pWeapon->UsesClipsForAmmo1() &&
  817. pWeapon->Clip1() < ( pWeapon->GetMaxClip1() * .75 ) &&
  818. pPlayer->GetAmmoCount( pWeapon->GetPrimaryAmmoType() ) )
  819. {
  820. SpeakIfAllowed( TLK_PLRELOAD );
  821. }
  822. }
  823. return SCHED_RELOAD;
  824. }
  825. }
  826. break;
  827. case SCHED_COWER:
  828. return SCHED_PC_COWER;
  829. case SCHED_TAKE_COVER_FROM_BEST_SOUND:
  830. {
  831. CSound *pSound = GetBestSound(SOUND_DANGER);
  832. if( pSound && pSound->m_hOwner )
  833. {
  834. if( pSound->m_hOwner->IsNPC() && FClassnameIs( pSound->m_hOwner, "npc_zombine" ) )
  835. {
  836. // Run fully away from a Zombine with a grenade.
  837. return SCHED_PC_TAKE_COVER_FROM_BEST_SOUND;
  838. }
  839. }
  840. return SCHED_PC_MOVE_TOWARDS_COVER_FROM_BEST_SOUND;
  841. }
  842. case SCHED_FLEE_FROM_BEST_SOUND:
  843. return SCHED_PC_FLEE_FROM_BEST_SOUND;
  844. case SCHED_ESTABLISH_LINE_OF_FIRE:
  845. case SCHED_MOVE_TO_WEAPON_RANGE:
  846. if ( IsMortar( GetEnemy() ) )
  847. return SCHED_TAKE_COVER_FROM_ENEMY;
  848. break;
  849. case SCHED_CHASE_ENEMY:
  850. if ( IsMortar( GetEnemy() ) )
  851. return SCHED_TAKE_COVER_FROM_ENEMY;
  852. if ( GetEnemy() && FClassnameIs( GetEnemy(), "npc_combinegunship" ) )
  853. return SCHED_ESTABLISH_LINE_OF_FIRE;
  854. break;
  855. case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
  856. // If we're fighting a gunship, try again
  857. if ( GetEnemy() && FClassnameIs( GetEnemy(), "npc_combinegunship" ) )
  858. return SCHED_ESTABLISH_LINE_OF_FIRE;
  859. break;
  860. case SCHED_RANGE_ATTACK1:
  861. if ( IsMortar( GetEnemy() ) )
  862. return SCHED_TAKE_COVER_FROM_ENEMY;
  863. if ( GetShotRegulator()->IsInRestInterval() )
  864. return SCHED_STANDOFF;
  865. if( !OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  866. return SCHED_STANDOFF;
  867. break;
  868. case SCHED_FAIL_TAKE_COVER:
  869. if ( IsEnemyTurret() )
  870. {
  871. return SCHED_PC_FAIL_TAKE_COVER_TURRET;
  872. }
  873. break;
  874. case SCHED_RUN_FROM_ENEMY_FALLBACK:
  875. {
  876. if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  877. {
  878. return SCHED_RANGE_ATTACK1;
  879. }
  880. break;
  881. }
  882. }
  883. return BaseClass::TranslateSchedule( scheduleType );
  884. }
  885. //-----------------------------------------------------------------------------
  886. //-----------------------------------------------------------------------------
  887. void CNPC_PlayerCompanion::StartTask( const Task_t *pTask )
  888. {
  889. switch( pTask->iTask )
  890. {
  891. case TASK_SOUND_WAKE:
  892. LocateEnemySound();
  893. SetWait( 0.5 );
  894. break;
  895. case TASK_ANNOUNCE_ATTACK:
  896. {
  897. if ( GetActiveWeapon() && m_AnnounceAttackTimer.Expired() )
  898. {
  899. if ( SpeakIfAllowed( TLK_ATTACKING, UTIL_VarArgs("attacking_with_weapon:%s", GetActiveWeapon()->GetClassname()) ) )
  900. {
  901. m_AnnounceAttackTimer.Set( 10, 30 );
  902. }
  903. }
  904. BaseClass::StartTask( pTask );
  905. break;
  906. }
  907. case TASK_PC_WAITOUT_MORTAR:
  908. if ( HasCondition( COND_NO_HEAR_DANGER ) )
  909. TaskComplete();
  910. break;
  911. case TASK_MOVE_AWAY_PATH:
  912. {
  913. if ( m_bMovingAwayFromPlayer )
  914. SpeakIfAllowed( TLK_PLPUSH );
  915. BaseClass::StartTask( pTask );
  916. }
  917. break;
  918. case TASK_PC_GET_PATH_OFF_COMPANION:
  919. {
  920. Assert( ( GetGroundEntity() && ( GetGroundEntity()->IsPlayer() || ( GetGroundEntity()->IsNPC() && IRelationType( GetGroundEntity() ) == D_LI ) ) ) );
  921. GetNavigator()->SetAllowBigStep( GetGroundEntity() );
  922. ChainStartTask( TASK_MOVE_AWAY_PATH, 48 );
  923. /*
  924. trace_t tr;
  925. UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin(), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  926. if ( tr.startsolid && tr.m_pEnt == GetGroundEntity() )
  927. {
  928. // Allow us to move through the entity for a short time
  929. NPCPhysics_CreateSolver( this, GetGroundEntity(), true, 2.0f );
  930. }
  931. */
  932. }
  933. break;
  934. default:
  935. BaseClass::StartTask( pTask );
  936. break;
  937. }
  938. }
  939. //-----------------------------------------------------------------------------
  940. //-----------------------------------------------------------------------------
  941. void CNPC_PlayerCompanion::RunTask( const Task_t *pTask )
  942. {
  943. switch( pTask->iTask )
  944. {
  945. case TASK_SOUND_WAKE:
  946. if( IsWaitFinished() )
  947. {
  948. TaskComplete();
  949. }
  950. break;
  951. case TASK_PC_WAITOUT_MORTAR:
  952. {
  953. if ( HasCondition( COND_NO_HEAR_DANGER ) )
  954. TaskComplete();
  955. }
  956. break;
  957. case TASK_MOVE_AWAY_PATH:
  958. {
  959. BaseClass::RunTask( pTask );
  960. if ( GetNavigator()->IsGoalActive() && !GetEnemy() )
  961. {
  962. AddFacingTarget( EyePosition() + BodyDirection2D() * 240, 1.0, 2.0 );
  963. }
  964. }
  965. break;
  966. case TASK_PC_GET_PATH_OFF_COMPANION:
  967. {
  968. if ( AI_IsSinglePlayer() )
  969. {
  970. GetNavigator()->SetAllowBigStep( UTIL_GetLocalPlayer() );
  971. }
  972. ChainRunTask( TASK_MOVE_AWAY_PATH, 48 );
  973. }
  974. break;
  975. default:
  976. BaseClass::RunTask( pTask );
  977. break;
  978. }
  979. }
  980. //-----------------------------------------------------------------------------
  981. // Parses this NPC's activity remap from the actremap.txt file
  982. //-----------------------------------------------------------------------------
  983. void CNPC_PlayerCompanion::PrepareReadinessRemap( void )
  984. {
  985. CUtlVector< CActivityRemap > entries;
  986. UTIL_LoadActivityRemapFile( "scripts/actremap.txt", "npc_playercompanion", entries );
  987. for ( int i = 0; i < entries.Count(); i++ )
  988. {
  989. CCompanionActivityRemap ActRemap;
  990. Q_memcpy( &ActRemap, &entries[i], sizeof( CActivityRemap ) );
  991. KeyValues *pExtraBlock = ActRemap.GetExtraKeyValueBlock();
  992. if ( pExtraBlock )
  993. {
  994. KeyValues *pKey = pExtraBlock->GetFirstSubKey();
  995. while ( pKey )
  996. {
  997. const char *pKeyName = pKey->GetName();
  998. const char *pKeyValue = pKey->GetString();
  999. if ( !stricmp( pKeyName, "readiness" ) )
  1000. {
  1001. ActRemap.m_fUsageBits |= bits_REMAP_READINESS;
  1002. if ( !stricmp( pKeyValue, "AIRL_PANIC" ) )
  1003. {
  1004. ActRemap.m_readiness = AIRL_PANIC;
  1005. }
  1006. else if ( !stricmp( pKeyValue, "AIRL_STEALTH" ) )
  1007. {
  1008. ActRemap.m_readiness = AIRL_STEALTH;
  1009. }
  1010. else if ( !stricmp( pKeyValue, "AIRL_RELAXED" ) )
  1011. {
  1012. ActRemap.m_readiness = AIRL_RELAXED;
  1013. }
  1014. else if ( !stricmp( pKeyValue, "AIRL_STIMULATED" ) )
  1015. {
  1016. ActRemap.m_readiness = AIRL_STIMULATED;
  1017. }
  1018. else if ( !stricmp( pKeyValue, "AIRL_AGITATED" ) )
  1019. {
  1020. ActRemap.m_readiness = AIRL_AGITATED;
  1021. }
  1022. }
  1023. else if ( !stricmp( pKeyName, "aiming" ) )
  1024. {
  1025. ActRemap.m_fUsageBits |= bits_REMAP_AIMING;
  1026. if ( !stricmp( pKeyValue, "TRS_NONE" ) )
  1027. {
  1028. // This is the new way to say that we don't care, the tri-state was abandoned (jdw)
  1029. ActRemap.m_fUsageBits &= ~bits_REMAP_AIMING;
  1030. }
  1031. else if ( !stricmp( pKeyValue, "TRS_FALSE" ) || !stricmp( pKeyValue, "FALSE" ) )
  1032. {
  1033. ActRemap.m_bAiming = false;
  1034. }
  1035. else if ( !stricmp( pKeyValue, "TRS_TRUE" ) || !stricmp( pKeyValue, "TRUE" ) )
  1036. {
  1037. ActRemap.m_bAiming = true;
  1038. }
  1039. }
  1040. else if ( !stricmp( pKeyName, "weaponrequired" ) )
  1041. {
  1042. ActRemap.m_fUsageBits |= bits_REMAP_WEAPON_REQUIRED;
  1043. if ( !stricmp( pKeyValue, "TRUE" ) )
  1044. {
  1045. ActRemap.m_bWeaponRequired = true;
  1046. }
  1047. else if ( !stricmp( pKeyValue, "FALSE" ) )
  1048. {
  1049. ActRemap.m_bWeaponRequired = false;
  1050. }
  1051. }
  1052. else if ( !stricmp( pKeyName, "invehicle" ) )
  1053. {
  1054. ActRemap.m_fUsageBits |= bits_REMAP_IN_VEHICLE;
  1055. if ( !stricmp( pKeyValue, "TRUE" ) )
  1056. {
  1057. ActRemap.m_bInVehicle = true;
  1058. }
  1059. else if ( !stricmp( pKeyValue, "FALSE" ) )
  1060. {
  1061. ActRemap.m_bInVehicle = false;
  1062. }
  1063. }
  1064. pKey = pKey->GetNextKey();
  1065. }
  1066. }
  1067. const char *pActName = ActivityList_NameForIndex( (int)ActRemap.mappedActivity );
  1068. if ( GetActivityID( pActName ) == ACT_INVALID )
  1069. {
  1070. AddActivityToSR( pActName, (int)ActRemap.mappedActivity );
  1071. }
  1072. m_activityMappings.AddToTail( ActRemap );
  1073. }
  1074. }
  1075. //-----------------------------------------------------------------------------
  1076. // Purpose:
  1077. //-----------------------------------------------------------------------------
  1078. void CNPC_PlayerCompanion::Activate( void )
  1079. {
  1080. BaseClass::Activate();
  1081. PrepareReadinessRemap();
  1082. }
  1083. //-----------------------------------------------------------------------------
  1084. // Purpose: Translate an activity given a list of criteria
  1085. //-----------------------------------------------------------------------------
  1086. Activity CNPC_PlayerCompanion::TranslateActivityReadiness( Activity activity )
  1087. {
  1088. // If we're in an actbusy, we don't want to mess with this
  1089. if ( m_ActBusyBehavior.IsActive() )
  1090. return activity;
  1091. if ( m_bReadinessCapable &&
  1092. ( GetReadinessUse() == AIRU_ALWAYS ||
  1093. ( GetReadinessUse() == AIRU_ONLY_PLAYER_SQUADMATES && (IsInPlayerSquad()||Classify()==CLASS_PLAYER_ALLY_VITAL) ) ) )
  1094. {
  1095. bool bShouldAim = ShouldBeAiming();
  1096. for ( int i = 0; i < m_activityMappings.Count(); i++ )
  1097. {
  1098. // Get our activity remap
  1099. CCompanionActivityRemap actremap = m_activityMappings[i];
  1100. // Activity must match
  1101. if ( activity != actremap.activity )
  1102. continue;
  1103. // Readiness must match
  1104. if ( ( actremap.m_fUsageBits & bits_REMAP_READINESS ) && GetReadinessLevel() != actremap.m_readiness )
  1105. continue;
  1106. // Deal with weapon state
  1107. if ( ( actremap.m_fUsageBits & bits_REMAP_WEAPON_REQUIRED ) && actremap.m_bWeaponRequired )
  1108. {
  1109. // Must have a weapon
  1110. if ( GetActiveWeapon() == NULL )
  1111. continue;
  1112. // Must either not care about aiming, or agree on aiming
  1113. if ( actremap.m_fUsageBits & bits_REMAP_AIMING )
  1114. {
  1115. if ( bShouldAim && actremap.m_bAiming == false )
  1116. continue;
  1117. if ( bShouldAim == false && actremap.m_bAiming )
  1118. continue;
  1119. }
  1120. }
  1121. // Must care about vehicle status
  1122. if ( actremap.m_fUsageBits & bits_REMAP_IN_VEHICLE )
  1123. {
  1124. // Deal with the two vehicle states
  1125. if ( actremap.m_bInVehicle && IsInAVehicle() == false )
  1126. continue;
  1127. if ( actremap.m_bInVehicle == false && IsInAVehicle() )
  1128. continue;
  1129. }
  1130. // We've successfully passed all criteria for remapping this
  1131. return actremap.mappedActivity;
  1132. }
  1133. }
  1134. return activity;
  1135. }
  1136. //-----------------------------------------------------------------------------
  1137. // Purpose: Override base class activiites
  1138. //-----------------------------------------------------------------------------
  1139. Activity CNPC_PlayerCompanion::NPC_TranslateActivity( Activity activity )
  1140. {
  1141. if ( activity == ACT_COWER )
  1142. return ACT_COVER_LOW;
  1143. if ( activity == ACT_RUN && ( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) || IsCurSchedule( SCHED_FLEE_FROM_BEST_SOUND ) ) )
  1144. {
  1145. if ( random->RandomInt( 0, 1 ) && HaveSequenceForActivity( ACT_RUN_PROTECTED ) )
  1146. activity = ACT_RUN_PROTECTED;
  1147. }
  1148. activity = BaseClass::NPC_TranslateActivity( activity );
  1149. if ( activity == ACT_IDLE )
  1150. {
  1151. if ( (m_NPCState == NPC_STATE_COMBAT || m_NPCState == NPC_STATE_ALERT) && gpGlobals->curtime - m_flLastAttackTime < 3)
  1152. {
  1153. activity = ACT_IDLE_ANGRY;
  1154. }
  1155. }
  1156. return TranslateActivityReadiness( activity );
  1157. }
  1158. //------------------------------------------------------------------------------
  1159. // Purpose: Handle animation events
  1160. //------------------------------------------------------------------------------
  1161. void CNPC_PlayerCompanion::HandleAnimEvent( animevent_t *pEvent )
  1162. {
  1163. #ifdef HL2_EPISODIC
  1164. // Create a flare and parent to our hand
  1165. if ( pEvent->event == AE_COMPANION_PRODUCE_FLARE )
  1166. {
  1167. m_hFlare = static_cast<CPhysicsProp *>(CreateEntityByName( "prop_physics" ));
  1168. if ( m_hFlare != NULL )
  1169. {
  1170. // Set the model
  1171. m_hFlare->SetModel( "models/props_junk/flare.mdl" );
  1172. // Set the parent attachment
  1173. m_hFlare->SetParent( this );
  1174. m_hFlare->SetParentAttachment( "SetParentAttachment", pEvent->options, false );
  1175. }
  1176. return;
  1177. }
  1178. // Start the flare up with proper fanfare
  1179. if ( pEvent->event == AE_COMPANION_LIGHT_FLARE )
  1180. {
  1181. if ( m_hFlare != NULL )
  1182. {
  1183. m_hFlare->CreateFlare( 5*60.0f );
  1184. }
  1185. return;
  1186. }
  1187. // Drop the flare to the ground
  1188. if ( pEvent->event == AE_COMPANION_RELEASE_FLARE )
  1189. {
  1190. // Detach
  1191. m_hFlare->SetParent( NULL );
  1192. m_hFlare->Spawn();
  1193. m_hFlare->RemoveInteraction( PROPINTER_PHYSGUN_CREATE_FLARE );
  1194. // Disable collisions between the NPC and the flare
  1195. PhysDisableEntityCollisions( this, m_hFlare );
  1196. // TODO: Find the velocity of the attachment point, at this time, in the animation cycle
  1197. // Construct a toss velocity
  1198. Vector vecToss;
  1199. AngleVectors( GetAbsAngles(), &vecToss );
  1200. VectorNormalize( vecToss );
  1201. vecToss *= random->RandomFloat( 64.0f, 72.0f );
  1202. vecToss[2] += 64.0f;
  1203. // Throw it
  1204. IPhysicsObject *pObj = m_hFlare->VPhysicsGetObject();
  1205. pObj->ApplyForceCenter( vecToss );
  1206. // Forget about the flare at this point
  1207. m_hFlare = NULL;
  1208. return;
  1209. }
  1210. #endif // HL2_EPISODIC
  1211. switch( pEvent->event )
  1212. {
  1213. case EVENT_WEAPON_RELOAD:
  1214. if ( GetActiveWeapon() )
  1215. {
  1216. GetActiveWeapon()->WeaponSound( RELOAD_NPC );
  1217. GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1();
  1218. ClearCondition(COND_LOW_PRIMARY_AMMO);
  1219. ClearCondition(COND_NO_PRIMARY_AMMO);
  1220. ClearCondition(COND_NO_SECONDARY_AMMO);
  1221. }
  1222. break;
  1223. default:
  1224. BaseClass::HandleAnimEvent( pEvent );
  1225. break;
  1226. }
  1227. }
  1228. //-----------------------------------------------------------------------------
  1229. // Purpose: This is a generic function (to be implemented by sub-classes) to
  1230. // handle specific interactions between different types of characters
  1231. // (For example the barnacle grabbing an NPC)
  1232. // Input : Constant for the type of interaction
  1233. // Output : true - if sub-class has a response for the interaction
  1234. // false - if sub-class has no response
  1235. //-----------------------------------------------------------------------------
  1236. bool CNPC_PlayerCompanion::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
  1237. {
  1238. if (interactionType == g_interactionHitByPlayerThrownPhysObj )
  1239. {
  1240. if ( IsOkToSpeakInResponseToPlayer() )
  1241. {
  1242. Speak( TLK_PLYR_PHYSATK );
  1243. }
  1244. return true;
  1245. }
  1246. return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
  1247. }
  1248. //------------------------------------------------------------------------------
  1249. //------------------------------------------------------------------------------
  1250. int CNPC_PlayerCompanion::GetSoundInterests()
  1251. {
  1252. return SOUND_WORLD |
  1253. SOUND_COMBAT |
  1254. SOUND_PLAYER |
  1255. SOUND_DANGER |
  1256. SOUND_BULLET_IMPACT |
  1257. SOUND_MOVE_AWAY |
  1258. SOUND_READINESS_LOW |
  1259. SOUND_READINESS_MEDIUM |
  1260. SOUND_READINESS_HIGH;
  1261. }
  1262. //------------------------------------------------------------------------------
  1263. //------------------------------------------------------------------------------
  1264. void CNPC_PlayerCompanion::Touch( CBaseEntity *pOther )
  1265. {
  1266. BaseClass::Touch( pOther );
  1267. // Did the player touch me?
  1268. if ( pOther->IsPlayer() || ( pOther->VPhysicsGetObject() && (pOther->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) ) )
  1269. {
  1270. // Ignore if pissed at player
  1271. if ( m_afMemory & bits_MEMORY_PROVOKED )
  1272. return;
  1273. TestPlayerPushing( ( pOther->IsPlayer() ) ? pOther : AI_GetSinglePlayer() );
  1274. }
  1275. }
  1276. //-----------------------------------------------------------------------------
  1277. //-----------------------------------------------------------------------------
  1278. void CNPC_PlayerCompanion::ModifyOrAppendCriteria( AI_CriteriaSet& set )
  1279. {
  1280. BaseClass::ModifyOrAppendCriteria( set );
  1281. if ( GetEnemy() && IsMortar( GetEnemy() ) )
  1282. {
  1283. set.RemoveCriteria( "enemy" );
  1284. set.AppendCriteria( "enemy", STRING(gm_iszMortarClassname) );
  1285. }
  1286. if ( HasCondition( COND_PC_HURTBYFIRE ) )
  1287. {
  1288. set.AppendCriteria( "hurt_by_fire", "1" );
  1289. }
  1290. if ( m_bReadinessCapable )
  1291. {
  1292. switch( GetReadinessLevel() )
  1293. {
  1294. case AIRL_PANIC:
  1295. set.AppendCriteria( "readiness", "panic" );
  1296. break;
  1297. case AIRL_STEALTH:
  1298. set.AppendCriteria( "readiness", "stealth" );
  1299. break;
  1300. case AIRL_RELAXED:
  1301. set.AppendCriteria( "readiness", "relaxed" );
  1302. break;
  1303. case AIRL_STIMULATED:
  1304. set.AppendCriteria( "readiness", "stimulated" );
  1305. break;
  1306. case AIRL_AGITATED:
  1307. set.AppendCriteria( "readiness", "agitated" );
  1308. break;
  1309. }
  1310. }
  1311. }
  1312. //-----------------------------------------------------------------------------
  1313. //-----------------------------------------------------------------------------
  1314. bool CNPC_PlayerCompanion::IsReadinessCapable()
  1315. {
  1316. if ( GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON )
  1317. return false;
  1318. #ifndef HL2_EPISODIC
  1319. // Allow episodic companions to use readiness even if unarmed. This allows for the panicked
  1320. // citizens in ep1_c17_05 (sjb)
  1321. if( !GetActiveWeapon() )
  1322. return false;
  1323. #endif
  1324. if( GetActiveWeapon() && LookupActivity("ACT_IDLE_AIM_RIFLE_STIMULATED") == ACT_INVALID )
  1325. return false;
  1326. if( GetActiveWeapon() && FClassnameIs( GetActiveWeapon(), "weapon_rpg" ) )
  1327. return false;
  1328. return true;
  1329. }
  1330. //-----------------------------------------------------------------------------
  1331. //-----------------------------------------------------------------------------
  1332. void CNPC_PlayerCompanion::AddReadiness( float flAdd, bool bOverrideLock )
  1333. {
  1334. if( IsReadinessLocked() && !bOverrideLock )
  1335. return;
  1336. SetReadinessValue( GetReadinessValue() + flAdd );
  1337. }
  1338. //-----------------------------------------------------------------------------
  1339. //-----------------------------------------------------------------------------
  1340. void CNPC_PlayerCompanion::SubtractReadiness( float flSub, bool bOverrideLock )
  1341. {
  1342. if( IsReadinessLocked() && !bOverrideLock )
  1343. return;
  1344. // Prevent readiness from going below 0 (below 0 is only for scripted states)
  1345. SetReadinessValue( MAX(GetReadinessValue() - flSub, 0) );
  1346. }
  1347. //-----------------------------------------------------------------------------
  1348. // This method returns false if the NPC is not allowed to change readiness at this point.
  1349. //-----------------------------------------------------------------------------
  1350. bool CNPC_PlayerCompanion::AllowReadinessValueChange( void )
  1351. {
  1352. if ( GetIdealActivity() == ACT_IDLE || GetIdealActivity() == ACT_WALK || GetIdealActivity() == ACT_RUN )
  1353. return true;
  1354. if ( HasActiveLayer() == true )
  1355. return false;
  1356. return false;
  1357. }
  1358. //-----------------------------------------------------------------------------
  1359. // NOTE: This function ignores the lock. Use the interface functions.
  1360. //-----------------------------------------------------------------------------
  1361. void CNPC_PlayerCompanion::SetReadinessValue( float flSet )
  1362. {
  1363. if ( AllowReadinessValueChange() == false )
  1364. return;
  1365. int priorReadiness = GetReadinessLevel();
  1366. flSet = MIN( 1.0f, flSet );
  1367. flSet = MAX( READINESS_MIN_VALUE, flSet );
  1368. m_flReadiness = flSet;
  1369. if( GetReadinessLevel() != priorReadiness )
  1370. {
  1371. // We've been bumped up into a different readiness level.
  1372. // Interrupt IDLE schedules (if we're playing one) so that
  1373. // we can pick the proper animation.
  1374. SetCondition( COND_IDLE_INTERRUPT );
  1375. // Force us to recalculate our animation. If we don't do this,
  1376. // our translated activity may change, but not our root activity,
  1377. // and then we won't actually visually change anims.
  1378. ResetActivity();
  1379. //Force the NPC to recalculate it's arrival sequence since it'll most likely be wrong now that we changed readiness level.
  1380. GetNavigator()->SetArrivalSequence( ACT_INVALID );
  1381. ReadinessLevelChanged( priorReadiness );
  1382. }
  1383. }
  1384. //-----------------------------------------------------------------------------
  1385. // if bOverrideLock, you'll change the readiness level even if we're within
  1386. // a time period during which someone else has locked the level.
  1387. //
  1388. // if bSlam, you'll allow the readiness level to be set lower than current.
  1389. //-----------------------------------------------------------------------------
  1390. void CNPC_PlayerCompanion::SetReadinessLevel( int iLevel, bool bOverrideLock, bool bSlam )
  1391. {
  1392. if( IsReadinessLocked() && !bOverrideLock )
  1393. return;
  1394. switch( iLevel )
  1395. {
  1396. case AIRL_PANIC:
  1397. if( bSlam )
  1398. SetReadinessValue( READINESS_MODE_PANIC );
  1399. break;
  1400. case AIRL_STEALTH:
  1401. if( bSlam )
  1402. SetReadinessValue( READINESS_MODE_STEALTH );
  1403. break;
  1404. case AIRL_RELAXED:
  1405. if( bSlam || GetReadinessValue() < READINESS_VALUE_RELAXED )
  1406. SetReadinessValue( READINESS_VALUE_RELAXED );
  1407. break;
  1408. case AIRL_STIMULATED:
  1409. if( bSlam || GetReadinessValue() < READINESS_VALUE_STIMULATED )
  1410. SetReadinessValue( READINESS_VALUE_STIMULATED );
  1411. break;
  1412. case AIRL_AGITATED:
  1413. if( bSlam || GetReadinessValue() < READINESS_VALUE_AGITATED )
  1414. SetReadinessValue( READINESS_VALUE_AGITATED );
  1415. break;
  1416. default:
  1417. DevMsg("ERROR: Bad readiness level\n");
  1418. break;
  1419. }
  1420. }
  1421. //-----------------------------------------------------------------------------
  1422. //-----------------------------------------------------------------------------
  1423. int CNPC_PlayerCompanion::GetReadinessLevel()
  1424. {
  1425. if ( m_bReadinessCapable == false )
  1426. return AIRL_RELAXED;
  1427. if( m_flReadiness == READINESS_MODE_PANIC )
  1428. {
  1429. return AIRL_PANIC;
  1430. }
  1431. if( m_flReadiness == READINESS_MODE_STEALTH )
  1432. {
  1433. return AIRL_STEALTH;
  1434. }
  1435. if( m_flReadiness <= READINESS_VALUE_RELAXED )
  1436. {
  1437. return AIRL_RELAXED;
  1438. }
  1439. if( m_flReadiness <= READINESS_VALUE_STIMULATED )
  1440. {
  1441. return AIRL_STIMULATED;
  1442. }
  1443. return AIRL_AGITATED;
  1444. }
  1445. //-----------------------------------------------------------------------------
  1446. //-----------------------------------------------------------------------------
  1447. void CNPC_PlayerCompanion::UpdateReadiness()
  1448. {
  1449. // Only update readiness if it's not in a scripted state
  1450. if ( !IsInScriptedReadinessState() )
  1451. {
  1452. if( HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_BULLET_IMPACT) )
  1453. SetReadinessLevel( AIRL_STIMULATED, false, false );
  1454. if( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_SEE_ENEMY) )
  1455. SetReadinessLevel( AIRL_AGITATED, false, false );
  1456. if( m_flReadiness > 0.0f && GetReadinessDecay() > 0 )
  1457. {
  1458. // Decay.
  1459. SubtractReadiness( ( 0.1 * (1.0f/GetReadinessDecay())) * m_flReadinessSensitivity );
  1460. }
  1461. }
  1462. if( ai_debug_readiness.GetBool() && AI_IsSinglePlayer() )
  1463. {
  1464. // Draw the readiness-o-meter
  1465. Vector vecSpot;
  1466. Vector vecOffset( 0, 0, 12 );
  1467. const float BARLENGTH = 12.0f;
  1468. const float GRADLENGTH = 4.0f;
  1469. Vector right;
  1470. UTIL_PlayerByIndex( 1 )->GetVectors( NULL, &right, NULL );
  1471. if ( IsInScriptedReadinessState() )
  1472. {
  1473. // Just print the name of the scripted state
  1474. vecSpot = EyePosition() + vecOffset;
  1475. if( GetReadinessLevel() == AIRL_STEALTH )
  1476. {
  1477. NDebugOverlay::Text( vecSpot, "Stealth", true, 0.1 );
  1478. }
  1479. else if( GetReadinessLevel() == AIRL_PANIC )
  1480. {
  1481. NDebugOverlay::Text( vecSpot, "Panic", true, 0.1 );
  1482. }
  1483. else
  1484. {
  1485. NDebugOverlay::Text( vecSpot, "Unspecified", true, 0.1 );
  1486. }
  1487. }
  1488. else
  1489. {
  1490. vecSpot = EyePosition() + vecOffset;
  1491. NDebugOverlay::Line( vecSpot, vecSpot + right * GRADLENGTH, 255, 255, 255, false, 0.1 );
  1492. vecSpot = EyePosition() + vecOffset + Vector( 0, 0, BARLENGTH * READINESS_VALUE_RELAXED );
  1493. NDebugOverlay::Line( vecSpot, vecSpot + right * GRADLENGTH, 0, 255, 0, false, 0.1 );
  1494. vecSpot = EyePosition() + vecOffset + Vector( 0, 0, BARLENGTH * READINESS_VALUE_STIMULATED );
  1495. NDebugOverlay::Line( vecSpot, vecSpot + right * GRADLENGTH, 255, 255, 0, false, 0.1 );
  1496. vecSpot = EyePosition() + vecOffset + Vector( 0, 0, BARLENGTH * READINESS_VALUE_AGITATED );
  1497. NDebugOverlay::Line( vecSpot, vecSpot + right * GRADLENGTH, 255, 0, 0, false, 0.1 );
  1498. vecSpot = EyePosition() + vecOffset;
  1499. NDebugOverlay::Line( vecSpot, vecSpot + Vector( 0, 0, BARLENGTH * GetReadinessValue() ), 255, 255, 0, false, 0.1 );
  1500. }
  1501. }
  1502. }
  1503. //-----------------------------------------------------------------------------
  1504. //-----------------------------------------------------------------------------
  1505. float CNPC_PlayerCompanion::GetReadinessDecay()
  1506. {
  1507. return ai_readiness_decay.GetFloat();
  1508. }
  1509. //-----------------------------------------------------------------------------
  1510. // Passing NULL to clear the aim target is acceptible.
  1511. //-----------------------------------------------------------------------------
  1512. void CNPC_PlayerCompanion::SetAimTarget( CBaseEntity *pTarget )
  1513. {
  1514. if( pTarget != NULL && IsAllowedToAim() )
  1515. {
  1516. m_hAimTarget = pTarget;
  1517. }
  1518. else
  1519. {
  1520. m_hAimTarget = NULL;
  1521. }
  1522. Activity NewActivity = NPC_TranslateActivity(GetActivity());
  1523. //Don't set the ideal activity to an activity that might not be there.
  1524. if ( SelectWeightedSequence( NewActivity ) == ACT_INVALID )
  1525. return;
  1526. if (NewActivity != GetActivity() )
  1527. {
  1528. SetIdealActivity( NewActivity );
  1529. }
  1530. #if 0
  1531. if( m_hAimTarget )
  1532. {
  1533. Msg("New Aim Target: %s\n", m_hAimTarget->GetClassname() );
  1534. NDebugOverlay::Line(EyePosition(), m_hAimTarget->WorldSpaceCenter(), 255, 255, 0, false, 0.1 );
  1535. }
  1536. #endif
  1537. }
  1538. //-----------------------------------------------------------------------------
  1539. //-----------------------------------------------------------------------------
  1540. void CNPC_PlayerCompanion::StopAiming( char *pszReason )
  1541. {
  1542. #if 0
  1543. if( pszReason )
  1544. {
  1545. Msg("Stopped aiming because %s\n", pszReason );
  1546. }
  1547. #endif
  1548. SetAimTarget(NULL);
  1549. Activity NewActivity = NPC_TranslateActivity(GetActivity());
  1550. if (NewActivity != GetActivity())
  1551. {
  1552. SetIdealActivity( NewActivity );
  1553. }
  1554. }
  1555. //-----------------------------------------------------------------------------
  1556. //-----------------------------------------------------------------------------
  1557. #define COMPANION_MAX_LOOK_TIME 3.0f
  1558. #define COMPANION_MIN_LOOK_TIME 1.0f
  1559. #define COMPANION_MAX_TACTICAL_TARGET_DIST 1800.0f // 150 feet
  1560. bool CNPC_PlayerCompanion::PickTacticalLookTarget( AILookTargetArgs_t *pArgs )
  1561. {
  1562. if( HasCondition( COND_SEE_ENEMY ) )
  1563. {
  1564. // Don't bother. We're dealing with our enemy.
  1565. return false;
  1566. }
  1567. float flMinLookTime;
  1568. float flMaxLookTime;
  1569. // Excited companions will look at each target only briefly and then find something else to look at.
  1570. flMinLookTime = COMPANION_MIN_LOOK_TIME + ((COMPANION_MAX_LOOK_TIME-COMPANION_MIN_LOOK_TIME) * (1.0f - GetReadinessValue()) );
  1571. switch( GetReadinessLevel() )
  1572. {
  1573. case AIRL_RELAXED:
  1574. // Linger on targets, look at them for quite a while.
  1575. flMinLookTime = COMPANION_MAX_LOOK_TIME + random->RandomFloat( 0.0f, 2.0f );
  1576. break;
  1577. case AIRL_STIMULATED:
  1578. // Look around a little quicker.
  1579. flMinLookTime = COMPANION_MIN_LOOK_TIME + random->RandomFloat( 0.0f, COMPANION_MAX_LOOK_TIME - 1.0f );
  1580. break;
  1581. case AIRL_AGITATED:
  1582. // Look around very quickly
  1583. flMinLookTime = COMPANION_MIN_LOOK_TIME;
  1584. break;
  1585. }
  1586. flMaxLookTime = flMinLookTime + random->RandomFloat( 0.0f, 0.5f );
  1587. pArgs->flDuration = random->RandomFloat( flMinLookTime, flMaxLookTime );
  1588. if( HasCondition(COND_SEE_PLAYER) && hl2_episodic.GetBool() )
  1589. {
  1590. // 1/3rd chance to authoritatively look at player
  1591. if( random->RandomInt( 0, 2 ) == 0 )
  1592. {
  1593. pArgs->hTarget = AI_GetSinglePlayer();
  1594. return true;
  1595. }
  1596. }
  1597. // Use hint nodes
  1598. CAI_Hint *pHint;
  1599. CHintCriteria hintCriteria;
  1600. hintCriteria.AddHintType( HINT_WORLD_VISUALLY_INTERESTING );
  1601. hintCriteria.AddHintType( HINT_WORLD_VISUALLY_INTERESTING_DONT_AIM );
  1602. hintCriteria.AddHintType( HINT_WORLD_VISUALLY_INTERESTING_STEALTH );
  1603. hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE | bits_HINT_NODE_IN_VIEWCONE | bits_HINT_NPC_IN_NODE_FOV );
  1604. hintCriteria.AddIncludePosition( GetAbsOrigin(), COMPANION_MAX_TACTICAL_TARGET_DIST );
  1605. {
  1606. AI_PROFILE_SCOPE( CNPC_PlayerCompanion_FindHint_PickTacticalLookTarget );
  1607. pHint = CAI_HintManager::FindHint( this, hintCriteria );
  1608. }
  1609. if( pHint )
  1610. {
  1611. pArgs->hTarget = pHint;
  1612. // Turn this node off for a few seconds to stop others aiming at the same thing (except for stealth nodes)
  1613. if ( pHint->HintType() != HINT_WORLD_VISUALLY_INTERESTING_STEALTH )
  1614. {
  1615. pHint->DisableForSeconds( 5.0f );
  1616. }
  1617. return true;
  1618. }
  1619. // See what the base class thinks.
  1620. return BaseClass::PickTacticalLookTarget( pArgs );
  1621. }
  1622. //-----------------------------------------------------------------------------
  1623. // Returns true if changing target.
  1624. //-----------------------------------------------------------------------------
  1625. bool CNPC_PlayerCompanion::FindNewAimTarget()
  1626. {
  1627. if( GetEnemy() )
  1628. {
  1629. // Don't bother. Aim at enemy.
  1630. return false;
  1631. }
  1632. if( !m_bReadinessCapable || GetReadinessLevel() == AIRL_RELAXED )
  1633. {
  1634. // If I'm relaxed (don't want to aim), or physically incapable,
  1635. // don't run this hint node searching code.
  1636. return false;
  1637. }
  1638. CAI_Hint *pHint;
  1639. CHintCriteria hintCriteria;
  1640. CBaseEntity *pPriorAimTarget = GetAimTarget();
  1641. hintCriteria.SetHintType( HINT_WORLD_VISUALLY_INTERESTING );
  1642. hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE | bits_HINT_NODE_IN_VIEWCONE | bits_HINT_NPC_IN_NODE_FOV );
  1643. hintCriteria.AddIncludePosition( GetAbsOrigin(), COMPANION_MAX_TACTICAL_TARGET_DIST );
  1644. pHint = CAI_HintManager::FindHint( this, hintCriteria );
  1645. if( pHint )
  1646. {
  1647. if( (pHint->GetAbsOrigin() - GetAbsOrigin()).Length2D() < COMPANION_AIMTARGET_NEAREST )
  1648. {
  1649. // Too close!
  1650. return false;
  1651. }
  1652. if( !HasAimLOS(pHint) )
  1653. {
  1654. // No LOS
  1655. return false;
  1656. }
  1657. if( pHint != pPriorAimTarget )
  1658. {
  1659. // Notify of the change.
  1660. SetAimTarget( pHint );
  1661. return true;
  1662. }
  1663. }
  1664. // Didn't find an aim target, or found the same one.
  1665. return false;
  1666. }
  1667. //-----------------------------------------------------------------------------
  1668. //-----------------------------------------------------------------------------
  1669. void CNPC_PlayerCompanion::OnNewLookTarget()
  1670. {
  1671. if( ai_new_aiming.GetBool() )
  1672. {
  1673. if( GetLooktarget() )
  1674. {
  1675. // See if our looktarget is a reasonable aim target.
  1676. CAI_Hint *pHint = dynamic_cast<CAI_Hint*>( GetLooktarget() );
  1677. if( pHint )
  1678. {
  1679. if( pHint->HintType() == HINT_WORLD_VISUALLY_INTERESTING &&
  1680. (pHint->GetAbsOrigin() - GetAbsOrigin()).Length2D() > COMPANION_AIMTARGET_NEAREST &&
  1681. FInAimCone(pHint->GetAbsOrigin()) &&
  1682. HasAimLOS(pHint) )
  1683. {
  1684. SetAimTarget( pHint );
  1685. return;
  1686. }
  1687. }
  1688. }
  1689. // Search for something else.
  1690. FindNewAimTarget();
  1691. }
  1692. else
  1693. {
  1694. if( GetLooktarget() )
  1695. {
  1696. // Have picked a new entity to look at. Should we copy it to the aim target?
  1697. if( IRelationType( GetLooktarget() ) == D_LI )
  1698. {
  1699. // Don't aim at friends, just keep the old target (if any)
  1700. return;
  1701. }
  1702. if( (GetLooktarget()->GetAbsOrigin() - GetAbsOrigin()).Length2D() < COMPANION_AIMTARGET_NEAREST )
  1703. {
  1704. // Too close!
  1705. return;
  1706. }
  1707. if( !HasAimLOS( GetLooktarget() ) )
  1708. {
  1709. // No LOS
  1710. return;
  1711. }
  1712. SetAimTarget( GetLooktarget() );
  1713. }
  1714. }
  1715. }
  1716. //-----------------------------------------------------------------------------
  1717. //-----------------------------------------------------------------------------
  1718. bool CNPC_PlayerCompanion::ShouldBeAiming()
  1719. {
  1720. if( !IsAllowedToAim() )
  1721. {
  1722. return false;
  1723. }
  1724. if( !GetEnemy() && !GetAimTarget() )
  1725. {
  1726. return false;
  1727. }
  1728. if( GetEnemy() && !HasCondition(COND_SEE_ENEMY) )
  1729. {
  1730. return false;
  1731. }
  1732. return true;
  1733. }
  1734. //-----------------------------------------------------------------------------
  1735. //-----------------------------------------------------------------------------
  1736. #define PC_MAX_ALLOWED_AIM 2
  1737. bool CNPC_PlayerCompanion::IsAllowedToAim()
  1738. {
  1739. if( !m_pSquad )
  1740. return true;
  1741. if( GetReadinessLevel() == AIRL_AGITATED )
  1742. {
  1743. // Agitated companions can always aim. This makes the squad look
  1744. // more alert as a whole when something very serious/dangerous has happened.
  1745. return true;
  1746. }
  1747. int count = 0;
  1748. // If I'm in a squad, only a certain number of us can aim.
  1749. AISquadIter_t iter;
  1750. for ( CAI_BaseNPC *pSquadmate = m_pSquad->GetFirstMember(&iter); pSquadmate; pSquadmate = m_pSquad->GetNextMember(&iter) )
  1751. {
  1752. CNPC_PlayerCompanion *pCompanion = dynamic_cast<CNPC_PlayerCompanion*>(pSquadmate);
  1753. if( pCompanion && pCompanion != this && pCompanion->GetAimTarget() != NULL )
  1754. {
  1755. count++;
  1756. }
  1757. }
  1758. if( count < PC_MAX_ALLOWED_AIM )
  1759. {
  1760. return true;
  1761. }
  1762. return false;
  1763. }
  1764. //-----------------------------------------------------------------------------
  1765. //-----------------------------------------------------------------------------
  1766. bool CNPC_PlayerCompanion::HasAimLOS( CBaseEntity *pAimTarget )
  1767. {
  1768. trace_t tr;
  1769. UTIL_TraceLine( Weapon_ShootPosition(), pAimTarget->WorldSpaceCenter(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  1770. if( tr.fraction < 0.5 || (tr.m_pEnt && (tr.m_pEnt->IsNPC()||tr.m_pEnt->IsPlayer())) )
  1771. {
  1772. return false;
  1773. }
  1774. return true;
  1775. }
  1776. //-----------------------------------------------------------------------------
  1777. //-----------------------------------------------------------------------------
  1778. void CNPC_PlayerCompanion::AimGun()
  1779. {
  1780. Vector vecAimDir;
  1781. if( !GetEnemy() )
  1782. {
  1783. if( GetAimTarget() && FInViewCone(GetAimTarget()) )
  1784. {
  1785. float flDist;
  1786. Vector vecAimTargetLoc = GetAimTarget()->WorldSpaceCenter();
  1787. flDist = (vecAimTargetLoc - GetAbsOrigin()).Length2DSqr();
  1788. // Throw away a looktarget if it gets too close. We don't want guys turning around as
  1789. // they walk through doorways which contain a looktarget.
  1790. if( flDist < COMPANION_AIMTARGET_NEAREST_SQR )
  1791. {
  1792. StopAiming("Target too near");
  1793. return;
  1794. }
  1795. // Aim at my target if it's in my cone
  1796. vecAimDir = vecAimTargetLoc - Weapon_ShootPosition();;
  1797. VectorNormalize( vecAimDir );
  1798. SetAim( vecAimDir);
  1799. if( !HasAimLOS(GetAimTarget()) )
  1800. {
  1801. // LOS is broken.
  1802. if( !FindNewAimTarget() )
  1803. {
  1804. // No alternative available right now. Stop aiming.
  1805. StopAiming("No LOS");
  1806. }
  1807. }
  1808. return;
  1809. }
  1810. else
  1811. {
  1812. if( GetAimTarget() )
  1813. {
  1814. // We're aiming at something, but we're about to stop because it's out of viewcone.
  1815. // Try to find something else.
  1816. if( FindNewAimTarget() )
  1817. {
  1818. // Found something else to aim at.
  1819. return;
  1820. }
  1821. else
  1822. {
  1823. // ditch the aim target, it's gone out of view.
  1824. StopAiming("Went out of view cone");
  1825. }
  1826. }
  1827. if( GetReadinessLevel() == AIRL_AGITATED )
  1828. {
  1829. // Aim down! Agitated animations don't have non-aiming versions, so
  1830. // just point the weapon down.
  1831. Vector vecSpot = EyePosition();
  1832. Vector forward, up;
  1833. GetVectors( &forward, NULL, &up );
  1834. vecSpot += forward * 128 + up * -64;
  1835. vecAimDir = vecSpot - Weapon_ShootPosition();
  1836. VectorNormalize( vecAimDir );
  1837. SetAim( vecAimDir);
  1838. return;
  1839. }
  1840. }
  1841. }
  1842. BaseClass::AimGun();
  1843. }
  1844. //-----------------------------------------------------------------------------
  1845. //-----------------------------------------------------------------------------
  1846. CBaseEntity *CNPC_PlayerCompanion::GetAlternateMoveShootTarget()
  1847. {
  1848. if( GetAimTarget() && !GetAimTarget()->IsNPC() && GetReadinessLevel() != AIRL_RELAXED )
  1849. {
  1850. return GetAimTarget();
  1851. }
  1852. return BaseClass::GetAlternateMoveShootTarget();
  1853. }
  1854. //-----------------------------------------------------------------------------
  1855. //-----------------------------------------------------------------------------
  1856. bool CNPC_PlayerCompanion::IsValidEnemy( CBaseEntity *pEnemy )
  1857. {
  1858. if ( GetFollowBehavior().GetFollowTarget() && GetFollowBehavior().GetFollowTarget()->IsPlayer() && IsSniper( pEnemy ) )
  1859. {
  1860. AI_EnemyInfo_t *pInfo = GetEnemies()->Find( pEnemy );
  1861. if ( pInfo )
  1862. {
  1863. if ( gpGlobals->curtime - pInfo->timeLastSeen > 10 )
  1864. {
  1865. if ( !((CAI_BaseNPC*)pEnemy)->HasCondition( COND_IN_PVS ) )
  1866. return false;
  1867. }
  1868. }
  1869. }
  1870. return BaseClass::IsValidEnemy( pEnemy );
  1871. }
  1872. //-----------------------------------------------------------------------------
  1873. //-----------------------------------------------------------------------------
  1874. bool CNPC_PlayerCompanion::IsSafeFromFloorTurret( const Vector &vecLocation, CBaseEntity *pTurret )
  1875. {
  1876. float dist = ( vecLocation - pTurret->EyePosition() ).LengthSqr();
  1877. if ( dist > Square( 4.0*12.0 ) )
  1878. {
  1879. if ( !pTurret->MyNPCPointer()->FInViewCone( vecLocation ) )
  1880. {
  1881. #if 0 // Draws a green line to turrets I'm safe from
  1882. NDebugOverlay::Line( vecLocation, pTurret->WorldSpaceCenter(), 0, 255, 0, false, 0.1 );
  1883. #endif
  1884. return true;
  1885. }
  1886. }
  1887. #if 0 // Draws a red lines to ones I'm not safe from.
  1888. NDebugOverlay::Line( vecLocation, pTurret->WorldSpaceCenter(), 255, 0, 0, false, 0.1 );
  1889. #endif
  1890. return false;
  1891. }
  1892. //------------------------------------------------------------------------------
  1893. //------------------------------------------------------------------------------
  1894. bool CNPC_PlayerCompanion::ShouldMoveAndShoot( void )
  1895. {
  1896. return BaseClass::ShouldMoveAndShoot();
  1897. }
  1898. //------------------------------------------------------------------------------
  1899. //------------------------------------------------------------------------------
  1900. #define PC_LARGER_BURST_RANGE (12.0f * 10.0f) // If an enemy is this close, player companions fire larger continuous bursts.
  1901. void CNPC_PlayerCompanion::OnUpdateShotRegulator()
  1902. {
  1903. BaseClass::OnUpdateShotRegulator();
  1904. if( GetEnemy() && HasCondition(COND_CAN_RANGE_ATTACK1) )
  1905. {
  1906. if( GetAbsOrigin().DistTo( GetEnemy()->GetAbsOrigin() ) <= PC_LARGER_BURST_RANGE )
  1907. {
  1908. if( hl2_episodic.GetBool() )
  1909. {
  1910. // Longer burst
  1911. int longBurst = random->RandomInt( 10, 15 );
  1912. GetShotRegulator()->SetBurstShotsRemaining( longBurst );
  1913. GetShotRegulator()->SetRestInterval( 0.1, 0.2 );
  1914. }
  1915. else
  1916. {
  1917. // Longer burst
  1918. GetShotRegulator()->SetBurstShotsRemaining( GetShotRegulator()->GetBurstShotsRemaining() * 2 );
  1919. // Shorter Rest interval
  1920. float flMinInterval, flMaxInterval;
  1921. GetShotRegulator()->GetRestInterval( &flMinInterval, &flMaxInterval );
  1922. GetShotRegulator()->SetRestInterval( flMinInterval * 0.6f, flMaxInterval * 0.6f );
  1923. }
  1924. }
  1925. }
  1926. }
  1927. //------------------------------------------------------------------------------
  1928. //------------------------------------------------------------------------------
  1929. void CNPC_PlayerCompanion::DecalTrace( trace_t *pTrace, char const *decalName )
  1930. {
  1931. // Do not decal a player companion's head or face, no matter what.
  1932. if( pTrace->hitgroup == HITGROUP_HEAD )
  1933. return;
  1934. BaseClass::DecalTrace( pTrace, decalName );
  1935. }
  1936. //------------------------------------------------------------------------------
  1937. //------------------------------------------------------------------------------
  1938. bool CNPC_PlayerCompanion::FCanCheckAttacks()
  1939. {
  1940. if( GetEnemy() && ( IsSniper(GetEnemy()) || IsMortar(GetEnemy()) || IsTurret(GetEnemy()) ) )
  1941. {
  1942. // Don't attack the sniper or the mortar.
  1943. return false;
  1944. }
  1945. return BaseClass::FCanCheckAttacks();
  1946. }
  1947. //-----------------------------------------------------------------------------
  1948. // Purpose: Return the actual position the NPC wants to fire at when it's trying
  1949. // to hit it's current enemy.
  1950. //-----------------------------------------------------------------------------
  1951. #define CITIZEN_HEADSHOT_FREQUENCY 3 // one in this many shots at a zombie will be aimed at the zombie's head
  1952. Vector CNPC_PlayerCompanion::GetActualShootPosition( const Vector &shootOrigin )
  1953. {
  1954. if( GetEnemy() && GetEnemy()->Classify() == CLASS_ZOMBIE && random->RandomInt( 1, CITIZEN_HEADSHOT_FREQUENCY ) == 1 )
  1955. {
  1956. return GetEnemy()->HeadTarget( shootOrigin );
  1957. }
  1958. return BaseClass::GetActualShootPosition( shootOrigin );
  1959. }
  1960. //------------------------------------------------------------------------------
  1961. //------------------------------------------------------------------------------
  1962. WeaponProficiency_t CNPC_PlayerCompanion::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon )
  1963. {
  1964. if( FClassnameIs( pWeapon, "weapon_ar2" ) )
  1965. {
  1966. return WEAPON_PROFICIENCY_VERY_GOOD;
  1967. }
  1968. return WEAPON_PROFICIENCY_PERFECT;
  1969. }
  1970. //-----------------------------------------------------------------------------
  1971. //-----------------------------------------------------------------------------
  1972. bool CNPC_PlayerCompanion::Weapon_CanUse( CBaseCombatWeapon *pWeapon )
  1973. {
  1974. if( BaseClass::Weapon_CanUse( pWeapon ) )
  1975. {
  1976. // If this weapon is a shotgun, take measures to control how many
  1977. // are being used in this squad. Don't allow a companion to pick up
  1978. // a shotgun if a squadmate already has one.
  1979. if( pWeapon->ClassMatches( gm_iszShotgunClassname ) )
  1980. {
  1981. return (NumWeaponsInSquad("weapon_shotgun") < 1 );
  1982. }
  1983. else
  1984. {
  1985. return true;
  1986. }
  1987. }
  1988. return false;
  1989. }
  1990. //-----------------------------------------------------------------------------
  1991. //-----------------------------------------------------------------------------
  1992. bool CNPC_PlayerCompanion::ShouldLookForBetterWeapon()
  1993. {
  1994. if ( m_bDontPickupWeapons )
  1995. return false;
  1996. return BaseClass::ShouldLookForBetterWeapon();
  1997. }
  1998. //-----------------------------------------------------------------------------
  1999. //-----------------------------------------------------------------------------
  2000. void CNPC_PlayerCompanion::Weapon_Equip( CBaseCombatWeapon *pWeapon )
  2001. {
  2002. BaseClass::Weapon_Equip( pWeapon );
  2003. m_bReadinessCapable = IsReadinessCapable();
  2004. }
  2005. //------------------------------------------------------------------------------
  2006. //------------------------------------------------------------------------------
  2007. void CNPC_PlayerCompanion::PickupWeapon( CBaseCombatWeapon *pWeapon )
  2008. {
  2009. BaseClass::PickupWeapon( pWeapon );
  2010. SpeakIfAllowed( TLK_NEWWEAPON );
  2011. m_OnWeaponPickup.FireOutput( this, this );
  2012. }
  2013. //-----------------------------------------------------------------------------
  2014. //-----------------------------------------------------------------------------
  2015. const int MAX_NON_SPECIAL_MULTICOVER = 2;
  2016. CUtlVector<AI_EnemyInfo_t *> g_MultiCoverSearchEnemies;
  2017. CNPC_PlayerCompanion * g_pMultiCoverSearcher;
  2018. //-------------------------------------
  2019. int __cdecl MultiCoverCompare( AI_EnemyInfo_t * const *ppLeft, AI_EnemyInfo_t * const *ppRight )
  2020. {
  2021. const AI_EnemyInfo_t *pLeft = *ppLeft;
  2022. const AI_EnemyInfo_t *pRight = *ppRight;
  2023. if ( !pLeft->hEnemy && !pRight->hEnemy)
  2024. return 0;
  2025. if ( !pLeft->hEnemy )
  2026. return 1;
  2027. if ( !pRight->hEnemy )
  2028. return -1;
  2029. if ( pLeft->hEnemy == g_pMultiCoverSearcher->GetEnemy() )
  2030. return -1;
  2031. if ( pRight->hEnemy == g_pMultiCoverSearcher->GetEnemy() )
  2032. return 1;
  2033. bool bLeftIsSpecial = ( CNPC_PlayerCompanion::IsMortar( pLeft->hEnemy ) || CNPC_PlayerCompanion::IsSniper( pLeft->hEnemy ) );
  2034. bool bRightIsSpecial = ( CNPC_PlayerCompanion::IsMortar( pLeft->hEnemy ) || CNPC_PlayerCompanion::IsSniper( pLeft->hEnemy ) );
  2035. if ( !bLeftIsSpecial && bRightIsSpecial )
  2036. return 1;
  2037. if ( bLeftIsSpecial && !bRightIsSpecial )
  2038. return -1;
  2039. float leftRelevantTime = ( pLeft->timeLastSeen == AI_INVALID_TIME || pLeft->timeLastSeen == 0 ) ? -99999 : pLeft->timeLastSeen;
  2040. if ( pLeft->timeLastReceivedDamageFrom != AI_INVALID_TIME && pLeft->timeLastReceivedDamageFrom > leftRelevantTime )
  2041. leftRelevantTime = pLeft->timeLastReceivedDamageFrom;
  2042. float rightRelevantTime = ( pRight->timeLastSeen == AI_INVALID_TIME || pRight->timeLastSeen == 0 ) ? -99999 : pRight->timeLastSeen;
  2043. if ( pRight->timeLastReceivedDamageFrom != AI_INVALID_TIME && pRight->timeLastReceivedDamageFrom > rightRelevantTime )
  2044. rightRelevantTime = pRight->timeLastReceivedDamageFrom;
  2045. if ( leftRelevantTime < rightRelevantTime )
  2046. return -1;
  2047. if ( leftRelevantTime > rightRelevantTime )
  2048. return 1;
  2049. float leftDistSq = g_pMultiCoverSearcher->GetAbsOrigin().DistToSqr( pLeft->hEnemy->GetAbsOrigin() );
  2050. float rightDistSq = g_pMultiCoverSearcher->GetAbsOrigin().DistToSqr( pRight->hEnemy->GetAbsOrigin() );
  2051. if ( leftDistSq < rightDistSq )
  2052. return -1;
  2053. if ( leftDistSq > rightDistSq )
  2054. return 1;
  2055. return 0;
  2056. }
  2057. //-------------------------------------
  2058. void CNPC_PlayerCompanion::SetupCoverSearch( CBaseEntity *pEntity )
  2059. {
  2060. if ( IsTurret( pEntity ) )
  2061. gm_fCoverSearchType = CT_TURRET;
  2062. gm_bFindingCoverFromAllEnemies = false;
  2063. g_pMultiCoverSearcher = this;
  2064. if ( Classify() == CLASS_PLAYER_ALLY_VITAL || IsInPlayerSquad() )
  2065. {
  2066. if ( GetEnemy() )
  2067. {
  2068. if ( !pEntity || GetEnemies()->NumEnemies() > 1 )
  2069. {
  2070. if ( !pEntity ) // if pEntity is NULL, test is against a point in space, so always to search against current enemy too
  2071. gm_bFindingCoverFromAllEnemies = true;
  2072. AIEnemiesIter_t iter;
  2073. for ( AI_EnemyInfo_t *pEnemyInfo = GetEnemies()->GetFirst(&iter); pEnemyInfo != NULL; pEnemyInfo = GetEnemies()->GetNext(&iter) )
  2074. {
  2075. CBaseEntity *pEnemy = pEnemyInfo->hEnemy;
  2076. if ( pEnemy )
  2077. {
  2078. if ( pEnemy != GetEnemy() )
  2079. {
  2080. if ( pEnemyInfo->timeAtFirstHand == AI_INVALID_TIME || gpGlobals->curtime - pEnemyInfo->timeLastSeen > 10.0 )
  2081. continue;
  2082. gm_bFindingCoverFromAllEnemies = true;
  2083. }
  2084. g_MultiCoverSearchEnemies.AddToTail( pEnemyInfo );
  2085. }
  2086. }
  2087. if ( g_MultiCoverSearchEnemies.Count() == 0 )
  2088. {
  2089. gm_bFindingCoverFromAllEnemies = false;
  2090. }
  2091. else if ( gm_bFindingCoverFromAllEnemies )
  2092. {
  2093. g_MultiCoverSearchEnemies.Sort( MultiCoverCompare );
  2094. Assert( g_MultiCoverSearchEnemies[0]->hEnemy == GetEnemy() );
  2095. }
  2096. }
  2097. }
  2098. }
  2099. }
  2100. //-----------------------------------------------------------------------------
  2101. //-----------------------------------------------------------------------------
  2102. void CNPC_PlayerCompanion::CleanupCoverSearch()
  2103. {
  2104. gm_fCoverSearchType = CT_NORMAL;
  2105. g_MultiCoverSearchEnemies.RemoveAll();
  2106. }
  2107. //-----------------------------------------------------------------------------
  2108. //-----------------------------------------------------------------------------
  2109. bool CNPC_PlayerCompanion::FindCoverPos( CBaseEntity *pEntity, Vector *pResult)
  2110. {
  2111. AI_PROFILE_SCOPE(CNPC_PlayerCompanion_FindCoverPos);
  2112. ASSERT_NO_REENTRY();
  2113. bool result = false;
  2114. SetupCoverSearch( pEntity );
  2115. if ( gm_bFindingCoverFromAllEnemies )
  2116. {
  2117. result = BaseClass::FindCoverPos( pEntity, pResult );
  2118. gm_bFindingCoverFromAllEnemies = false;
  2119. }
  2120. if ( !result )
  2121. result = BaseClass::FindCoverPos( pEntity, pResult );
  2122. CleanupCoverSearch();
  2123. return result;
  2124. }
  2125. //-----------------------------------------------------------------------------
  2126. //-----------------------------------------------------------------------------
  2127. bool CNPC_PlayerCompanion::FindCoverPosInRadius( CBaseEntity *pEntity, const Vector &goalPos, float coverRadius, Vector *pResult )
  2128. {
  2129. AI_PROFILE_SCOPE(CNPC_PlayerCompanion_FindCoverPosInRadius);
  2130. ASSERT_NO_REENTRY();
  2131. bool result = false;
  2132. SetupCoverSearch( pEntity );
  2133. if ( gm_bFindingCoverFromAllEnemies )
  2134. {
  2135. result = BaseClass::FindCoverPosInRadius( pEntity, goalPos, coverRadius, pResult );
  2136. gm_bFindingCoverFromAllEnemies = false;
  2137. }
  2138. if ( !result )
  2139. {
  2140. result = BaseClass::FindCoverPosInRadius( pEntity, goalPos, coverRadius, pResult );
  2141. }
  2142. CleanupCoverSearch();
  2143. return result;
  2144. }
  2145. //-----------------------------------------------------------------------------
  2146. //-----------------------------------------------------------------------------
  2147. bool CNPC_PlayerCompanion::FindCoverPos( CSound *pSound, Vector *pResult )
  2148. {
  2149. AI_PROFILE_SCOPE(CNPC_PlayerCompanion_FindCoverPos);
  2150. bool result = false;
  2151. bool bIsMortar = ( pSound->SoundContext() == SOUND_CONTEXT_MORTAR );
  2152. SetupCoverSearch( NULL );
  2153. if ( gm_bFindingCoverFromAllEnemies )
  2154. {
  2155. result = ( bIsMortar ) ? FindMortarCoverPos( pSound, pResult ) :
  2156. BaseClass::FindCoverPos( pSound, pResult );
  2157. gm_bFindingCoverFromAllEnemies = false;
  2158. }
  2159. if ( !result )
  2160. {
  2161. result = ( bIsMortar ) ? FindMortarCoverPos( pSound, pResult ) :
  2162. BaseClass::FindCoverPos( pSound, pResult );
  2163. }
  2164. CleanupCoverSearch();
  2165. return result;
  2166. }
  2167. //-----------------------------------------------------------------------------
  2168. //-----------------------------------------------------------------------------
  2169. bool CNPC_PlayerCompanion::FindMortarCoverPos( CSound *pSound, Vector *pResult )
  2170. {
  2171. bool result = false;
  2172. Assert( pSound->SoundContext() == SOUND_CONTEXT_MORTAR );
  2173. gm_fCoverSearchType = CT_MORTAR;
  2174. result = GetTacticalServices()->FindLateralCover( pSound->GetSoundOrigin(), 0, pResult );
  2175. if ( !result )
  2176. {
  2177. result = GetTacticalServices()->FindCoverPos( pSound->GetSoundOrigin(),
  2178. pSound->GetSoundOrigin(),
  2179. 0,
  2180. CoverRadius(),
  2181. pResult );
  2182. }
  2183. gm_fCoverSearchType = CT_NORMAL;
  2184. return result;
  2185. }
  2186. //-----------------------------------------------------------------------------
  2187. //-----------------------------------------------------------------------------
  2188. bool CNPC_PlayerCompanion::IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition )
  2189. {
  2190. if ( gm_bFindingCoverFromAllEnemies )
  2191. {
  2192. for ( int i = 0; i < g_MultiCoverSearchEnemies.Count(); i++ )
  2193. {
  2194. // @TODO (toml 07-27-04): Should skip checking points near already checked points
  2195. AI_EnemyInfo_t *pEnemyInfo = g_MultiCoverSearchEnemies[i];
  2196. Vector testPos;
  2197. CBaseEntity *pEnemy = pEnemyInfo->hEnemy;
  2198. if ( !pEnemy )
  2199. continue;
  2200. if ( pEnemy == GetEnemy() || IsMortar( pEnemy ) || IsSniper( pEnemy ) || i < MAX_NON_SPECIAL_MULTICOVER )
  2201. {
  2202. testPos = pEnemyInfo->vLastKnownLocation + pEnemy->GetViewOffset();
  2203. }
  2204. else
  2205. break;
  2206. gm_bFindingCoverFromAllEnemies = false;
  2207. bool result = IsCoverPosition( testPos, vecPosition );
  2208. gm_bFindingCoverFromAllEnemies = true;
  2209. if ( !result )
  2210. return false;
  2211. }
  2212. if ( gm_fCoverSearchType != CT_MORTAR && GetEnemy() && vecThreat.DistToSqr( GetEnemy()->EyePosition() ) < 1 )
  2213. return true;
  2214. // else fall through
  2215. }
  2216. if ( gm_fCoverSearchType == CT_TURRET && GetEnemy() && IsSafeFromFloorTurret( vecPosition, GetEnemy() ) )
  2217. {
  2218. return true;
  2219. }
  2220. if ( gm_fCoverSearchType == CT_MORTAR )
  2221. {
  2222. CSound *pSound = GetBestSound( SOUND_DANGER );
  2223. Assert ( pSound && pSound->SoundContext() == SOUND_CONTEXT_MORTAR );
  2224. if( pSound )
  2225. {
  2226. // Don't get closer to the shell
  2227. Vector vecToSound = vecThreat - GetAbsOrigin();
  2228. Vector vecToPosition = vecPosition - GetAbsOrigin();
  2229. VectorNormalize( vecToPosition );
  2230. VectorNormalize( vecToSound );
  2231. if ( vecToPosition.AsVector2D().Dot( vecToSound.AsVector2D() ) > 0 )
  2232. return false;
  2233. // Anything outside the radius is okay
  2234. float flDistSqr = (vecPosition - vecThreat).Length2DSqr();
  2235. float radiusSq = Square( pSound->Volume() );
  2236. if( flDistSqr > radiusSq )
  2237. {
  2238. return true;
  2239. }
  2240. }
  2241. }
  2242. return BaseClass::IsCoverPosition( vecThreat, vecPosition );
  2243. }
  2244. //-----------------------------------------------------------------------------
  2245. //-----------------------------------------------------------------------------
  2246. bool CNPC_PlayerCompanion::IsMortar( CBaseEntity *pEntity )
  2247. {
  2248. if ( !pEntity )
  2249. return false;
  2250. CBaseEntity *pEntityParent = pEntity->GetParent();
  2251. return ( pEntityParent && pEntityParent->GetClassname() == STRING(gm_iszMortarClassname) );
  2252. }
  2253. //-----------------------------------------------------------------------------
  2254. //-----------------------------------------------------------------------------
  2255. bool CNPC_PlayerCompanion::IsSniper( CBaseEntity *pEntity )
  2256. {
  2257. if ( !pEntity )
  2258. return false;
  2259. return ( pEntity->Classify() == CLASS_PROTOSNIPER );
  2260. }
  2261. //-----------------------------------------------------------------------------
  2262. //-----------------------------------------------------------------------------
  2263. bool CNPC_PlayerCompanion::IsTurret( CBaseEntity *pEntity )
  2264. {
  2265. if ( !pEntity )
  2266. return false;
  2267. const char *pszClassname = pEntity->GetClassname();
  2268. return ( pszClassname == STRING(gm_iszFloorTurretClassname) || pszClassname == STRING(gm_iszGroundTurretClassname) );
  2269. }
  2270. //-----------------------------------------------------------------------------
  2271. //-----------------------------------------------------------------------------
  2272. bool CNPC_PlayerCompanion::IsGunship( CBaseEntity *pEntity )
  2273. {
  2274. if( !pEntity )
  2275. return false;
  2276. return (pEntity->Classify() == CLASS_COMBINE_GUNSHIP );
  2277. }
  2278. //-----------------------------------------------------------------------------
  2279. //-----------------------------------------------------------------------------
  2280. int CNPC_PlayerCompanion::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  2281. {
  2282. if( info.GetAttacker() )
  2283. {
  2284. bool bIsEnvFire;
  2285. if( ( bIsEnvFire = FClassnameIs( info.GetAttacker(), "env_fire" ) ) != false || FClassnameIs( info.GetAttacker(), "entityflame" ) || FClassnameIs( info.GetAttacker(), "env_entity_igniter" ) )
  2286. {
  2287. GetMotor()->SetIdealYawToTarget( info.GetAttacker()->GetAbsOrigin() );
  2288. SetCondition( COND_PC_HURTBYFIRE );
  2289. }
  2290. // @Note (toml 07-25-04): there isn't a good solution to player companions getting injured by
  2291. // fires that have huge damage radii that extend outside the rendered
  2292. // fire. Recovery from being injured by fire will also not be done
  2293. // before we ship/ Here we trade one bug (guys standing around dying
  2294. // from flames they appear to not be near), for a lesser one
  2295. // this guy was standing in a fire and didn't react. Since
  2296. // the levels are supposed to have the centers of all the fires
  2297. // npc clipped, this latter case should be rare.
  2298. if ( bIsEnvFire )
  2299. {
  2300. if ( ( GetAbsOrigin() - info.GetAttacker()->GetAbsOrigin() ).Length2DSqr() > Square(12 + GetHullWidth() * .5 ) )
  2301. {
  2302. return 0;
  2303. }
  2304. }
  2305. }
  2306. return BaseClass::OnTakeDamage_Alive( info );
  2307. }
  2308. //-----------------------------------------------------------------------------
  2309. //-----------------------------------------------------------------------------
  2310. void CNPC_PlayerCompanion::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity *pAttackerEnt )
  2311. {
  2312. AI_PROFILE_SCOPE( CNPC_PlayerCompanion_OnFriendDamaged );
  2313. BaseClass::OnFriendDamaged( pSquadmate, pAttackerEnt );
  2314. CAI_BaseNPC *pAttacker = pAttackerEnt->MyNPCPointer();
  2315. if ( pAttacker )
  2316. {
  2317. bool bDirect = ( pSquadmate->FInViewCone(pAttacker) &&
  2318. ( ( pSquadmate->IsPlayer() && HasCondition(COND_SEE_PLAYER) ) ||
  2319. ( pSquadmate->MyNPCPointer() && pSquadmate->MyNPCPointer()->IsPlayerAlly() &&
  2320. GetSenses()->DidSeeEntity( pSquadmate ) ) ) );
  2321. if ( bDirect )
  2322. {
  2323. UpdateEnemyMemory( pAttacker, pAttacker->GetAbsOrigin(), pSquadmate );
  2324. }
  2325. else
  2326. {
  2327. if ( FVisible( pSquadmate ) )
  2328. {
  2329. AI_EnemyInfo_t *pInfo = GetEnemies()->Find( pAttacker );
  2330. if ( !pInfo || ( gpGlobals->curtime - pInfo->timeLastSeen ) > 15.0 )
  2331. UpdateEnemyMemory( pAttacker, pSquadmate->GetAbsOrigin(), pSquadmate );
  2332. }
  2333. }
  2334. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  2335. if ( pPlayer && IsInPlayerSquad() && ( pPlayer->GetAbsOrigin().AsVector2D() - GetAbsOrigin().AsVector2D() ).LengthSqr() < Square( 25*12 ) && IsAllowedToSpeak( TLK_WATCHOUT ) )
  2336. {
  2337. if ( !pPlayer->FInViewCone( pAttacker ) )
  2338. {
  2339. Vector2D vPlayerDir = pPlayer->EyeDirection2D().AsVector2D();
  2340. Vector2D vEnemyDir = pAttacker->EyePosition().AsVector2D() - pPlayer->EyePosition().AsVector2D();
  2341. vEnemyDir.NormalizeInPlace();
  2342. float dot = vPlayerDir.Dot( vEnemyDir );
  2343. if ( dot < 0 )
  2344. Speak( TLK_WATCHOUT, "dangerloc:behind" );
  2345. else if ( ( pPlayer->GetAbsOrigin().AsVector2D() - pAttacker->GetAbsOrigin().AsVector2D() ).LengthSqr() > Square( 40*12 ) )
  2346. Speak( TLK_WATCHOUT, "dangerloc:far" );
  2347. }
  2348. else if ( pAttacker->GetAbsOrigin().z - pPlayer->GetAbsOrigin().z > 128 )
  2349. {
  2350. Speak( TLK_WATCHOUT, "dangerloc:above" );
  2351. }
  2352. else if ( pAttacker->GetHullType() <= HULL_TINY && ( pPlayer->GetAbsOrigin().AsVector2D() - pAttacker->GetAbsOrigin().AsVector2D() ).LengthSqr() > Square( 100*12 ) )
  2353. {
  2354. Speak( TLK_WATCHOUT, "dangerloc:far" );
  2355. }
  2356. }
  2357. }
  2358. }
  2359. //-----------------------------------------------------------------------------
  2360. //-----------------------------------------------------------------------------
  2361. bool CNPC_PlayerCompanion::IsValidMoveAwayDest( const Vector &vecDest )
  2362. {
  2363. // Don't care what the destination is unless I have an enemy and
  2364. // that enemy is a sniper (for now).
  2365. if( !GetEnemy() )
  2366. {
  2367. return true;
  2368. }
  2369. if( GetEnemy()->Classify() != CLASS_PROTOSNIPER )
  2370. {
  2371. return true;
  2372. }
  2373. if( IsCoverPosition( GetEnemy()->EyePosition(), vecDest + GetViewOffset() ) )
  2374. {
  2375. return true;
  2376. }
  2377. return false;
  2378. }
  2379. //-----------------------------------------------------------------------------
  2380. //-----------------------------------------------------------------------------
  2381. bool CNPC_PlayerCompanion::FValidateHintType( CAI_Hint *pHint )
  2382. {
  2383. switch( pHint->HintType() )
  2384. {
  2385. case HINT_PLAYER_SQUAD_TRANSITON_POINT:
  2386. case HINT_WORLD_VISUALLY_INTERESTING_DONT_AIM:
  2387. case HINT_PLAYER_ALLY_MOVE_AWAY_DEST:
  2388. case HINT_PLAYER_ALLY_FEAR_DEST:
  2389. return true;
  2390. break;
  2391. default:
  2392. break;
  2393. }
  2394. return BaseClass::FValidateHintType( pHint );
  2395. }
  2396. //-----------------------------------------------------------------------------
  2397. //-----------------------------------------------------------------------------
  2398. bool CNPC_PlayerCompanion::ValidateNavGoal()
  2399. {
  2400. bool result;
  2401. if ( GetNavigator()->GetGoalType() == GOALTYPE_COVER )
  2402. {
  2403. if ( IsEnemyTurret() )
  2404. gm_fCoverSearchType = CT_TURRET;
  2405. }
  2406. result = BaseClass::ValidateNavGoal();
  2407. gm_fCoverSearchType = CT_NORMAL;
  2408. return result;
  2409. }
  2410. const float AVOID_TEST_DIST = 18.0f*12.0f;
  2411. //-----------------------------------------------------------------------------
  2412. //-----------------------------------------------------------------------------
  2413. #define COMPANION_EPISODIC_AVOID_ENTITY_FLAME_RADIUS 18.0f
  2414. bool CNPC_PlayerCompanion::OverrideMove( float flInterval )
  2415. {
  2416. bool overrode = BaseClass::OverrideMove( flInterval );
  2417. if ( !overrode && GetNavigator()->GetGoalType() != GOALTYPE_NONE )
  2418. {
  2419. string_t iszEnvFire = AllocPooledString( "env_fire" );
  2420. string_t iszBounceBomb = AllocPooledString( "combine_mine" );
  2421. #ifdef HL2_EPISODIC
  2422. string_t iszNPCTurretFloor = AllocPooledString( "npc_turret_floor" );
  2423. string_t iszEntityFlame = AllocPooledString( "entityflame" );
  2424. #endif // HL2_EPISODIC
  2425. if ( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) )
  2426. {
  2427. CSound *pSound = GetBestSound( SOUND_DANGER );
  2428. if( pSound && pSound->SoundContext() == SOUND_CONTEXT_MORTAR )
  2429. {
  2430. // Try not to get any closer to the center
  2431. GetLocalNavigator()->AddObstacle( pSound->GetSoundOrigin(), (pSound->GetSoundOrigin() - GetAbsOrigin()).Length2D() * 0.5, AIMST_AVOID_DANGER );
  2432. }
  2433. }
  2434. CBaseEntity *pEntity = NULL;
  2435. trace_t tr;
  2436. // For each possible entity, compare our known interesting classnames to its classname, via ID
  2437. while( ( pEntity = OverrideMoveCache_FindTargetsInRadius( pEntity, GetAbsOrigin(), AVOID_TEST_DIST ) ) != NULL )
  2438. {
  2439. // Handle each type
  2440. if ( pEntity->m_iClassname == iszEnvFire )
  2441. {
  2442. Vector vMins, vMaxs;
  2443. if ( FireSystem_GetFireDamageDimensions( pEntity, &vMins, &vMaxs ) )
  2444. {
  2445. UTIL_TraceLine( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), MASK_FIRE_SOLID, pEntity, COLLISION_GROUP_NONE, &tr );
  2446. if (tr.fraction == 1.0 && !tr.startsolid)
  2447. {
  2448. GetLocalNavigator()->AddObstacle( pEntity->GetAbsOrigin(), ( ( vMaxs.x - vMins.x ) * 1.414 * 0.5 ) + 6.0, AIMST_AVOID_DANGER );
  2449. }
  2450. }
  2451. }
  2452. #ifdef HL2_EPISODIC
  2453. else if ( pEntity->m_iClassname == iszNPCTurretFloor )
  2454. {
  2455. UTIL_TraceLine( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), MASK_BLOCKLOS, pEntity, COLLISION_GROUP_NONE, &tr );
  2456. if (tr.fraction == 1.0 && !tr.startsolid)
  2457. {
  2458. float radius = 1.4 * pEntity->CollisionProp()->BoundingRadius2D();
  2459. GetLocalNavigator()->AddObstacle( pEntity->WorldSpaceCenter(), radius, AIMST_AVOID_OBJECT );
  2460. }
  2461. }
  2462. else if( pEntity->m_iClassname == iszEntityFlame && pEntity->GetParent() && !pEntity->GetParent()->IsNPC() )
  2463. {
  2464. float flDist = pEntity->WorldSpaceCenter().DistTo( WorldSpaceCenter() );
  2465. if( flDist > COMPANION_EPISODIC_AVOID_ENTITY_FLAME_RADIUS )
  2466. {
  2467. // If I'm not in the flame, prevent me from getting close to it.
  2468. // If I AM in the flame, avoid placing an obstacle until the flame frightens me away from itself.
  2469. UTIL_TraceLine( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), MASK_BLOCKLOS, pEntity, COLLISION_GROUP_NONE, &tr );
  2470. if (tr.fraction == 1.0 && !tr.startsolid)
  2471. {
  2472. GetLocalNavigator()->AddObstacle( pEntity->WorldSpaceCenter(), COMPANION_EPISODIC_AVOID_ENTITY_FLAME_RADIUS, AIMST_AVOID_OBJECT );
  2473. }
  2474. }
  2475. }
  2476. #endif // HL2_EPISODIC
  2477. else if ( pEntity->m_iClassname == iszBounceBomb )
  2478. {
  2479. CBounceBomb *pBomb = static_cast<CBounceBomb *>(pEntity);
  2480. if ( pBomb && !pBomb->IsPlayerPlaced() && pBomb->IsAwake() )
  2481. {
  2482. UTIL_TraceLine( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), MASK_BLOCKLOS, pEntity, COLLISION_GROUP_NONE, &tr );
  2483. if (tr.fraction == 1.0 && !tr.startsolid)
  2484. {
  2485. GetLocalNavigator()->AddObstacle( pEntity->GetAbsOrigin(), BOUNCEBOMB_DETONATE_RADIUS * .8, AIMST_AVOID_DANGER );
  2486. }
  2487. }
  2488. }
  2489. }
  2490. }
  2491. return overrode;
  2492. }
  2493. //-----------------------------------------------------------------------------
  2494. // Purpose:
  2495. //-----------------------------------------------------------------------------
  2496. bool CNPC_PlayerCompanion::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
  2497. {
  2498. bool bResult = BaseClass::MovementCost( moveType, vecStart, vecEnd, pCost );
  2499. if ( moveType == bits_CAP_MOVE_GROUND )
  2500. {
  2501. if ( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) )
  2502. {
  2503. CSound *pSound = GetBestSound( SOUND_DANGER );
  2504. if( pSound && (pSound->SoundContext() & (SOUND_CONTEXT_MORTAR|SOUND_CONTEXT_FROM_SNIPER)) )
  2505. {
  2506. Vector vecToSound = pSound->GetSoundReactOrigin() - GetAbsOrigin();
  2507. Vector vecToPosition = vecEnd - GetAbsOrigin();
  2508. VectorNormalize( vecToPosition );
  2509. VectorNormalize( vecToSound );
  2510. if ( vecToPosition.AsVector2D().Dot( vecToSound.AsVector2D() ) > 0 )
  2511. {
  2512. *pCost *= 1.5;
  2513. bResult = true;
  2514. }
  2515. }
  2516. }
  2517. if ( m_bWeightPathsInCover && GetEnemy() )
  2518. {
  2519. if ( BaseClass::IsCoverPosition( GetEnemy()->EyePosition(), vecEnd ) )
  2520. {
  2521. *pCost *= 0.1;
  2522. bResult = true;
  2523. }
  2524. }
  2525. }
  2526. return bResult;
  2527. }
  2528. //-----------------------------------------------------------------------------
  2529. //-----------------------------------------------------------------------------
  2530. float CNPC_PlayerCompanion::GetIdealSpeed() const
  2531. {
  2532. float baseSpeed = BaseClass::GetIdealSpeed();
  2533. if ( baseSpeed < m_flBoostSpeed )
  2534. return m_flBoostSpeed;
  2535. return baseSpeed;
  2536. }
  2537. //-----------------------------------------------------------------------------
  2538. //-----------------------------------------------------------------------------
  2539. float CNPC_PlayerCompanion::GetIdealAccel() const
  2540. {
  2541. float multiplier = 1.0;
  2542. if ( AI_IsSinglePlayer() )
  2543. {
  2544. if ( m_bMovingAwayFromPlayer && (UTIL_PlayerByIndex(1)->GetAbsOrigin() - GetAbsOrigin()).Length2DSqr() < Square(3.0*12.0) )
  2545. multiplier = 2.0;
  2546. }
  2547. return BaseClass::GetIdealAccel() * multiplier;
  2548. }
  2549. //-----------------------------------------------------------------------------
  2550. //-----------------------------------------------------------------------------
  2551. bool CNPC_PlayerCompanion::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult )
  2552. {
  2553. if ( pMoveGoal->directTrace.flTotalDist - pMoveGoal->directTrace.flDistObstructed < GetHullWidth() * 1.5 )
  2554. {
  2555. CAI_BaseNPC *pBlocker = pMoveGoal->directTrace.pObstruction->MyNPCPointer();
  2556. if ( pBlocker && pBlocker->IsPlayerAlly() && !pBlocker->IsMoving() && !pBlocker->IsInAScript() &&
  2557. ( IsCurSchedule( SCHED_NEW_WEAPON ) ||
  2558. IsCurSchedule( SCHED_GET_HEALTHKIT ) ||
  2559. pBlocker->IsCurSchedule( SCHED_FAIL ) ||
  2560. ( IsInPlayerSquad() && !pBlocker->IsInPlayerSquad() ) ||
  2561. Classify() == CLASS_PLAYER_ALLY_VITAL ||
  2562. IsInAScript() ) )
  2563. {
  2564. if ( pBlocker->ConditionInterruptsCurSchedule( COND_GIVE_WAY ) ||
  2565. pBlocker->ConditionInterruptsCurSchedule( COND_PLAYER_PUSHING ) )
  2566. {
  2567. // HACKHACK
  2568. pBlocker->GetMotor()->SetIdealYawToTarget( WorldSpaceCenter() );
  2569. pBlocker->SetSchedule( SCHED_MOVE_AWAY );
  2570. }
  2571. }
  2572. }
  2573. if ( pMoveGoal->directTrace.pObstruction )
  2574. {
  2575. }
  2576. return BaseClass::OnObstructionPreSteer( pMoveGoal, distClear, pResult );
  2577. }
  2578. //-----------------------------------------------------------------------------
  2579. // Purpose: Whether or not we should always transition with the player
  2580. // Output : Returns true on success, false on failure.
  2581. //-----------------------------------------------------------------------------
  2582. bool CNPC_PlayerCompanion::ShouldAlwaysTransition( void )
  2583. {
  2584. // No matter what, come through
  2585. if ( m_bAlwaysTransition )
  2586. return true;
  2587. // Squadmates always come with
  2588. if ( IsInPlayerSquad() )
  2589. return true;
  2590. // If we're following the player, then come along
  2591. if ( GetFollowBehavior().GetFollowTarget() && GetFollowBehavior().GetFollowTarget()->IsPlayer() )
  2592. return true;
  2593. return false;
  2594. }
  2595. //-----------------------------------------------------------------------------
  2596. //-----------------------------------------------------------------------------
  2597. void CNPC_PlayerCompanion::InputOutsideTransition( inputdata_t &inputdata )
  2598. {
  2599. if ( !AI_IsSinglePlayer() )
  2600. return;
  2601. // Must want to do this
  2602. if ( ShouldAlwaysTransition() == false )
  2603. return;
  2604. // If we're in a vehicle, that vehicle will transition with us still inside (which is preferable)
  2605. if ( IsInAVehicle() )
  2606. return;
  2607. CBaseEntity *pPlayer = UTIL_GetLocalPlayer();
  2608. const Vector &playerPos = pPlayer->GetAbsOrigin();
  2609. // Mark us as already having succeeded if we're vital or always meant to come with the player
  2610. bool bAlwaysTransition = ( ( Classify() == CLASS_PLAYER_ALLY_VITAL ) || m_bAlwaysTransition );
  2611. bool bPathToPlayer = bAlwaysTransition;
  2612. if ( bAlwaysTransition == false )
  2613. {
  2614. AI_Waypoint_t *pPathToPlayer = GetPathfinder()->BuildRoute( GetAbsOrigin(), playerPos, pPlayer, 0 );
  2615. if ( pPathToPlayer )
  2616. {
  2617. bPathToPlayer = true;
  2618. CAI_Path tempPath;
  2619. tempPath.SetWaypoints( pPathToPlayer ); // path object will delete waypoints
  2620. GetPathfinder()->UnlockRouteNodes( pPathToPlayer );
  2621. }
  2622. }
  2623. #ifdef USE_PATHING_LENGTH_REQUIREMENT_FOR_TELEPORT
  2624. float pathLength = tempPath.GetPathDistanceToGoal( GetAbsOrigin() );
  2625. if ( pathLength > 150 * 12 )
  2626. return;
  2627. #endif
  2628. bool bMadeIt = false;
  2629. Vector teleportLocation;
  2630. CAI_Hint *pHint = CAI_HintManager::FindHint( this, HINT_PLAYER_SQUAD_TRANSITON_POINT, bits_HINT_NODE_NEAREST, PLAYERCOMPANION_TRANSITION_SEARCH_DISTANCE, &playerPos );
  2631. while ( pHint )
  2632. {
  2633. pHint->Lock(this);
  2634. pHint->Unlock(0.5); // prevent other squadmates and self from using during transition.
  2635. pHint->GetPosition( GetHullType(), &teleportLocation );
  2636. if ( GetNavigator()->CanFitAtPosition( teleportLocation, MASK_NPCSOLID ) )
  2637. {
  2638. bMadeIt = true;
  2639. if ( !bPathToPlayer && ( playerPos - GetAbsOrigin() ).LengthSqr() > Square(40*12) )
  2640. {
  2641. AI_Waypoint_t *pPathToTeleport = GetPathfinder()->BuildRoute( GetAbsOrigin(), teleportLocation, pPlayer, 0 );
  2642. if ( !pPathToTeleport )
  2643. {
  2644. DevMsg( 2, "NPC \"%s\" failed to teleport to transition a point because there is no path\n", STRING(GetEntityName()) );
  2645. bMadeIt = false;
  2646. }
  2647. else
  2648. {
  2649. CAI_Path tempPath;
  2650. GetPathfinder()->UnlockRouteNodes( pPathToTeleport );
  2651. tempPath.SetWaypoints( pPathToTeleport ); // path object will delete waypoints
  2652. }
  2653. }
  2654. if ( bMadeIt )
  2655. {
  2656. DevMsg( 2, "NPC \"%s\" teleported to transition point %d\n", STRING(GetEntityName()), pHint->GetNodeId() );
  2657. break;
  2658. }
  2659. }
  2660. else
  2661. {
  2662. if ( g_debug_transitions.GetBool() )
  2663. {
  2664. NDebugOverlay::Box( teleportLocation, GetHullMins(), GetHullMaxs(), 255,0,0, 8, 999 );
  2665. }
  2666. }
  2667. pHint = CAI_HintManager::FindHint( this, HINT_PLAYER_SQUAD_TRANSITON_POINT, bits_HINT_NODE_NEAREST, PLAYERCOMPANION_TRANSITION_SEARCH_DISTANCE, &playerPos );
  2668. }
  2669. if ( !bMadeIt )
  2670. {
  2671. // Force us if we didn't find a normal route
  2672. if ( bAlwaysTransition )
  2673. {
  2674. bMadeIt = FindSpotForNPCInRadius( &teleportLocation, pPlayer->GetAbsOrigin(), this, 32.0*1.414, true );
  2675. if ( !bMadeIt )
  2676. bMadeIt = FindSpotForNPCInRadius( &teleportLocation, pPlayer->GetAbsOrigin(), this, 32.0*1.414, false );
  2677. }
  2678. }
  2679. if ( bMadeIt )
  2680. {
  2681. Teleport( &teleportLocation, NULL, NULL );
  2682. }
  2683. else
  2684. {
  2685. DevMsg( 2, "NPC \"%s\" failed to find a suitable transition a point\n", STRING(GetEntityName()) );
  2686. }
  2687. BaseClass::InputOutsideTransition( inputdata );
  2688. }
  2689. //------------------------------------------------------------------------------
  2690. //------------------------------------------------------------------------------
  2691. void CNPC_PlayerCompanion::InputSetReadinessPanic( inputdata_t &inputdata )
  2692. {
  2693. SetReadinessLevel( AIRL_PANIC, true, true );
  2694. }
  2695. //------------------------------------------------------------------------------
  2696. //------------------------------------------------------------------------------
  2697. void CNPC_PlayerCompanion::InputSetReadinessStealth( inputdata_t &inputdata )
  2698. {
  2699. SetReadinessLevel( AIRL_STEALTH, true, true );
  2700. }
  2701. //------------------------------------------------------------------------------
  2702. //------------------------------------------------------------------------------
  2703. void CNPC_PlayerCompanion::InputSetReadinessLow( inputdata_t &inputdata )
  2704. {
  2705. SetReadinessLevel( AIRL_RELAXED, true, true );
  2706. }
  2707. //------------------------------------------------------------------------------
  2708. //------------------------------------------------------------------------------
  2709. void CNPC_PlayerCompanion::InputSetReadinessMedium( inputdata_t &inputdata )
  2710. {
  2711. SetReadinessLevel( AIRL_STIMULATED, true, true );
  2712. }
  2713. //------------------------------------------------------------------------------
  2714. //------------------------------------------------------------------------------
  2715. void CNPC_PlayerCompanion::InputSetReadinessHigh( inputdata_t &inputdata )
  2716. {
  2717. SetReadinessLevel( AIRL_AGITATED, true, true );
  2718. }
  2719. //------------------------------------------------------------------------------
  2720. //------------------------------------------------------------------------------
  2721. void CNPC_PlayerCompanion::InputLockReadiness( inputdata_t &inputdata )
  2722. {
  2723. float value = inputdata.value.Float();
  2724. LockReadiness( value );
  2725. }
  2726. //-----------------------------------------------------------------------------
  2727. // Purpose: Locks the readiness state of the NCP
  2728. // Input : time - if -1, the lock is effectively infinite
  2729. //-----------------------------------------------------------------------------
  2730. void CNPC_PlayerCompanion::LockReadiness( float duration )
  2731. {
  2732. if ( duration == -1.0f )
  2733. {
  2734. m_flReadinessLockedUntil = FLT_MAX;
  2735. }
  2736. else
  2737. {
  2738. m_flReadinessLockedUntil = gpGlobals->curtime + duration;
  2739. }
  2740. }
  2741. //-----------------------------------------------------------------------------
  2742. // Purpose: Unlocks the readiness state
  2743. //-----------------------------------------------------------------------------
  2744. void CNPC_PlayerCompanion::UnlockReadiness( void )
  2745. {
  2746. // Set to the past
  2747. m_flReadinessLockedUntil = gpGlobals->curtime - 0.1f;
  2748. }
  2749. //------------------------------------------------------------------------------
  2750. #ifdef HL2_EPISODIC
  2751. //-----------------------------------------------------------------------------
  2752. // Purpose:
  2753. // Output : Returns true on success, false on failure.
  2754. //-----------------------------------------------------------------------------
  2755. bool CNPC_PlayerCompanion::ShouldDeferToPassengerBehavior( void )
  2756. {
  2757. if ( m_PassengerBehavior.CanSelectSchedule() )
  2758. return true;
  2759. return false;
  2760. }
  2761. //-----------------------------------------------------------------------------
  2762. // Purpose: Determines if this player companion is capable of entering a vehicle
  2763. // Output : Returns true on success, false on failure.
  2764. //-----------------------------------------------------------------------------
  2765. bool CNPC_PlayerCompanion::CanEnterVehicle( void )
  2766. {
  2767. return true;
  2768. }
  2769. //-----------------------------------------------------------------------------
  2770. // Purpose:
  2771. // Output : Returns true on success, false on failure.
  2772. //-----------------------------------------------------------------------------
  2773. bool CNPC_PlayerCompanion::CanExitVehicle( void )
  2774. {
  2775. // See if we can exit our vehicle
  2776. CPropJeepEpisodic *pVehicle = dynamic_cast<CPropJeepEpisodic *>(m_PassengerBehavior.GetTargetVehicle());
  2777. if ( pVehicle != NULL && pVehicle->NPC_CanExitVehicle( this, true ) == false )
  2778. return false;
  2779. return true;
  2780. }
  2781. //-----------------------------------------------------------------------------
  2782. // Purpose:
  2783. // Input : *lpszVehicleName -
  2784. //-----------------------------------------------------------------------------
  2785. void CNPC_PlayerCompanion::EnterVehicle( CBaseEntity *pEntityVehicle, bool bImmediately )
  2786. {
  2787. // Must be allowed to do this
  2788. if ( CanEnterVehicle() == false )
  2789. return;
  2790. // Find the target vehicle
  2791. CPropJeepEpisodic *pVehicle = dynamic_cast<CPropJeepEpisodic *>(pEntityVehicle);
  2792. // Get in the car if it's valid
  2793. if ( pVehicle != NULL && pVehicle->NPC_CanEnterVehicle( this, true ) )
  2794. {
  2795. // Set her into a "passenger" behavior
  2796. m_PassengerBehavior.Enable( pVehicle, bImmediately );
  2797. m_PassengerBehavior.EnterVehicle();
  2798. // Only do this if we're outside the vehicle
  2799. if ( m_PassengerBehavior.GetPassengerState() == PASSENGER_STATE_OUTSIDE )
  2800. {
  2801. SetCondition( COND_PC_BECOMING_PASSENGER );
  2802. }
  2803. }
  2804. }
  2805. //-----------------------------------------------------------------------------
  2806. // Purpose: Get into the requested vehicle
  2807. // Input : &inputdata - contains the entity name of the vehicle to enter
  2808. //-----------------------------------------------------------------------------
  2809. void CNPC_PlayerCompanion::InputEnterVehicle( inputdata_t &inputdata )
  2810. {
  2811. CBaseEntity *pEntity = FindNamedEntity( inputdata.value.String() );
  2812. EnterVehicle( pEntity, false );
  2813. }
  2814. //-----------------------------------------------------------------------------
  2815. // Purpose: Get into the requested vehicle immediately (no animation, pop)
  2816. // Input : &inputdata - contains the entity name of the vehicle to enter
  2817. //-----------------------------------------------------------------------------
  2818. void CNPC_PlayerCompanion::InputEnterVehicleImmediately( inputdata_t &inputdata )
  2819. {
  2820. CBaseEntity *pEntity = FindNamedEntity( inputdata.value.String() );
  2821. EnterVehicle( pEntity, true );
  2822. }
  2823. //-----------------------------------------------------------------------------
  2824. // Purpose:
  2825. //-----------------------------------------------------------------------------
  2826. void CNPC_PlayerCompanion::InputExitVehicle( inputdata_t &inputdata )
  2827. {
  2828. // See if we're allowed to exit the vehicle
  2829. if ( CanExitVehicle() == false )
  2830. return;
  2831. m_PassengerBehavior.ExitVehicle();
  2832. }
  2833. //-----------------------------------------------------------------------------
  2834. // Purpose:
  2835. // Input : &inputdata -
  2836. //-----------------------------------------------------------------------------
  2837. void CNPC_PlayerCompanion::InputCancelEnterVehicle( inputdata_t &inputdata )
  2838. {
  2839. m_PassengerBehavior.CancelEnterVehicle();
  2840. }
  2841. //-----------------------------------------------------------------------------
  2842. // Purpose: Forces the NPC out of the vehicle they're riding in
  2843. // Input : bImmediate - If we need to exit immediately, teleport to any exit location
  2844. // Output : Returns true on success, false on failure.
  2845. //-----------------------------------------------------------------------------
  2846. bool CNPC_PlayerCompanion::ExitVehicle( void )
  2847. {
  2848. // For now just get out
  2849. m_PassengerBehavior.ExitVehicle();
  2850. return true;
  2851. }
  2852. //-----------------------------------------------------------------------------
  2853. // Purpose:
  2854. // Output : Returns true on success, false on failure.
  2855. //-----------------------------------------------------------------------------
  2856. bool CNPC_PlayerCompanion::IsInAVehicle( void ) const
  2857. {
  2858. // Must be active and getting in/out of vehicle
  2859. if ( m_PassengerBehavior.IsEnabled() && m_PassengerBehavior.GetPassengerState() != PASSENGER_STATE_OUTSIDE )
  2860. return true;
  2861. return false;
  2862. }
  2863. //-----------------------------------------------------------------------------
  2864. // Purpose:
  2865. // Output : IServerVehicle -
  2866. //-----------------------------------------------------------------------------
  2867. IServerVehicle *CNPC_PlayerCompanion::GetVehicle( void )
  2868. {
  2869. if ( IsInAVehicle() )
  2870. {
  2871. CPropVehicleDriveable *pDriveableVehicle = m_PassengerBehavior.GetTargetVehicle();
  2872. if ( pDriveableVehicle != NULL )
  2873. return pDriveableVehicle->GetServerVehicle();
  2874. }
  2875. return NULL;
  2876. }
  2877. //-----------------------------------------------------------------------------
  2878. // Purpose:
  2879. // Output : CBaseEntity
  2880. //-----------------------------------------------------------------------------
  2881. CBaseEntity *CNPC_PlayerCompanion::GetVehicleEntity( void )
  2882. {
  2883. if ( IsInAVehicle() )
  2884. {
  2885. CPropVehicleDriveable *pDriveableVehicle = m_PassengerBehavior.GetTargetVehicle();
  2886. return pDriveableVehicle;
  2887. }
  2888. return NULL;
  2889. }
  2890. //-----------------------------------------------------------------------------
  2891. // Purpose: Override our efficiency so that we don't jitter when we're in the middle
  2892. // of our enter/exit animations.
  2893. // Input : bInPVS - Whether we're in the PVS or not
  2894. //-----------------------------------------------------------------------------
  2895. void CNPC_PlayerCompanion::UpdateEfficiency( bool bInPVS )
  2896. {
  2897. // If we're transitioning and in the PVS, we override our efficiency
  2898. if ( IsInAVehicle() && bInPVS )
  2899. {
  2900. PassengerState_e nState = m_PassengerBehavior.GetPassengerState();
  2901. if ( nState == PASSENGER_STATE_ENTERING || nState == PASSENGER_STATE_EXITING )
  2902. {
  2903. SetEfficiency( AIE_NORMAL );
  2904. return;
  2905. }
  2906. }
  2907. // Do the default behavior
  2908. BaseClass::UpdateEfficiency( bInPVS );
  2909. }
  2910. //-----------------------------------------------------------------------------
  2911. // Purpose: Whether or not we can dynamically interact with another NPC
  2912. //-----------------------------------------------------------------------------
  2913. bool CNPC_PlayerCompanion::CanRunAScriptedNPCInteraction( bool bForced /*= false*/ )
  2914. {
  2915. // TODO: Allow this but only for interactions who stem from being in a vehicle?
  2916. if ( IsInAVehicle() )
  2917. return false;
  2918. return BaseClass::CanRunAScriptedNPCInteraction( bForced );
  2919. }
  2920. //-----------------------------------------------------------------------------
  2921. // Purpose:
  2922. //-----------------------------------------------------------------------------
  2923. bool CNPC_PlayerCompanion::IsAllowedToDodge( void )
  2924. {
  2925. // TODO: Allow this but only for interactions who stem from being in a vehicle?
  2926. if ( IsInAVehicle() )
  2927. return false;
  2928. return BaseClass::IsAllowedToDodge();
  2929. }
  2930. #endif //HL2_EPISODIC
  2931. //------------------------------------------------------------------------------
  2932. //-----------------------------------------------------------------------------
  2933. // Purpose: Always transition along with the player
  2934. //-----------------------------------------------------------------------------
  2935. void CNPC_PlayerCompanion::InputEnableAlwaysTransition( inputdata_t &inputdata )
  2936. {
  2937. m_bAlwaysTransition = true;
  2938. }
  2939. //-----------------------------------------------------------------------------
  2940. // Purpose: Stop always transitioning along with the player
  2941. //-----------------------------------------------------------------------------
  2942. void CNPC_PlayerCompanion::InputDisableAlwaysTransition( inputdata_t &inputdata )
  2943. {
  2944. m_bAlwaysTransition = false;
  2945. }
  2946. //-----------------------------------------------------------------------------
  2947. // Purpose: Stop picking up weapons from the ground
  2948. //-----------------------------------------------------------------------------
  2949. void CNPC_PlayerCompanion::InputEnableWeaponPickup( inputdata_t &inputdata )
  2950. {
  2951. m_bDontPickupWeapons = false;
  2952. }
  2953. //-----------------------------------------------------------------------------
  2954. // Purpose: Return to default behavior of picking up better weapons on the ground
  2955. //-----------------------------------------------------------------------------
  2956. void CNPC_PlayerCompanion::InputDisableWeaponPickup( inputdata_t &inputdata )
  2957. {
  2958. m_bDontPickupWeapons = true;
  2959. }
  2960. //------------------------------------------------------------------------------
  2961. // Purpose: Give the NPC in question the weapon specified
  2962. //------------------------------------------------------------------------------
  2963. void CNPC_PlayerCompanion::InputGiveWeapon( inputdata_t &inputdata )
  2964. {
  2965. // Give the NPC the specified weapon
  2966. string_t iszWeaponName = inputdata.value.StringID();
  2967. if ( iszWeaponName != NULL_STRING )
  2968. {
  2969. if( Classify() == CLASS_PLAYER_ALLY_VITAL )
  2970. {
  2971. m_iszPendingWeapon = iszWeaponName;
  2972. }
  2973. else
  2974. {
  2975. GiveWeapon( iszWeaponName );
  2976. }
  2977. }
  2978. }
  2979. #if HL2_EPISODIC
  2980. //------------------------------------------------------------------------------
  2981. // Purpose: Delete all outputs from this NPC.
  2982. //------------------------------------------------------------------------------
  2983. void CNPC_PlayerCompanion::InputClearAllOuputs( inputdata_t &inputdata )
  2984. {
  2985. datamap_t *dmap = GetDataDescMap();
  2986. while ( dmap )
  2987. {
  2988. int fields = dmap->dataNumFields;
  2989. for ( int i = 0; i < fields; i++ )
  2990. {
  2991. typedescription_t *dataDesc = &dmap->dataDesc[i];
  2992. if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
  2993. {
  2994. CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]);
  2995. pOutput->DeleteAllElements();
  2996. /*
  2997. int nConnections = pOutput->NumberOfElements();
  2998. for ( int j = 0; j < nConnections; j++ )
  2999. {
  3000. }
  3001. */
  3002. }
  3003. }
  3004. dmap = dmap->baseMap;
  3005. }
  3006. }
  3007. #endif
  3008. //-----------------------------------------------------------------------------
  3009. // Purpose: Player in our squad killed something
  3010. // Input : *pVictim - Who he killed
  3011. // &info - How they died
  3012. //-----------------------------------------------------------------------------
  3013. void CNPC_PlayerCompanion::OnPlayerKilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info )
  3014. {
  3015. // filter everything that comes in here that isn't an NPC
  3016. CAI_BaseNPC *pCombatVictim = dynamic_cast<CAI_BaseNPC *>( pVictim );
  3017. if ( !pCombatVictim )
  3018. {
  3019. return;
  3020. }
  3021. CBaseEntity *pInflictor = info.GetInflictor();
  3022. int iNumBarrels = 0;
  3023. int iConsecutivePlayerKills = 0;
  3024. bool bPuntedGrenade = false;
  3025. bool bVictimWasEnemy = false;
  3026. bool bVictimWasMob = false;
  3027. bool bVictimWasAttacker = false;
  3028. bool bHeadshot = false;
  3029. bool bOneShot = false;
  3030. if ( dynamic_cast<CBreakableProp *>( pInflictor ) && ( info.GetDamageType() & DMG_BLAST ) )
  3031. {
  3032. // if a barrel explodes that was initiated by the player within a few seconds of the previous one,
  3033. // increment a counter to keep track of how many have exploded in a row.
  3034. if ( gpGlobals->curtime - m_fLastBarrelExploded >= MAX_TIME_BETWEEN_BARRELS_EXPLODING )
  3035. {
  3036. m_iNumConsecutiveBarrelsExploded = 0;
  3037. }
  3038. m_iNumConsecutiveBarrelsExploded++;
  3039. m_fLastBarrelExploded = gpGlobals->curtime;
  3040. iNumBarrels = m_iNumConsecutiveBarrelsExploded;
  3041. }
  3042. else
  3043. {
  3044. // if player kills an NPC within a few seconds of the previous kill,
  3045. // increment a counter to keep track of how many he's killed in a row.
  3046. if ( gpGlobals->curtime - m_fLastPlayerKill >= MAX_TIME_BETWEEN_CONSECUTIVE_PLAYER_KILLS )
  3047. {
  3048. m_iNumConsecutivePlayerKills = 0;
  3049. }
  3050. m_iNumConsecutivePlayerKills++;
  3051. m_fLastPlayerKill = gpGlobals->curtime;
  3052. iConsecutivePlayerKills = m_iNumConsecutivePlayerKills;
  3053. }
  3054. // don't comment on kills when she can't see the victim
  3055. if ( !FVisible( pVictim ) )
  3056. {
  3057. return;
  3058. }
  3059. // check if the player killed an enemy by punting a grenade
  3060. if ( pInflictor && Fraggrenade_WasPunted( pInflictor ) && Fraggrenade_WasCreatedByCombine( pInflictor ) )
  3061. {
  3062. bPuntedGrenade = true;
  3063. }
  3064. // check if the victim was Alyx's enemy
  3065. if ( GetEnemy() == pVictim )
  3066. {
  3067. bVictimWasEnemy = true;
  3068. }
  3069. AI_EnemyInfo_t *pEMemory = GetEnemies()->Find( pVictim );
  3070. if ( pEMemory != NULL )
  3071. {
  3072. // was Alyx being mobbed by this enemy?
  3073. bVictimWasMob = pEMemory->bMobbedMe;
  3074. // has Alyx recieved damage from this enemy?
  3075. if ( pEMemory->timeLastReceivedDamageFrom > 0 ) {
  3076. bVictimWasAttacker = true;
  3077. }
  3078. }
  3079. // Was it a headshot?
  3080. if ( ( pCombatVictim->LastHitGroup() == HITGROUP_HEAD ) && ( info.GetDamageType() & DMG_BULLET ) )
  3081. {
  3082. bHeadshot = true;
  3083. }
  3084. // Did the player kill the enemy with 1 shot?
  3085. if ( ( pCombatVictim->GetDamageCount() == 1 ) && ( info.GetDamageType() & DMG_BULLET ) )
  3086. {
  3087. bOneShot = true;
  3088. }
  3089. // set up the speech modifiers
  3090. CFmtStrN<512> modifiers( "num_barrels:%d,distancetoplayerenemy:%f,playerAmmo:%s,consecutive_player_kills:%d,"
  3091. "punted_grenade:%d,victim_was_enemy:%d,victim_was_mob:%d,victim_was_attacker:%d,headshot:%d,oneshot:%d",
  3092. iNumBarrels, EnemyDistance( pVictim ), info.GetAmmoName(), iConsecutivePlayerKills,
  3093. bPuntedGrenade, bVictimWasEnemy, bVictimWasMob, bVictimWasAttacker, bHeadshot, bOneShot );
  3094. SpeakIfAllowed( TLK_PLAYER_KILLED_NPC, modifiers );
  3095. BaseClass::OnPlayerKilledOther( pVictim, info );
  3096. }
  3097. //-----------------------------------------------------------------------------
  3098. //-----------------------------------------------------------------------------
  3099. bool CNPC_PlayerCompanion::IsNavigationUrgent( void )
  3100. {
  3101. bool bBase = BaseClass::IsNavigationUrgent();
  3102. // Consider follow & assault behaviour urgent
  3103. if ( !bBase && (m_FollowBehavior.IsActive() || ( m_AssaultBehavior.IsRunning() && m_AssaultBehavior.IsUrgent() )) && Classify() == CLASS_PLAYER_ALLY_VITAL )
  3104. {
  3105. // But only if the blocker isn't the player, and isn't a physics object that's still moving
  3106. CBaseEntity *pBlocker = GetNavigator()->GetBlockingEntity();
  3107. if ( pBlocker && !pBlocker->IsPlayer() )
  3108. {
  3109. IPhysicsObject *pPhysObject = pBlocker->VPhysicsGetObject();
  3110. if ( pPhysObject && !pPhysObject->IsAsleep() )
  3111. return false;
  3112. if ( pBlocker->IsNPC() )
  3113. return false;
  3114. }
  3115. // If we're within the player's viewcone, then don't teleport.
  3116. // This test was made more general because previous iterations had cases where characters
  3117. // could not see the player but the player could in fact see them. Now the NPC's facing is
  3118. // irrelevant and the player's viewcone is more authorative. -- jdw
  3119. CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
  3120. if ( pLocalPlayer->FInViewCone( EyePosition() ) )
  3121. return false;
  3122. return true;
  3123. }
  3124. return bBase;
  3125. }
  3126. //-----------------------------------------------------------------------------
  3127. //
  3128. // Schedules
  3129. //
  3130. //-----------------------------------------------------------------------------
  3131. AI_BEGIN_CUSTOM_NPC( player_companion_base, CNPC_PlayerCompanion )
  3132. // AI Interaction for being hit by a physics object
  3133. DECLARE_INTERACTION(g_interactionHitByPlayerThrownPhysObj)
  3134. DECLARE_INTERACTION(g_interactionPlayerPuntedHeavyObject)
  3135. DECLARE_CONDITION( COND_PC_HURTBYFIRE )
  3136. DECLARE_CONDITION( COND_PC_SAFE_FROM_MORTAR )
  3137. DECLARE_CONDITION( COND_PC_BECOMING_PASSENGER )
  3138. DECLARE_TASK( TASK_PC_WAITOUT_MORTAR )
  3139. DECLARE_TASK( TASK_PC_GET_PATH_OFF_COMPANION )
  3140. DECLARE_ANIMEVENT( AE_COMPANION_PRODUCE_FLARE )
  3141. DECLARE_ANIMEVENT( AE_COMPANION_LIGHT_FLARE )
  3142. DECLARE_ANIMEVENT( AE_COMPANION_RELEASE_FLARE )
  3143. //=========================================================
  3144. // > TakeCoverFromBestSound
  3145. //
  3146. // Find cover and move towards it, but only do so for a short
  3147. // time. This is appropriate when the dangerous item is going
  3148. // to detonate very soon. This way our NPC doesn't run a great
  3149. // distance from an object that explodes shortly after the NPC
  3150. // gets underway.
  3151. //=========================================================
  3152. DEFINE_SCHEDULE
  3153. (
  3154. SCHED_PC_MOVE_TOWARDS_COVER_FROM_BEST_SOUND,
  3155. " Tasks"
  3156. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FLEE_FROM_BEST_SOUND"
  3157. " TASK_STOP_MOVING 0"
  3158. " TASK_SET_TOLERANCE_DISTANCE 24"
  3159. " TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0"
  3160. " TASK_FIND_COVER_FROM_BEST_SOUND 0"
  3161. " TASK_RUN_PATH_TIMED 1.0"
  3162. " TASK_STOP_MOVING 0"
  3163. " TASK_FACE_SAVEPOSITION 0"
  3164. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
  3165. ""
  3166. " Interrupts"
  3167. " COND_PC_SAFE_FROM_MORTAR"
  3168. )
  3169. DEFINE_SCHEDULE
  3170. (
  3171. SCHED_PC_TAKE_COVER_FROM_BEST_SOUND,
  3172. " Tasks"
  3173. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_FLEE_FROM_BEST_SOUND"
  3174. " TASK_STOP_MOVING 0"
  3175. " TASK_SET_TOLERANCE_DISTANCE 24"
  3176. " TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0"
  3177. " TASK_FIND_COVER_FROM_BEST_SOUND 0"
  3178. " TASK_RUN_PATH 0"
  3179. " TASK_WAIT_FOR_MOVEMENT 0"
  3180. " TASK_STOP_MOVING 0"
  3181. " TASK_FACE_SAVEPOSITION 0"
  3182. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
  3183. ""
  3184. " Interrupts"
  3185. " COND_NEW_ENEMY"
  3186. " COND_PC_SAFE_FROM_MORTAR"
  3187. )
  3188. DEFINE_SCHEDULE
  3189. (
  3190. SCHED_PC_COWER,
  3191. " Tasks"
  3192. " TASK_WAIT_RANDOM 0.1"
  3193. " TASK_SET_ACTIVITY ACTIVITY:ACT_COWER"
  3194. " TASK_PC_WAITOUT_MORTAR 0"
  3195. " TASK_WAIT 0.1"
  3196. " TASK_WAIT_RANDOM 0.5"
  3197. ""
  3198. " Interrupts"
  3199. " "
  3200. )
  3201. //=========================================================
  3202. //
  3203. //=========================================================
  3204. DEFINE_SCHEDULE
  3205. (
  3206. SCHED_PC_FLEE_FROM_BEST_SOUND,
  3207. " Tasks"
  3208. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COWER"
  3209. " TASK_GET_PATH_AWAY_FROM_BEST_SOUND 600"
  3210. " TASK_RUN_PATH_TIMED 1.5"
  3211. " TASK_STOP_MOVING 0"
  3212. " TASK_TURN_LEFT 179"
  3213. ""
  3214. " Interrupts"
  3215. " COND_NEW_ENEMY"
  3216. " COND_PC_SAFE_FROM_MORTAR"
  3217. )
  3218. //=========================================================
  3219. DEFINE_SCHEDULE
  3220. (
  3221. SCHED_PC_FAIL_TAKE_COVER_TURRET,
  3222. " Tasks"
  3223. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COWER"
  3224. " TASK_STOP_MOVING 0"
  3225. " TASK_MOVE_AWAY_PATH 600"
  3226. " TASK_RUN_PATH_FLEE 100"
  3227. " TASK_STOP_MOVING 0"
  3228. " TASK_TURN_LEFT 179"
  3229. ""
  3230. " Interrupts"
  3231. " COND_NEW_ENEMY"
  3232. )
  3233. //=========================================================
  3234. DEFINE_SCHEDULE
  3235. (
  3236. SCHED_PC_FAKEOUT_MORTAR,
  3237. " Tasks"
  3238. " TASK_MOVE_AWAY_PATH 300"
  3239. " TASK_RUN_PATH 0"
  3240. " TASK_WAIT_FOR_MOVEMENT 0"
  3241. ""
  3242. " Interrupts"
  3243. " COND_HEAR_DANGER"
  3244. )
  3245. //=========================================================
  3246. DEFINE_SCHEDULE
  3247. (
  3248. SCHED_PC_GET_OFF_COMPANION,
  3249. " Tasks"
  3250. " TASK_PC_GET_PATH_OFF_COMPANION 0"
  3251. " TASK_RUN_PATH 0"
  3252. " TASK_WAIT_FOR_MOVEMENT 0"
  3253. ""
  3254. " Interrupts"
  3255. ""
  3256. )
  3257. AI_END_CUSTOM_NPC()
  3258. //
  3259. // Special movement overrides for player companions
  3260. //
  3261. #define NUM_OVERRIDE_MOVE_CLASSNAMES 4
  3262. class COverrideMoveCache : public IEntityListener
  3263. {
  3264. public:
  3265. void LevelInitPreEntity( void )
  3266. {
  3267. CacheClassnames();
  3268. gEntList.AddListenerEntity( this );
  3269. Clear();
  3270. }
  3271. void LevelShutdownPostEntity( void )
  3272. {
  3273. gEntList.RemoveListenerEntity( this );
  3274. Clear();
  3275. }
  3276. inline void Clear( void )
  3277. {
  3278. m_Cache.Purge();
  3279. }
  3280. inline bool MatchesCriteria( CBaseEntity *pEntity )
  3281. {
  3282. for ( int i = 0; i < NUM_OVERRIDE_MOVE_CLASSNAMES; i++ )
  3283. {
  3284. if ( pEntity->m_iClassname == m_Classname[i] )
  3285. return true;
  3286. }
  3287. return false;
  3288. }
  3289. virtual void OnEntitySpawned( CBaseEntity *pEntity )
  3290. {
  3291. if ( MatchesCriteria( pEntity ) )
  3292. {
  3293. m_Cache.AddToTail( pEntity );
  3294. }
  3295. };
  3296. virtual void OnEntityDeleted( CBaseEntity *pEntity )
  3297. {
  3298. if ( !m_Cache.Count() )
  3299. return;
  3300. if ( MatchesCriteria( pEntity ) )
  3301. {
  3302. m_Cache.FindAndRemove( pEntity );
  3303. }
  3304. };
  3305. CBaseEntity *FindTargetsInRadius( CBaseEntity *pFirstEntity, const Vector &vecOrigin, float flRadius )
  3306. {
  3307. if ( !m_Cache.Count() )
  3308. return NULL;
  3309. int nIndex = m_Cache.InvalidIndex();
  3310. // If we're starting with an entity, start there and move past it
  3311. if ( pFirstEntity != NULL )
  3312. {
  3313. nIndex = m_Cache.Find( pFirstEntity );
  3314. nIndex = m_Cache.Next( nIndex );
  3315. if ( nIndex == m_Cache.InvalidIndex() )
  3316. return NULL;
  3317. }
  3318. else
  3319. {
  3320. nIndex = m_Cache.Head();
  3321. }
  3322. CBaseEntity *pTarget = NULL;
  3323. const float flRadiusSqr = Square( flRadius );
  3324. // Look through each cached target, looking for one in our range
  3325. while ( nIndex != m_Cache.InvalidIndex() )
  3326. {
  3327. pTarget = m_Cache[nIndex];
  3328. if ( pTarget && ( pTarget->GetAbsOrigin() - vecOrigin ).LengthSqr() < flRadiusSqr )
  3329. return pTarget;
  3330. nIndex = m_Cache.Next( nIndex );
  3331. }
  3332. return NULL;
  3333. }
  3334. void ForceRepopulateList( void )
  3335. {
  3336. Clear();
  3337. CacheClassnames();
  3338. CBaseEntity *pEnt = gEntList.FirstEnt();
  3339. while( pEnt )
  3340. {
  3341. if( MatchesCriteria( pEnt ) )
  3342. {
  3343. m_Cache.AddToTail( pEnt );
  3344. }
  3345. pEnt = gEntList.NextEnt( pEnt );
  3346. }
  3347. }
  3348. private:
  3349. inline void CacheClassnames( void )
  3350. {
  3351. m_Classname[0] = AllocPooledString( "env_fire" );
  3352. m_Classname[1] = AllocPooledString( "combine_mine" );
  3353. m_Classname[2] = AllocPooledString( "npc_turret_floor" );
  3354. m_Classname[3] = AllocPooledString( "entityflame" );
  3355. }
  3356. CUtlLinkedList<EHANDLE> m_Cache;
  3357. string_t m_Classname[NUM_OVERRIDE_MOVE_CLASSNAMES];
  3358. };
  3359. // Singleton for access
  3360. COverrideMoveCache g_OverrideMoveCache;
  3361. COverrideMoveCache *OverrideMoveCache( void ) { return &g_OverrideMoveCache; }
  3362. CBaseEntity *OverrideMoveCache_FindTargetsInRadius( CBaseEntity *pFirstEntity, const Vector &vecOrigin, float flRadius )
  3363. {
  3364. return g_OverrideMoveCache.FindTargetsInRadius( pFirstEntity, vecOrigin, flRadius );
  3365. }
  3366. void OverrideMoveCache_ForceRepopulateList( void )
  3367. {
  3368. g_OverrideMoveCache.ForceRepopulateList();
  3369. }
  3370. void OverrideMoveCache_LevelInitPreEntity( void )
  3371. {
  3372. g_OverrideMoveCache.LevelInitPreEntity();
  3373. }
  3374. void OverrideMoveCache_LevelShutdownPostEntity( void )
  3375. {
  3376. g_OverrideMoveCache.LevelShutdownPostEntity();
  3377. }