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.

3603 lines
105 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Alyx, the female sidekick and love interest that's taking the world by storm!
  4. //
  5. // Try the new Alyx Brite toothpaste!
  6. // Alyx lederhosen!
  7. //
  8. // FIXME: need a better comment block
  9. //
  10. //=============================================================================//
  11. #include "cbase.h"
  12. #include "npcevent.h"
  13. #include "ai_basenpc.h"
  14. #include "ai_hull.h"
  15. #include "ai_basehumanoid.h"
  16. #include "ai_behavior_follow.h"
  17. #include "npc_alyx_episodic.h"
  18. #include "npc_headcrab.h"
  19. #include "npc_BaseZombie.h"
  20. #include "ai_senses.h"
  21. #include "ai_memory.h"
  22. #include "soundent.h"
  23. #include "props.h"
  24. #include "IEffects.h"
  25. #include "globalstate.h"
  26. #include "weapon_physcannon.h"
  27. #include "info_darknessmode_lightsource.h"
  28. #include "sceneentity.h"
  29. #include "hl2_gamerules.h"
  30. #include "scripted.h"
  31. #include "hl2_player.h"
  32. #include "env_alyxemp_shared.h"
  33. #include "basehlcombatweapon.h"
  34. #include "basegrenade_shared.h"
  35. #include "ai_interactions.h"
  36. #include "weapon_flaregun.h"
  37. #include "env_debughistory.h"
  38. extern Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint);
  39. // memdbgon must be the last include file in a .cpp file!!!
  40. #include "tier0/memdbgon.h"
  41. bool g_HackOutland10DamageHack;
  42. int ACT_ALYX_DRAW_TOOL;
  43. int ACT_ALYX_IDLE_TOOL;
  44. int ACT_ALYX_ZAP_TOOL;
  45. int ACT_ALYX_HOLSTER_TOOL;
  46. int ACT_ALYX_PICKUP_RACK;
  47. string_t CLASSNAME_ALYXGUN;
  48. string_t CLASSNAME_SMG1;
  49. string_t CLASSNAME_SHOTGUN;
  50. string_t CLASSNAME_AR2;
  51. bool IsInCommentaryMode( void );
  52. #define ALYX_BREATHING_VOLUME_MAX 1.0
  53. #define ALYX_DARKNESS_LOST_PLAYER_DIST ( 120 * 120 ) // 12 feet
  54. #define ALYX_MIN_MOB_DIST_SQR Square(120) // Any enemy closer than this adds to the 'mob'
  55. #define ALYX_MIN_CONSIDER_DIST Square(1200) // Only enemies within this range are counted and considered to generate AI speech
  56. #define CONCEPT_ALYX_REQUEST_ITEM "TLK_ALYX_REQUEST_ITEM"
  57. #define CONCEPT_ALYX_INTERACTION_DONE "TLK_ALYX_INTERACTION_DONE"
  58. #define CONCEPT_ALYX_CANCEL_INTERACTION "TLK_ALYX_CANCEL_INTERACTION"
  59. #define ALYX_MIN_ENEMY_DIST_TO_CROUCH 360 // Minimum distance that our enemy must be for me to crouch
  60. #define ALYX_MIN_ENEMY_HEALTH_TO_CROUCH 15
  61. #define ALYX_CROUCH_DELAY 5 // Time after crouching before Alyx will crouch again
  62. //-----------------------------------------------------------------------------
  63. // Interactions
  64. //-----------------------------------------------------------------------------
  65. extern int g_interactionZombieMeleeWarning;
  66. LINK_ENTITY_TO_CLASS( npc_alyx, CNPC_Alyx );
  67. BEGIN_DATADESC( CNPC_Alyx )
  68. DEFINE_FIELD( m_hEmpTool, FIELD_EHANDLE ),
  69. DEFINE_FIELD( m_hHackTarget, FIELD_EHANDLE ),
  70. DEFINE_FIELD( m_hStealthLookTarget, FIELD_EHANDLE ),
  71. DEFINE_FIELD( m_bInteractionAllowed, FIELD_BOOLEAN ),
  72. DEFINE_FIELD( m_fTimeNextSearchForInteractTargets, FIELD_TIME ),
  73. DEFINE_FIELD( m_bDarknessSpeechAllowed, FIELD_BOOLEAN ),
  74. DEFINE_FIELD( m_bIsEMPHolstered, FIELD_BOOLEAN ),
  75. DEFINE_FIELD( m_bIsFlashlightBlind, FIELD_BOOLEAN ),
  76. DEFINE_FIELD( m_fStayBlindUntil, FIELD_TIME ),
  77. DEFINE_FIELD( m_flDontBlindUntil, FIELD_TIME ),
  78. DEFINE_FIELD( m_bSpokeLostPlayerInDarkness, FIELD_BOOLEAN ),
  79. DEFINE_FIELD( m_bPlayerFlashlightState, FIELD_BOOLEAN ),
  80. DEFINE_FIELD( m_bHadCondSeeEnemy, FIELD_BOOLEAN ),
  81. DEFINE_FIELD( m_iszCurrentBlindScene, FIELD_STRING ),
  82. DEFINE_FIELD( m_fTimeUntilNextDarknessFoundPlayer, FIELD_TIME ),
  83. DEFINE_FIELD( m_fCombatStartTime, FIELD_TIME ),
  84. DEFINE_FIELD( m_fCombatEndTime, FIELD_TIME ),
  85. DEFINE_FIELD( m_flNextCrouchTime, FIELD_TIME ),
  86. DEFINE_FIELD( m_WeaponType, FIELD_INTEGER ),
  87. DEFINE_KEYFIELD( m_bShouldHaveEMP, FIELD_BOOLEAN, "ShouldHaveEMP" ),
  88. DEFINE_SOUNDPATCH( m_sndDarknessBreathing ),
  89. DEFINE_EMBEDDED( m_SpeechWatch_LostPlayer ),
  90. DEFINE_EMBEDDED( m_SpeechTimer_HeardSound ),
  91. DEFINE_EMBEDDED( m_SpeechWatch_SoundDelay ),
  92. DEFINE_EMBEDDED( m_SpeechWatch_BreathingRamp ),
  93. DEFINE_EMBEDDED( m_SpeechWatch_FoundPlayer ),
  94. DEFINE_EMBEDDED( m_MoveMonitor ),
  95. DEFINE_INPUTFUNC( FIELD_VOID, "DisallowInteraction", InputDisallowInteraction ),
  96. DEFINE_INPUTFUNC( FIELD_VOID, "AllowInteraction", InputAllowInteraction ),
  97. DEFINE_INPUTFUNC( FIELD_STRING, "GiveWeapon", InputGiveWeapon ),
  98. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "AllowDarknessSpeech", InputAllowDarknessSpeech ),
  99. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "GiveEMP", InputGiveEMP ),
  100. DEFINE_INPUTFUNC( FIELD_VOID, "VehiclePunted", InputVehiclePunted ),
  101. DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ),
  102. DEFINE_OUTPUT( m_OnFinishInteractWithObject, "OnFinishInteractWithObject" ),
  103. DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ),
  104. DEFINE_USEFUNC( Use ),
  105. END_DATADESC()
  106. #define ALYX_FEAR_ZOMBIE_DIST_SQR Square(60)
  107. #define ALYX_FEAR_ANTLION_DIST_SQR Square(360)
  108. //-----------------------------------------------------------------------------
  109. // Anim events
  110. //-----------------------------------------------------------------------------
  111. static int AE_ALYX_EMPTOOL_ATTACHMENT;
  112. static int AE_ALYX_EMPTOOL_SEQUENCE;
  113. static int AE_ALYX_EMPTOOL_USE;
  114. static int COMBINE_AE_BEGIN_ALTFIRE;
  115. static int COMBINE_AE_ALTFIRE;
  116. ConVar npc_alyx_readiness( "npc_alyx_readiness", "1" );
  117. ConVar npc_alyx_force_stop_moving( "npc_alyx_force_stop_moving", "1" );
  118. ConVar npc_alyx_readiness_transitions( "npc_alyx_readiness_transitions", "1" );
  119. ConVar npc_alyx_crouch( "npc_alyx_crouch", "1" );
  120. // global pointer to Alyx for fast lookups
  121. CEntityClassList<CNPC_Alyx> g_AlyxList;
  122. template <> CNPC_Alyx *CEntityClassList<CNPC_Alyx>::m_pClassList = NULL;
  123. //=========================================================
  124. // initialize Alyx before keyvalues are processed
  125. //=========================================================
  126. CNPC_Alyx::CNPC_Alyx()
  127. {
  128. g_AlyxList.Insert(this);
  129. // defaults to having an EMP
  130. m_bShouldHaveEMP = true;
  131. }
  132. CNPC_Alyx::~CNPC_Alyx( )
  133. {
  134. g_AlyxList.Remove(this);
  135. }
  136. //=========================================================
  137. // Classify - indicates this NPC's place in the
  138. // relationship table.
  139. //=========================================================
  140. Class_T CNPC_Alyx::Classify ( void )
  141. {
  142. return CLASS_PLAYER_ALLY_VITAL;
  143. }
  144. //-----------------------------------------------------------------------------
  145. //-----------------------------------------------------------------------------
  146. bool CNPC_Alyx::FValidateHintType( CAI_Hint *pHint )
  147. {
  148. switch( pHint->HintType() )
  149. {
  150. case HINT_WORLD_VISUALLY_INTERESTING:
  151. return true;
  152. break;
  153. case HINT_WORLD_VISUALLY_INTERESTING_STEALTH:
  154. return true;
  155. break;
  156. }
  157. return BaseClass::FValidateHintType( pHint );
  158. }
  159. //-----------------------------------------------------------------------------
  160. //-----------------------------------------------------------------------------
  161. int CNPC_Alyx::ObjectCaps()
  162. {
  163. int caps = BaseClass::ObjectCaps();
  164. if( m_FuncTankBehavior.IsMounted() )
  165. {
  166. caps &= ~FCAP_IMPULSE_USE;
  167. }
  168. return caps;
  169. }
  170. //=========================================================
  171. // HandleAnimEvent - catches the NPC-specific messages
  172. // that occur when tagged animation frames are played.
  173. //=========================================================
  174. void CNPC_Alyx::HandleAnimEvent( animevent_t *pEvent )
  175. {
  176. if (pEvent->event == AE_ALYX_EMPTOOL_ATTACHMENT)
  177. {
  178. if (!m_hEmpTool)
  179. {
  180. // Old savegame?
  181. CreateEmpTool();
  182. if (!m_hEmpTool)
  183. return;
  184. }
  185. int iAttachment = LookupAttachment( pEvent->options );
  186. m_hEmpTool->SetParent(this, iAttachment);
  187. m_hEmpTool->SetLocalOrigin( Vector( 0, 0, 0 ) );
  188. m_hEmpTool->SetLocalAngles( QAngle( 0, 0, 0 ) );
  189. if( !stricmp( pEvent->options, "Emp_Holster" ) )
  190. {
  191. SetEMPHolstered(true);
  192. }
  193. else
  194. {
  195. SetEMPHolstered(false);
  196. }
  197. return;
  198. }
  199. else if (pEvent->event == AE_ALYX_EMPTOOL_SEQUENCE)
  200. {
  201. if (!m_hEmpTool)
  202. return;
  203. CDynamicProp *pEmpTool = dynamic_cast<CDynamicProp *>(m_hEmpTool.Get());
  204. if (!pEmpTool)
  205. return;
  206. int iSequence = pEmpTool->LookupSequence( pEvent->options );
  207. if (iSequence != ACT_INVALID)
  208. {
  209. pEmpTool->PropSetSequence( iSequence );
  210. }
  211. return;
  212. }
  213. else if (pEvent->event == AE_ALYX_EMPTOOL_USE)
  214. {
  215. if( m_OperatorBehavior.IsGoalReady() )
  216. {
  217. if( m_OperatorBehavior.m_hContextTarget.Get() != NULL )
  218. {
  219. EmpZapTarget( m_OperatorBehavior.m_hContextTarget );
  220. }
  221. }
  222. return;
  223. }
  224. else if ( pEvent->event == COMBINE_AE_BEGIN_ALTFIRE )
  225. {
  226. EmitSound( "Weapon_CombineGuard.Special1" );
  227. return;
  228. }
  229. else if ( pEvent->event == COMBINE_AE_ALTFIRE )
  230. {
  231. animevent_t fakeEvent;
  232. fakeEvent.pSource = this;
  233. fakeEvent.event = EVENT_WEAPON_AR2_ALTFIRE;
  234. GetActiveWeapon()->Operator_HandleAnimEvent( &fakeEvent, this );
  235. //m_iNumGrenades--;
  236. return;
  237. }
  238. switch( pEvent->event )
  239. {
  240. case 1:
  241. default:
  242. BaseClass::HandleAnimEvent( pEvent );
  243. break;
  244. }
  245. }
  246. //=========================================================
  247. // Returns a pointer to Alyx's entity
  248. //=========================================================
  249. CNPC_Alyx *CNPC_Alyx::GetAlyx( void )
  250. {
  251. return g_AlyxList.m_pClassList;
  252. }
  253. //=========================================================
  254. //
  255. //=========================================================
  256. bool CNPC_Alyx::CreateBehaviors()
  257. {
  258. AddBehavior( &m_FuncTankBehavior );
  259. bool result = BaseClass::CreateBehaviors();
  260. return result;
  261. }
  262. //=========================================================
  263. // Spawn
  264. //=========================================================
  265. void CNPC_Alyx::Spawn()
  266. {
  267. BaseClass::Spawn();
  268. // If Alyx has a parent, she's currently inside a pod. Prevent her from moving.
  269. if ( GetMoveParent() )
  270. {
  271. SetMoveType( MOVETYPE_NONE );
  272. CapabilitiesClear();
  273. CapabilitiesAdd( bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD );
  274. CapabilitiesAdd( bits_CAP_FRIENDLY_DMG_IMMUNE );
  275. }
  276. else
  277. {
  278. SetupAlyxWithoutParent();
  279. CreateEmpTool( );
  280. }
  281. AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
  282. m_iHealth = 80;
  283. m_bloodColor = DONT_BLEED;
  284. NPCInit();
  285. SetUse( &CNPC_Alyx::Use );
  286. m_bInteractionAllowed = true;
  287. m_fTimeNextSearchForInteractTargets = gpGlobals->curtime;
  288. SetEMPHolstered(true);
  289. m_bDontPickupWeapons = true;
  290. m_bDarknessSpeechAllowed = true;
  291. m_fCombatStartTime = 0.0f;
  292. m_fCombatEndTime = 0.0f;
  293. m_AnnounceAttackTimer.Set( 3, 5 );
  294. }
  295. //=========================================================
  296. // Precache - precaches all resources this NPC needs
  297. //=========================================================
  298. void CNPC_Alyx::Precache()
  299. {
  300. BaseClass::Precache();
  301. PrecacheScriptSound( "npc_alyx.die" );
  302. PrecacheModel( STRING( GetModelName() ) );
  303. PrecacheModel( "models/alyx_emptool_prop.mdl" );
  304. // For hacking
  305. PrecacheScriptSound( "DoSpark" );
  306. PrecacheScriptSound( "npc_alyx.starthacking" );
  307. PrecacheScriptSound( "npc_alyx.donehacking" );
  308. PrecacheScriptSound( "npc_alyx.readytohack" );
  309. PrecacheScriptSound( "npc_alyx.interruptedhacking" );
  310. PrecacheScriptSound( "ep_01.al_dark_breathing01" );
  311. PrecacheScriptSound( "Weapon_CombineGuard.Special1" );
  312. UTIL_PrecacheOther( "env_alyxemp" );
  313. CLASSNAME_ALYXGUN = AllocPooledString( "weapon_alyxgun" );
  314. CLASSNAME_SMG1 = AllocPooledString( "weapon_smg1" );
  315. CLASSNAME_SHOTGUN = AllocPooledString( "weapon_shotgun" );
  316. CLASSNAME_AR2 = AllocPooledString( "weapon_ar2" );
  317. }
  318. //-----------------------------------------------------------------------------
  319. // Purpose:
  320. //-----------------------------------------------------------------------------
  321. void CNPC_Alyx::Activate( void )
  322. {
  323. // Alyx always kicks her health back up to full after loading a savegame.
  324. // Avoids problems with players saving the game in places where she dies immediately afterwards.
  325. m_iHealth = 80;
  326. BaseClass::Activate();
  327. // Alyx always assumes she has said hello to Gordon!
  328. SetSpokeConcept( TLK_HELLO, NULL, false );
  329. // Add my personal concepts
  330. CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager();
  331. if( pSpeechManager )
  332. {
  333. ConceptInfo_t conceptRequestItem =
  334. {
  335. CONCEPT_ALYX_REQUEST_ITEM, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_TARGET_PLAYER
  336. };
  337. pSpeechManager->AddCustomConcept( conceptRequestItem );
  338. }
  339. // cleanup savegames that may not have this set
  340. if (m_hEmpTool)
  341. {
  342. m_hEmpTool->AddEffects( EF_PARENT_ANIMATES );
  343. }
  344. m_WeaponType = ComputeWeaponType();
  345. // !!!HACKHACK for Overwatch, If we're in ep2_outland_10, do half damage to Combine
  346. // Be advised, this will also happen in 10a, but this is not a problem.
  347. g_HackOutland10DamageHack = false;
  348. if( !Q_strnicmp( STRING(gpGlobals->mapname), "ep2_outland_10", 14) )
  349. {
  350. g_HackOutland10DamageHack = true;
  351. }
  352. }
  353. //-----------------------------------------------------------------------------
  354. // Purpose:
  355. //-----------------------------------------------------------------------------
  356. void CNPC_Alyx::StopLoopingSounds( void )
  357. {
  358. CSoundEnvelopeController::GetController().SoundDestroy( m_sndDarknessBreathing );
  359. m_sndDarknessBreathing = NULL;
  360. BaseClass::StopLoopingSounds();
  361. }
  362. //-----------------------------------------------------------------------------
  363. // Purpose:
  364. //-----------------------------------------------------------------------------
  365. void CNPC_Alyx::SelectModel()
  366. {
  367. // Alyx is allowed to use multiple models, because she appears in the pod.
  368. // She defaults to her normal model.
  369. const char *szModel = STRING( GetModelName() );
  370. if (!szModel || !*szModel)
  371. {
  372. SetModelName( AllocPooledString("models/alyx.mdl") );
  373. }
  374. }
  375. //-----------------------------------------------------------------------------
  376. // Purpose:
  377. //-----------------------------------------------------------------------------
  378. void CNPC_Alyx::SetupAlyxWithoutParent( void )
  379. {
  380. SetSolid( SOLID_BBOX );
  381. AddSolidFlags( FSOLID_NOT_STANDABLE );
  382. SetMoveType( MOVETYPE_STEP );
  383. CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_DOORS_GROUP | bits_CAP_TURN_HEAD | bits_CAP_DUCK | bits_CAP_SQUAD );
  384. CapabilitiesAdd( bits_CAP_USE_WEAPONS );
  385. CapabilitiesAdd( bits_CAP_ANIMATEDFACE );
  386. CapabilitiesAdd( bits_CAP_FRIENDLY_DMG_IMMUNE );
  387. CapabilitiesAdd( bits_CAP_AIM_GUN );
  388. CapabilitiesAdd( bits_CAP_MOVE_SHOOT );
  389. CapabilitiesAdd( bits_CAP_USE_SHOT_REGULATOR );
  390. }
  391. //-----------------------------------------------------------------------------
  392. // Purpose: Create and initialized Alyx's EMP tool
  393. //-----------------------------------------------------------------------------
  394. void CNPC_Alyx::CreateEmpTool( void )
  395. {
  396. if (!m_bShouldHaveEMP || m_hEmpTool)
  397. return;
  398. m_hEmpTool = (CBaseAnimating*)CreateEntityByName( "prop_dynamic" );
  399. if ( m_hEmpTool )
  400. {
  401. m_hEmpTool->SetModel( "models/alyx_emptool_prop.mdl" );
  402. m_hEmpTool->SetName( AllocPooledString("Alyx_Emptool") );
  403. int iAttachment = LookupAttachment( "Emp_Holster" );
  404. m_hEmpTool->SetParent(this, iAttachment);
  405. m_hEmpTool->SetOwnerEntity(this);
  406. m_hEmpTool->SetSolid( SOLID_NONE );
  407. m_hEmpTool->SetLocalOrigin( Vector( 0, 0, 0 ) );
  408. m_hEmpTool->SetLocalAngles( QAngle( 0, 0, 0 ) );
  409. m_hEmpTool->AddSpawnFlags(SF_DYNAMICPROP_NO_VPHYSICS);
  410. m_hEmpTool->AddEffects( EF_PARENT_ANIMATES );
  411. m_hEmpTool->Spawn();
  412. }
  413. }
  414. //-----------------------------------------------------------------------------
  415. // Purpose: Map input to create or destroy alyx's EMP tool
  416. //-----------------------------------------------------------------------------
  417. void CNPC_Alyx::InputGiveEMP( inputdata_t &inputdata )
  418. {
  419. m_bShouldHaveEMP = inputdata.value.Bool();
  420. if (m_bShouldHaveEMP)
  421. {
  422. if (!m_hEmpTool)
  423. {
  424. CreateEmpTool( );
  425. }
  426. }
  427. else
  428. {
  429. if (m_hEmpTool)
  430. {
  431. UTIL_Remove( m_hEmpTool );
  432. }
  433. }
  434. }
  435. //-----------------------------------------------------------------------------
  436. // Purpose:
  437. //-----------------------------------------------------------------------------
  438. struct ReadinessTransition_t
  439. {
  440. int iPreviousLevel;
  441. int iCurrentLevel;
  442. Activity requiredActivity;
  443. Activity transitionActivity;
  444. };
  445. void CNPC_Alyx::ReadinessLevelChanged( int iPriorLevel )
  446. {
  447. BaseClass::ReadinessLevelChanged( iPriorLevel );
  448. // When we drop from agitated to stimulated, stand up if we were crouching.
  449. if ( iPriorLevel == AIRL_AGITATED && GetReadinessLevel() == AIRL_STIMULATED )
  450. {
  451. //Warning("CROUCH: Standing, dropping back to stimulated.\n" );
  452. Stand();
  453. }
  454. if ( GetActiveWeapon() == NULL )
  455. return;
  456. //If Alyx is going from Relaxed to Agitated or Stimulated, let her raise her weapon before she's able to fire.
  457. if ( iPriorLevel == AIRL_RELAXED && GetReadinessLevel() > iPriorLevel )
  458. {
  459. GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + 0.5 );
  460. }
  461. // FIXME: Are there certain animations that we DO want to interrupt?
  462. if ( HasActiveLayer() )
  463. return;
  464. if ( npc_alyx_readiness_transitions.GetBool() )
  465. {
  466. // We don't have crouching readiness transitions yet
  467. if ( IsCrouching() )
  468. return;
  469. static ReadinessTransition_t readinessTransitions[] =
  470. {
  471. //Previous Readiness level - Current Readiness Level - Activity NPC needs to be playing - Gesture to play
  472. { AIRL_RELAXED, AIRL_STIMULATED, ACT_IDLE, ACT_READINESS_RELAXED_TO_STIMULATED, },
  473. { AIRL_RELAXED, AIRL_STIMULATED, ACT_WALK, ACT_READINESS_RELAXED_TO_STIMULATED_WALK, },
  474. { AIRL_AGITATED, AIRL_STIMULATED, ACT_IDLE, ACT_READINESS_AGITATED_TO_STIMULATED, },
  475. { AIRL_STIMULATED, AIRL_RELAXED, ACT_IDLE, ACT_READINESS_STIMULATED_TO_RELAXED, }
  476. };
  477. for ( int i = 0; i < ARRAYSIZE( readinessTransitions ); i++ )
  478. {
  479. if ( GetIdealActivity() != readinessTransitions[i].requiredActivity )
  480. continue;
  481. Activity translatedTransitionActivity = Weapon_TranslateActivity( readinessTransitions[i].transitionActivity );
  482. if ( translatedTransitionActivity == ACT_INVALID || translatedTransitionActivity == readinessTransitions[i].transitionActivity )
  483. continue;
  484. Activity finalActivity = TranslateActivityReadiness( translatedTransitionActivity );
  485. if ( iPriorLevel == readinessTransitions[i].iPreviousLevel && GetReadinessLevel() == readinessTransitions[i].iCurrentLevel )
  486. {
  487. RestartGesture( finalActivity );
  488. break;
  489. }
  490. }
  491. }
  492. }
  493. //-----------------------------------------------------------------------------
  494. // Purpose:
  495. //-----------------------------------------------------------------------------
  496. void CNPC_Alyx::PrescheduleThink( void )
  497. {
  498. BaseClass::PrescheduleThink();
  499. // Figure out if Alyx has just been removed from her parent
  500. if ( GetMoveType() == MOVETYPE_NONE && !GetMoveParent() )
  501. {
  502. // Don't confuse the passenger behavior with just removing Alyx's parent!
  503. if ( m_PassengerBehavior.IsEnabled() == false )
  504. {
  505. SetupAlyxWithoutParent();
  506. SetupVPhysicsHull();
  507. }
  508. }
  509. // If Alyx is in combat, and she doesn't have her gun out, fetch it
  510. if ( GetState() == NPC_STATE_COMBAT && IsWeaponHolstered() && !m_FuncTankBehavior.IsRunning() )
  511. {
  512. SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED );
  513. }
  514. // If we're in stealth mode, and we can still see the stealth node, keep using it
  515. if ( GetReadinessLevel() == AIRL_STEALTH )
  516. {
  517. if ( m_hStealthLookTarget && !m_hStealthLookTarget->IsDisabled() )
  518. {
  519. if ( m_hStealthLookTarget->IsInNodeFOV(this) && FVisible( m_hStealthLookTarget ) )
  520. return;
  521. }
  522. // Break out of stealth mode
  523. SetReadinessLevel( AIRL_STIMULATED, true, true );
  524. ClearLookTarget( m_hStealthLookTarget );
  525. m_hStealthLookTarget = NULL;
  526. }
  527. // If we're being blinded by the flashlight, see if we should stop
  528. if ( m_bIsFlashlightBlind )
  529. {
  530. // we used to have a bug where if we tried to remove alyx from the blind scene before it got loaded asynchronously,
  531. // she would get stuck in the animation with m_bIsFlashlightBlind set to false. that should be fixed, but just to
  532. // be sure, we wait a bit to prevent this from happening.
  533. if ( m_fStayBlindUntil < gpGlobals->curtime )
  534. {
  535. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  536. if ( pPlayer && (!CanBeBlindedByFlashlight( true ) || !pPlayer->IsIlluminatedByFlashlight(this, NULL ) || !PlayerFlashlightOnMyEyes( pPlayer )) &&
  537. !BlindedByFlare() )
  538. {
  539. // Remove the actor from the flashlight scene
  540. ADD_DEBUG_HISTORY( HISTORY_ALYX_BLIND, UTIL_VarArgs( "(%0.2f) Alyx: end blind scene '%s'\n", gpGlobals->curtime, STRING(m_iszCurrentBlindScene) ) );
  541. RemoveActorFromScriptedScenes( this, true, false, STRING(m_iszCurrentBlindScene) );
  542. // Allow firing again, but prevent myself from firing until I'm done
  543. GetShotRegulator()->EnableShooting();
  544. GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + 1.0 );
  545. m_bIsFlashlightBlind = false;
  546. m_flDontBlindUntil = gpGlobals->curtime + RandomFloat( 1, 3 );
  547. }
  548. }
  549. }
  550. else
  551. {
  552. CheckBlindedByFlare();
  553. }
  554. }
  555. //-----------------------------------------------------------------------------
  556. // Periodically look for opportunities to interact with objects in the world.
  557. // Right now Alyx only interacts with things the player picks up with
  558. // physcannon.
  559. //-----------------------------------------------------------------------------
  560. #define ALYX_INTERACT_SEARCH_FREQUENCY 1.0f // seconds
  561. void CNPC_Alyx::SearchForInteractTargets()
  562. {
  563. if( m_fTimeNextSearchForInteractTargets > gpGlobals->curtime )
  564. {
  565. return;
  566. }
  567. m_fTimeNextSearchForInteractTargets = gpGlobals->curtime + ALYX_INTERACT_SEARCH_FREQUENCY;
  568. // Ensure player can be seen.
  569. if( !HasCondition( COND_SEE_PLAYER) )
  570. {
  571. //Msg("ALYX Can't interact: can't see player\n");
  572. return;
  573. }
  574. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  575. if( !pPlayer )
  576. {
  577. return;
  578. }
  579. CBaseEntity *pProspect = PhysCannonGetHeldEntity(pPlayer->GetActiveWeapon());
  580. if( !pProspect )
  581. {
  582. //Msg("ALYX Can't interact: player not holding anything\n");
  583. return;
  584. }
  585. if( !IsValidInteractTarget(pProspect) )
  586. {
  587. //Msg("ALYX Can't interact: player holding an invalid object\n");
  588. return;
  589. }
  590. SetInteractTarget(pProspect);
  591. }
  592. //-----------------------------------------------------------------------------
  593. //-----------------------------------------------------------------------------
  594. void CNPC_Alyx::GatherConditions()
  595. {
  596. BaseClass::GatherConditions();
  597. if( HasCondition( COND_HEAR_DANGER ) )
  598. {
  599. // Don't let Alyx worry about combat sounds if she's panicking
  600. // from danger sounds. This prevents her from running ALERT_FACE_BEST_SOUND
  601. // as soon as a grenade explodes (which makes a loud combat sound). If Alyx
  602. // is NOT panicking over a Danger sound, she'll hear the combat sounds as normal.
  603. ClearCondition( COND_HEAR_COMBAT );
  604. }
  605. // Update flashlight state
  606. ClearCondition( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED );
  607. ClearCondition( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT );
  608. ClearCondition( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT );
  609. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  610. if ( pPlayer )
  611. {
  612. bool bFlashlightState = pPlayer->FlashlightIsOn() != 0;
  613. if ( bFlashlightState != m_bPlayerFlashlightState )
  614. {
  615. if ( bFlashlightState )
  616. {
  617. SetCondition( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT );
  618. }
  619. else
  620. {
  621. // If the power level is low, consider it expired, due
  622. // to it running out or the player turning it off in anticipation.
  623. CHL2_Player *pHLPlayer = assert_cast<CHL2_Player*>( pPlayer );
  624. if ( pHLPlayer->SuitPower_GetCurrentPercentage() < 15 )
  625. {
  626. SetCondition( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED );
  627. }
  628. else
  629. {
  630. SetCondition( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT );
  631. }
  632. }
  633. m_bPlayerFlashlightState = bFlashlightState;
  634. }
  635. }
  636. if ( m_NPCState == NPC_STATE_COMBAT )
  637. {
  638. DoCustomCombatAI();
  639. }
  640. if( HasInteractTarget() )
  641. {
  642. // Check that any current interact target is still valid.
  643. if( !IsValidInteractTarget(GetInteractTarget()) )
  644. {
  645. SetInteractTarget(NULL);
  646. }
  647. }
  648. // This is not an else...if because the code above could have started
  649. // with an interact target and ended without one.
  650. if( !HasInteractTarget() )
  651. {
  652. SearchForInteractTargets();
  653. }
  654. // Set up our interact conditions.
  655. if( HasInteractTarget() )
  656. {
  657. if( CanInteractWithTarget(GetInteractTarget()) )
  658. {
  659. SetCondition(COND_ALYX_CAN_INTERACT_WITH_TARGET);
  660. ClearCondition(COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET);
  661. }
  662. else
  663. {
  664. SetCondition(COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET);
  665. ClearCondition(COND_ALYX_CAN_INTERACT_WITH_TARGET);
  666. }
  667. SetCondition( COND_ALYX_HAS_INTERACT_TARGET );
  668. ClearCondition( COND_ALYX_NO_INTERACT_TARGET );
  669. }
  670. else
  671. {
  672. SetCondition( COND_ALYX_NO_INTERACT_TARGET );
  673. ClearCondition( COND_ALYX_HAS_INTERACT_TARGET );
  674. }
  675. // Check for explosions!
  676. if( HasCondition(COND_HEAR_COMBAT) )
  677. {
  678. CSound *pSound = GetBestSound();
  679. if ( IsInAVehicle() == false ) // For now, don't do these animations while in the vehicle
  680. {
  681. if( (pSound->SoundTypeNoContext() & SOUND_COMBAT) && (pSound->SoundContext() & SOUND_CONTEXT_EXPLOSION) )
  682. {
  683. if ( HasShotgun() )
  684. {
  685. if ( !IsPlayingGesture(ACT_GESTURE_FLINCH_BLAST_SHOTGUN) && !IsPlayingGesture(ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN) )
  686. {
  687. RestartGesture( ACT_GESTURE_FLINCH_BLAST_SHOTGUN );
  688. GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + SequenceDuration( ACT_GESTURE_FLINCH_BLAST_SHOTGUN ) + 0.5f ); // Allow another second for Alyx to bring her weapon to bear after the flinch.
  689. }
  690. }
  691. else
  692. {
  693. if ( !IsPlayingGesture(ACT_GESTURE_FLINCH_BLAST) && !IsPlayingGesture(ACT_GESTURE_FLINCH_BLAST_DAMAGED) )
  694. {
  695. RestartGesture( ACT_GESTURE_FLINCH_BLAST );
  696. GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + SequenceDuration( ACT_GESTURE_FLINCH_BLAST ) + 0.5f ); // Allow another second for Alyx to bring her weapon to bear after the flinch.
  697. }
  698. }
  699. }
  700. }
  701. }
  702. // ROBIN: This was here to solve a problem in a playtest. We've since found what we think was the cause.
  703. // It's a useful piece of debug to have lying there, so I've left it in.
  704. if ( (GetFlags() & FL_FLY) && m_NPCState != NPC_STATE_SCRIPT && !m_ActBusyBehavior.IsActive() && !m_PassengerBehavior.IsEnabled() )
  705. {
  706. Warning( "Removed FL_FLY from Alyx, who wasn't running a script or actbusy. Time %.2f, map %s.\n", gpGlobals->curtime, STRING(gpGlobals->mapname) );
  707. RemoveFlag( FL_FLY );
  708. }
  709. }
  710. //-----------------------------------------------------------------------------
  711. //-----------------------------------------------------------------------------
  712. bool CNPC_Alyx::ShouldPlayerAvoid( void )
  713. {
  714. if( IsCurSchedule(SCHED_ALYX_NEW_WEAPON, false) )
  715. return true;
  716. #if 1
  717. if( IsCurSchedule( SCHED_PC_GET_OFF_COMPANION, false) )
  718. {
  719. CBaseEntity *pGroundEnt = GetGroundEntity();
  720. if( pGroundEnt != NULL && pGroundEnt->IsPlayer() )
  721. {
  722. if( GetAbsOrigin().z < pGroundEnt->EyePosition().z )
  723. return true;
  724. }
  725. }
  726. #endif
  727. return BaseClass::ShouldPlayerAvoid();
  728. }
  729. //-----------------------------------------------------------------------------
  730. // Just heard a gunfire sound. Try to figure out how much we should know
  731. // about it.
  732. //-----------------------------------------------------------------------------
  733. void CNPC_Alyx::AnalyzeGunfireSound( CSound *pSound )
  734. {
  735. Assert( pSound != NULL );
  736. if( GetState() != NPC_STATE_ALERT && GetState() != NPC_STATE_IDLE )
  737. {
  738. // Only have code for IDLE and ALERT now.
  739. return;
  740. }
  741. // Have to verify a bunch of stuff about the sound. It must have a valid BaseCombatCharacter as the owner,
  742. // must have a valid target, and we need a valid pointer to the player.
  743. if( pSound->m_hOwner.Get() == NULL )
  744. return;
  745. if( pSound->m_hTarget.Get() == NULL )
  746. return;
  747. CBaseCombatCharacter *pSoundOriginBCC = pSound->m_hOwner->MyCombatCharacterPointer();
  748. if( pSoundOriginBCC == NULL )
  749. return;
  750. CBaseEntity *pSoundTarget = pSound->m_hTarget.Get();
  751. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  752. Assert( pPlayer != NULL );
  753. if( pSoundTarget == this )
  754. {
  755. // The shooter is firing at me. Assume if Alyx can hear the gunfire, she can deduce its origin.
  756. UpdateEnemyMemory( pSoundOriginBCC, pSoundOriginBCC->GetAbsOrigin(), this );
  757. }
  758. else if( pSoundTarget == pPlayer )
  759. {
  760. // The shooter is firing at the player. Assume Alyx can deduce the origin if the player COULD see the origin, and Alyx COULD see the player.
  761. if( pPlayer->FVisible(pSoundOriginBCC) && FVisible(pPlayer) )
  762. {
  763. UpdateEnemyMemory( pSoundOriginBCC, pSoundOriginBCC->GetAbsOrigin(), this );
  764. }
  765. }
  766. }
  767. //-----------------------------------------------------------------------------
  768. //-----------------------------------------------------------------------------
  769. bool CNPC_Alyx::IsValidEnemy( CBaseEntity *pEnemy )
  770. {
  771. if ( HL2GameRules()->IsAlyxInDarknessMode() )
  772. {
  773. if ( !CanSeeEntityInDarkness( pEnemy ) )
  774. return false;
  775. }
  776. // Alyx can only take a stalker as her enemy which is angry at the player or her.
  777. if ( pEnemy->Classify() == CLASS_STALKER )
  778. {
  779. if( !pEnemy->GetEnemy() )
  780. {
  781. return false;
  782. }
  783. if( pEnemy->GetEnemy() != this && !pEnemy->GetEnemy()->IsPlayer() )
  784. {
  785. return false;
  786. }
  787. }
  788. if ( m_AssaultBehavior.IsRunning() && IsTurret( pEnemy ) )
  789. {
  790. CBaseCombatCharacter *pBCC = dynamic_cast<CBaseCombatCharacter*>(pEnemy);
  791. if ( pBCC != NULL && !pBCC->FInViewCone(this) )
  792. {
  793. // Don't let turrets that can't shoot me distract me from my assault behavior.
  794. // This fixes a very specific problem that appeared in Episode 2 map ep2_outland_09
  795. // Where Alyx wouldn't terminate an assault while standing on an assault point because
  796. // she was afraid of a turret that was visible from the assault point, but facing the
  797. // other direction and thus not a threat.
  798. return false;
  799. }
  800. }
  801. return BaseClass::IsValidEnemy(pEnemy);
  802. }
  803. //-----------------------------------------------------------------------------
  804. // Purpose:
  805. //-----------------------------------------------------------------------------
  806. void CNPC_Alyx::Event_Killed( const CTakeDamageInfo &info )
  807. {
  808. // Destroy our EMP tool since it won't follow us onto the ragdoll anyway
  809. if ( m_hEmpTool != NULL )
  810. {
  811. UTIL_Remove( m_hEmpTool );
  812. }
  813. BaseClass::Event_Killed( info );
  814. }
  815. //-----------------------------------------------------------------------------
  816. //-----------------------------------------------------------------------------
  817. void CNPC_Alyx::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info )
  818. {
  819. // comment on killing npc's
  820. if ( pVictim->IsNPC() )
  821. {
  822. SpeakIfAllowed( TLK_ALYX_ENEMY_DEAD );
  823. }
  824. // Alyx builds a proxy for the dead enemy so she has something to shoot at for a short time after
  825. // the enemy ragdolls.
  826. if( !(pVictim->GetFlags() & FL_ONGROUND) || pVictim->GetMoveType() != MOVETYPE_STEP )
  827. {
  828. // Don't fire up in the air, since the dead enemy will have fallen.
  829. return;
  830. }
  831. if( pVictim->GetAbsOrigin().DistTo(GetAbsOrigin()) < 96.0f )
  832. {
  833. // Don't shoot at an enemy corpse that dies very near to me. This will prevent Alyx attacking
  834. // Other nearby enemies.
  835. return;
  836. }
  837. if( !HasShotgun() )
  838. {
  839. CAI_BaseNPC *pTarget = CreateCustomTarget( pVictim->GetAbsOrigin(), 2.0f );
  840. AddEntityRelationship( pTarget, IRelationType(pVictim), IRelationPriority(pVictim) );
  841. // Update or Create a memory entry for this target and make Alyx think she's seen this target recently.
  842. // This prevents the baseclass from not recognizing this target and forcing Alyx into
  843. // SCHED_WAKE_ANGRY, which wastes time and causes her to change animation sequences rapidly.
  844. GetEnemies()->UpdateMemory( GetNavigator()->GetNetwork(), pTarget, pTarget->GetAbsOrigin(), 0.0f, true );
  845. AI_EnemyInfo_t *pMemory = GetEnemies()->Find( pTarget );
  846. if( pMemory )
  847. {
  848. // Pretend we've known about this target longer than we really have.
  849. pMemory->timeFirstSeen = gpGlobals->curtime - 10.0f;
  850. }
  851. }
  852. }
  853. //-----------------------------------------------------------------------------
  854. // Purpose: Called by enemy NPC's when they are ignited
  855. // Input : pVictim - entity that was ignited
  856. //-----------------------------------------------------------------------------
  857. void CNPC_Alyx::EnemyIgnited( CAI_BaseNPC *pVictim )
  858. {
  859. if ( FVisible( pVictim ) )
  860. {
  861. SpeakIfAllowed( TLK_ENEMY_BURNING );
  862. }
  863. }
  864. //-----------------------------------------------------------------------------
  865. // Purpose: Called by combine balls when they're socketed
  866. // Input : pVictim - entity killed by player
  867. //-----------------------------------------------------------------------------
  868. void CNPC_Alyx::CombineBallSocketed( int iNumBounces )
  869. {
  870. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  871. if ( !pPlayer || !FVisible(pPlayer) )
  872. {
  873. return;
  874. }
  875. // set up the speech modifiers
  876. CFmtStrN<128> modifiers( "num_bounces:%d", iNumBounces );
  877. // fire off a ball socketed concept
  878. SpeakIfAllowed( TLK_BALLSOCKETED, modifiers );
  879. }
  880. //-----------------------------------------------------------------------------
  881. // Purpose: If we're a passenger in a vehicle
  882. //-----------------------------------------------------------------------------
  883. bool CNPC_Alyx::RunningPassengerBehavior( void )
  884. {
  885. // Must be active and not outside the vehicle
  886. if ( m_PassengerBehavior.IsRunning() && m_PassengerBehavior.GetPassengerState() != PASSENGER_STATE_OUTSIDE )
  887. return true;
  888. return false;
  889. }
  890. //-----------------------------------------------------------------------------
  891. // Purpose: Handle "mobbed" combat condition when Alyx is overwhelmed by force
  892. //-----------------------------------------------------------------------------
  893. void CNPC_Alyx::DoMobbedCombatAI( void )
  894. {
  895. AIEnemiesIter_t iter;
  896. float visibleEnemiesScore = 0.0f;
  897. float closeEnemiesScore = 0.0f;
  898. for ( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  899. {
  900. if ( IRelationType( pEMemory->hEnemy ) != D_NU && IRelationType( pEMemory->hEnemy ) != D_LI && pEMemory->hEnemy->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= ALYX_MIN_CONSIDER_DIST )
  901. {
  902. if( pEMemory->hEnemy && pEMemory->hEnemy->IsAlive() && gpGlobals->curtime - pEMemory->timeLastSeen <= 0.5f && pEMemory->hEnemy->Classify() != CLASS_BULLSEYE )
  903. {
  904. if( pEMemory->hEnemy->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= ALYX_MIN_MOB_DIST_SQR )
  905. {
  906. closeEnemiesScore += 1.0f;
  907. }
  908. else
  909. {
  910. visibleEnemiesScore += 1.0f;
  911. }
  912. }
  913. }
  914. }
  915. if( closeEnemiesScore > 2 )
  916. {
  917. SetCondition( COND_MOBBED_BY_ENEMIES );
  918. // mark anyone in the mob as having mobbed me
  919. for ( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  920. {
  921. if ( pEMemory->bMobbedMe )
  922. continue;
  923. if ( IRelationType( pEMemory->hEnemy ) != D_NU && IRelationType( pEMemory->hEnemy ) != D_LI && pEMemory->hEnemy->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= ALYX_MIN_CONSIDER_DIST )
  924. {
  925. if( pEMemory->hEnemy && pEMemory->hEnemy->IsAlive() && gpGlobals->curtime - pEMemory->timeLastSeen <= 0.5f && pEMemory->hEnemy->Classify() != CLASS_BULLSEYE )
  926. {
  927. if( pEMemory->hEnemy->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= ALYX_MIN_MOB_DIST_SQR )
  928. {
  929. pEMemory->bMobbedMe = true;
  930. }
  931. }
  932. }
  933. }
  934. }
  935. else
  936. {
  937. ClearCondition( COND_MOBBED_BY_ENEMIES );
  938. }
  939. // Alyx's gun can never run out of ammo. Allow Alyx to ignore LOW AMMO warnings
  940. // if she's in a close quarters fight with several enemies. She'll attempt to reload
  941. // as soon as her combat situation is less pressing.
  942. if( HasCondition( COND_MOBBED_BY_ENEMIES ) )
  943. {
  944. ClearCondition( COND_LOW_PRIMARY_AMMO );
  945. }
  946. // Say a combat thing
  947. if( HasCondition( COND_MOBBED_BY_ENEMIES ) )
  948. {
  949. SpeakIfAllowed( TLK_MOBBED );
  950. }
  951. else if( visibleEnemiesScore > 4 )
  952. {
  953. SpeakIfAllowed( TLK_MANY_ENEMIES );
  954. }
  955. }
  956. //-----------------------------------------------------------------------------
  957. // Purpose: Custom AI for Alyx while in combat
  958. //-----------------------------------------------------------------------------
  959. void CNPC_Alyx::DoCustomCombatAI( void )
  960. {
  961. // Only run the following code if we're not in a vehicle
  962. if ( RunningPassengerBehavior() == false )
  963. {
  964. // Do our mobbed by enemies logic
  965. DoMobbedCombatAI();
  966. }
  967. CBaseEntity *pEnemy = GetEnemy();
  968. if( HasCondition( COND_LOW_PRIMARY_AMMO ) )
  969. {
  970. if( pEnemy )
  971. {
  972. if( GetAbsOrigin().DistToSqr( pEnemy->GetAbsOrigin() ) < Square( 60.0f ) )
  973. {
  974. // Don't reload if an enemy is right in my face.
  975. ClearCondition( COND_LOW_PRIMARY_AMMO );
  976. }
  977. }
  978. }
  979. if ( HasCondition( COND_LIGHT_DAMAGE ) )
  980. {
  981. if ( pEnemy && !IsCrouching() )
  982. {
  983. // If my enemy is shooting at me from a distance, crouch for protection
  984. if ( EnemyDistance( pEnemy ) > ALYX_MIN_ENEMY_DIST_TO_CROUCH )
  985. {
  986. DesireCrouch();
  987. }
  988. }
  989. }
  990. }
  991. //-----------------------------------------------------------------------------
  992. // Purpose:
  993. //-----------------------------------------------------------------------------
  994. void CNPC_Alyx::DoCustomSpeechAI( void )
  995. {
  996. BaseClass::DoCustomSpeechAI();
  997. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  998. if ( HasCondition(COND_NEW_ENEMY) && GetEnemy() )
  999. {
  1000. if ( GetEnemy()->Classify() == CLASS_HEADCRAB )
  1001. {
  1002. CBaseHeadcrab *pHC = assert_cast<CBaseHeadcrab*>(GetEnemy());
  1003. // If we see a headcrab for the first time as he's jumping at me, freak out!
  1004. if ( ( GetEnemy()->GetEnemy() == this ) && pHC->IsJumping() && gpGlobals->curtime - GetEnemies()->FirstTimeSeen(GetEnemy()) < 0.5 )
  1005. {
  1006. SpeakIfAllowed( "TLK_SPOTTED_INCOMING_HEADCRAB" );
  1007. }
  1008. // If we see a headcrab leaving a zombie that just died, mention it
  1009. else if ( pHC->GetOwnerEntity() && ( pHC->GetOwnerEntity()->Classify() == CLASS_ZOMBIE ) && !pHC->GetOwnerEntity()->IsAlive() )
  1010. {
  1011. SpeakIfAllowed( "TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE" );
  1012. }
  1013. }
  1014. else if ( GetEnemy()->Classify() == CLASS_ZOMBIE )
  1015. {
  1016. CNPC_BaseZombie *pZombie = assert_cast<CNPC_BaseZombie*>(GetEnemy());
  1017. // If we see a zombie getting up, mention it
  1018. if ( pZombie->IsGettingUp() )
  1019. {
  1020. SpeakIfAllowed( "TLK_SPOTTED_ZOMBIE_WAKEUP" );
  1021. }
  1022. }
  1023. }
  1024. // Darkness mode speech
  1025. ClearCondition( COND_ALYX_IN_DARK );
  1026. if ( HL2GameRules()->IsAlyxInDarknessMode() )
  1027. {
  1028. // Even though the darkness light system will take flares into account when Alyx
  1029. // says she's lost the player in the darkness, players still think she's silly
  1030. // when they're too far from the flare to be seen.
  1031. // So, check for lit flares or other dynamic lights, and don't do
  1032. // a bunch of the darkness speech if there's a lit flare nearby.
  1033. bool bNearbyFlare = DarknessLightSourceWithinRadius( this, 500 );
  1034. if ( !bNearbyFlare )
  1035. {
  1036. SetCondition( COND_ALYX_IN_DARK );
  1037. if ( HasCondition( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) || HasCondition( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) )
  1038. {
  1039. // Player just turned off the flashlight. Start ramping up Alyx's breathing.
  1040. if ( !m_sndDarknessBreathing )
  1041. {
  1042. CPASAttenuationFilter filter( this );
  1043. m_sndDarknessBreathing = CSoundEnvelopeController::GetController().SoundCreate( filter, entindex(), CHAN_STATIC,
  1044. "ep_01.al_dark_breathing01", SNDLVL_TALKING );
  1045. CSoundEnvelopeController::GetController().Play( m_sndDarknessBreathing, 0.0f, PITCH_NORM );
  1046. }
  1047. if ( m_sndDarknessBreathing )
  1048. {
  1049. CSoundEnvelopeController::GetController().SoundChangeVolume( m_sndDarknessBreathing, ALYX_BREATHING_VOLUME_MAX, RandomFloat(10,20) );
  1050. m_SpeechWatch_BreathingRamp.Stop();
  1051. }
  1052. }
  1053. }
  1054. // If we lose an enemy due to the flashlight, comment about it
  1055. if ( !HasCondition( COND_SEE_ENEMY ) && m_bHadCondSeeEnemy && !HasCondition( COND_TALKER_PLAYER_DEAD ) )
  1056. {
  1057. if ( m_bDarknessSpeechAllowed && HasCondition( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) &&
  1058. GetEnemy() && ( GetEnemy()->Classify() != CLASS_BULLSEYE ) )
  1059. {
  1060. SpeakIfAllowed( "TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT" );
  1061. }
  1062. else if ( m_bDarknessSpeechAllowed && HasCondition( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) &&
  1063. GetEnemy() && ( GetEnemy()->Classify() != CLASS_BULLSEYE ) )
  1064. {
  1065. SpeakIfAllowed( "TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT_EXPIRED" );
  1066. }
  1067. else if ( m_bDarknessSpeechAllowed && GetEnemy() && ( GetEnemy()->Classify() != CLASS_BULLSEYE ) &&
  1068. pPlayer && pPlayer->FlashlightIsOn() && !pPlayer->IsIlluminatedByFlashlight(GetEnemy(), NULL ) &&
  1069. FVisible( GetEnemy() ) )
  1070. {
  1071. SpeakIfAllowed( TLK_DARKNESS_ENEMY_IN_DARKNESS );
  1072. }
  1073. m_bHadCondSeeEnemy = false;
  1074. }
  1075. else if ( HasCondition( COND_SEE_ENEMY ) )
  1076. {
  1077. m_bHadCondSeeEnemy = true;
  1078. }
  1079. else if ( ( !GetEnemy() || ( GetEnemy()->Classify() == CLASS_BULLSEYE ) ) && m_bDarknessSpeechAllowed )
  1080. {
  1081. if ( HasCondition( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) )
  1082. {
  1083. SpeakIfAllowed( TLK_DARKNESS_FLASHLIGHT_EXPIRED );
  1084. }
  1085. else if ( HasCondition( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) )
  1086. {
  1087. SpeakIfAllowed( TLK_FLASHLIGHT_OFF );
  1088. }
  1089. else if ( HasCondition( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT ) )
  1090. {
  1091. SpeakIfAllowed( TLK_FLASHLIGHT_ON );
  1092. }
  1093. }
  1094. // If we've just seen a new enemy, and it's illuminated by the flashlight,
  1095. // tell the player to keep the flashlight on 'em.
  1096. if ( HasCondition(COND_NEW_ENEMY) && !HasCondition( COND_TALKER_PLAYER_DEAD ) )
  1097. {
  1098. // First time we've seen this guy?
  1099. if ( gpGlobals->curtime - GetEnemies()->FirstTimeSeen(GetEnemy()) < 0.5 )
  1100. {
  1101. if ( pPlayer && pPlayer->IsIlluminatedByFlashlight(GetEnemy(), NULL ) && m_bDarknessSpeechAllowed &&
  1102. !LookerCouldSeeTargetInDarkness( this, GetEnemy() ) )
  1103. {
  1104. SpeakIfAllowed( "TLK_DARKNESS_FOUNDENEMY_BY_FLASHLIGHT" );
  1105. }
  1106. }
  1107. }
  1108. // When we lose the player, start lost-player talker after some time
  1109. if ( !bNearbyFlare && m_bDarknessSpeechAllowed )
  1110. {
  1111. if ( !HasCondition(COND_SEE_PLAYER) && !m_SpeechWatch_LostPlayer.IsRunning() )
  1112. {
  1113. m_SpeechWatch_LostPlayer.Set( 5,8 );
  1114. m_SpeechWatch_LostPlayer.Start();
  1115. m_MoveMonitor.SetMark( AI_GetSinglePlayer(), 48 );
  1116. }
  1117. else if ( m_SpeechWatch_LostPlayer.Expired() )
  1118. {
  1119. // Can't see the player?
  1120. if ( !HasCondition(COND_SEE_PLAYER) && !HasCondition( COND_TALKER_PLAYER_DEAD ) && !HasCondition( COND_SEE_ENEMY ) &&
  1121. ( !pPlayer || pPlayer->GetAbsOrigin().DistToSqr(GetAbsOrigin()) > ALYX_DARKNESS_LOST_PLAYER_DIST ) )
  1122. {
  1123. // only speak if player hasn't moved.
  1124. if ( m_MoveMonitor.TargetMoved( AI_GetSinglePlayer() ) )
  1125. {
  1126. SpeakIfAllowed( "TLK_DARKNESS_LOSTPLAYER" );
  1127. m_SpeechWatch_LostPlayer.Set(10);
  1128. m_SpeechWatch_LostPlayer.Start();
  1129. m_bSpokeLostPlayerInDarkness = true;
  1130. }
  1131. }
  1132. }
  1133. // Speech concepts that only occur when the player's flashlight is off
  1134. if ( pPlayer && !HasCondition( COND_TALKER_PLAYER_DEAD ) && !pPlayer->FlashlightIsOn() )
  1135. {
  1136. // When the player first turns off the light, don't talk about sounds for a bit
  1137. if ( HasCondition( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) || HasCondition( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) )
  1138. {
  1139. m_SpeechTimer_HeardSound.Set(4);
  1140. }
  1141. else if ( m_SpeechWatch_SoundDelay.Expired() )
  1142. {
  1143. // We've waited for a bit after the sound, now talk about it
  1144. SpeakIfAllowed( "TLK_DARKNESS_HEARDSOUND" );
  1145. m_SpeechWatch_SoundDelay.Stop();
  1146. }
  1147. else if ( HasCondition( COND_HEAR_SPOOKY ) )
  1148. {
  1149. // If we hear anything while the player's flashlight is off, randomly mention it
  1150. if ( m_SpeechTimer_HeardSound.Expired() )
  1151. {
  1152. m_SpeechTimer_HeardSound.Set(10);
  1153. // Wait for the sound to play for a bit before speaking about it
  1154. m_SpeechWatch_SoundDelay.Set( 1.0,3.0 );
  1155. m_SpeechWatch_SoundDelay.Start();
  1156. }
  1157. }
  1158. }
  1159. }
  1160. // Stop the heard sound response if the player turns the flashlight on
  1161. if ( bNearbyFlare || HasCondition( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT ) )
  1162. {
  1163. m_SpeechWatch_SoundDelay.Stop();
  1164. if ( m_sndDarknessBreathing )
  1165. {
  1166. CSoundEnvelopeController::GetController().SoundChangeVolume( m_sndDarknessBreathing, 0.0f, 0.5 );
  1167. m_SpeechWatch_BreathingRamp.Stop();
  1168. }
  1169. }
  1170. }
  1171. else
  1172. {
  1173. if ( m_sndDarknessBreathing )
  1174. {
  1175. CSoundEnvelopeController::GetController().SoundChangeVolume( m_sndDarknessBreathing, 0.0f, 0.5 );
  1176. m_SpeechWatch_BreathingRamp.Stop();
  1177. }
  1178. if ( !HasCondition(COND_SEE_PLAYER) && !m_SpeechWatch_FoundPlayer.IsRunning() )
  1179. {
  1180. // wait a minute before saying something when alyx sees him again
  1181. m_SpeechWatch_FoundPlayer.Set( 60, 75 );
  1182. m_SpeechWatch_FoundPlayer.Start();
  1183. }
  1184. else if ( HasCondition(COND_SEE_PLAYER) )
  1185. {
  1186. if ( m_SpeechWatch_FoundPlayer.Expired() && m_bDarknessSpeechAllowed )
  1187. {
  1188. SpeakIfAllowed( "TLK_FOUNDPLAYER" );
  1189. }
  1190. m_SpeechWatch_FoundPlayer.Stop();
  1191. }
  1192. }
  1193. // If we spoke lost-player, and now we see him/her, say so
  1194. if ( m_bSpokeLostPlayerInDarkness )
  1195. {
  1196. // If we've left darkness mode, or if the player has blinded me with
  1197. // the flashlight, don't bother speaking the found player line.
  1198. if ( !m_bIsFlashlightBlind && HL2GameRules()->IsAlyxInDarknessMode() && m_bDarknessSpeechAllowed )
  1199. {
  1200. if ( HasCondition(COND_SEE_PLAYER) && !HasCondition( COND_TALKER_PLAYER_DEAD ) )
  1201. {
  1202. if ( ( m_fTimeUntilNextDarknessFoundPlayer == AI_INVALID_TIME ) || ( gpGlobals->curtime < m_fTimeUntilNextDarknessFoundPlayer ) )
  1203. {
  1204. SpeakIfAllowed( "TLK_DARKNESS_FOUNDPLAYER" );
  1205. }
  1206. m_bSpokeLostPlayerInDarkness = false;
  1207. }
  1208. }
  1209. else
  1210. {
  1211. m_bSpokeLostPlayerInDarkness = false;
  1212. }
  1213. }
  1214. if ( ( !m_bDarknessSpeechAllowed || HasCondition(COND_SEE_PLAYER) ) && m_SpeechWatch_LostPlayer.IsRunning() )
  1215. {
  1216. m_SpeechWatch_LostPlayer.Stop();
  1217. m_MoveMonitor.ClearMark();
  1218. }
  1219. // Ramp the breathing back up after speaking
  1220. if ( m_SpeechWatch_BreathingRamp.IsRunning() )
  1221. {
  1222. if ( m_SpeechWatch_BreathingRamp.Expired() )
  1223. {
  1224. CSoundEnvelopeController::GetController().SoundChangeVolume( m_sndDarknessBreathing, ALYX_BREATHING_VOLUME_MAX, RandomFloat(5,10) );
  1225. m_SpeechWatch_BreathingRamp.Stop();
  1226. }
  1227. }
  1228. }
  1229. //-----------------------------------------------------------------------------
  1230. // Purpose:
  1231. //-----------------------------------------------------------------------------
  1232. bool CNPC_Alyx::SpeakIfAllowed( AIConcept_t concept, const char *modifiers /*= NULL*/, bool bRespondingToPlayer /*= false*/, char *pszOutResponseChosen /*= NULL*/, size_t bufsize /* = 0 */ )
  1233. {
  1234. if ( BaseClass::SpeakIfAllowed( concept, modifiers, bRespondingToPlayer, pszOutResponseChosen, bufsize ) )
  1235. {
  1236. // If we're breathing in the darkness, drop the volume quickly
  1237. if ( m_sndDarknessBreathing && CSoundEnvelopeController::GetController().SoundGetVolume( m_sndDarknessBreathing ) > 0.0 )
  1238. {
  1239. CSoundEnvelopeController::GetController().SoundChangeVolume( m_sndDarknessBreathing, 0.0f, 0.1 );
  1240. // Ramp up the sound again after the response is over
  1241. float flDelay = (GetTimeSpeechComplete() - gpGlobals->curtime);
  1242. m_SpeechWatch_BreathingRamp.Set( flDelay );
  1243. m_SpeechWatch_BreathingRamp.Start();
  1244. }
  1245. return true;
  1246. }
  1247. return false;
  1248. }
  1249. extern int ACT_ANTLION_FLIP;
  1250. extern int ACT_ANTLION_ZAP_FLIP;
  1251. //-----------------------------------------------------------------------------
  1252. //-----------------------------------------------------------------------------
  1253. Disposition_t CNPC_Alyx::IRelationType( CBaseEntity *pTarget )
  1254. {
  1255. Disposition_t disposition = BaseClass::IRelationType( pTarget );
  1256. if ( pTarget == NULL )
  1257. return disposition;
  1258. if( pTarget->Classify() == CLASS_ANTLION )
  1259. {
  1260. if( disposition == D_HT )
  1261. {
  1262. // If Alyx hates this antlion (default relationship), make her fear it, if it is very close.
  1263. if( GetAbsOrigin().DistToSqr(pTarget->GetAbsOrigin()) < ALYX_FEAR_ANTLION_DIST_SQR )
  1264. {
  1265. disposition = D_FR;
  1266. }
  1267. // Fall through...
  1268. }
  1269. }
  1270. else if( pTarget->Classify() == CLASS_ZOMBIE && disposition == D_HT && GetActiveWeapon() )
  1271. {
  1272. if( GetAbsOrigin().DistToSqr(pTarget->GetAbsOrigin()) < ALYX_FEAR_ZOMBIE_DIST_SQR )
  1273. {
  1274. // Be afraid of a zombie that's near if I'm not allowed to dodge. This will make Alyx back away.
  1275. return D_FR;
  1276. }
  1277. }
  1278. else if ( pTarget->Classify() == CLASS_MISSILE )
  1279. {
  1280. // Fire at missiles while in the vehicle
  1281. if ( IsInAVehicle() )
  1282. return D_HT;
  1283. }
  1284. return disposition;
  1285. }
  1286. //-----------------------------------------------------------------------------
  1287. //-----------------------------------------------------------------------------
  1288. int CNPC_Alyx::IRelationPriority( CBaseEntity *pTarget )
  1289. {
  1290. int priority = BaseClass::IRelationPriority( pTarget );
  1291. if( pTarget->Classify() == CLASS_ANTLION )
  1292. {
  1293. // Make Alyx prefer Antlions that are flipped onto their backs.
  1294. // UNLESS she has a different enemy that could melee attack her while her back is turned.
  1295. CAI_BaseNPC *pNPC = pTarget->MyNPCPointer();
  1296. if ( pNPC && ( pNPC->GetActivity() == ACT_ANTLION_FLIP || pNPC->GetActivity() == ACT_ANTLION_ZAP_FLIP ) )
  1297. {
  1298. if( GetEnemy() && GetEnemy() != pTarget )
  1299. {
  1300. // I have an enemy that is not this thing. If that enemy is near, I shouldn't
  1301. // become distracted.
  1302. if( GetAbsOrigin().DistToSqr(GetEnemy()->GetAbsOrigin()) < Square(180) )
  1303. {
  1304. return priority;
  1305. }
  1306. }
  1307. priority += 1;
  1308. }
  1309. }
  1310. return priority;
  1311. }
  1312. //-----------------------------------------------------------------------------
  1313. //-----------------------------------------------------------------------------
  1314. #define ALYX_360_VIEW_DIST_SQR 129600 // 30 feet
  1315. bool CNPC_Alyx::FInViewCone( CBaseEntity *pEntity )
  1316. {
  1317. // Alyx can see 360 degrees but only at limited distance. This allows her to be aware of a
  1318. // large mob of enemies (usually antlions or zombies) closing in. This situation is so obvious to the
  1319. // player that it doesn't make sense for Alyx to be unaware of the entire group simply because she
  1320. // hasn't seen all of the enemies with her own eyes.
  1321. if( ( pEntity->IsNPC() || pEntity->IsPlayer() ) && pEntity->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= ALYX_360_VIEW_DIST_SQR )
  1322. {
  1323. // Only see players and NPC's with 360 cone
  1324. // For instance, DON'T tell the eyeball/head tracking code that you can see an object that is behind you!
  1325. return true;
  1326. }
  1327. // Else, fall through...
  1328. if ( HL2GameRules()->IsAlyxInDarknessMode() )
  1329. {
  1330. if ( CanSeeEntityInDarkness( pEntity ) )
  1331. return true;
  1332. }
  1333. return BaseClass::FInViewCone( pEntity );
  1334. }
  1335. //-----------------------------------------------------------------------------
  1336. // Purpose:
  1337. // Input : *pEntity -
  1338. //-----------------------------------------------------------------------------
  1339. bool CNPC_Alyx::CanSeeEntityInDarkness( CBaseEntity *pEntity )
  1340. {
  1341. /*
  1342. // Alyx can see enemies that are right next to her
  1343. // Robin: Disabled, made her too effective, you could safely leave her alone.
  1344. if ( pEntity->IsNPC() )
  1345. {
  1346. if ( (pEntity->WorldSpaceCenter() - EyePosition()).LengthSqr() < (80*80) )
  1347. return true;
  1348. }
  1349. */
  1350. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  1351. if ( pPlayer && pEntity != pPlayer )
  1352. {
  1353. if ( pPlayer->IsIlluminatedByFlashlight(pEntity, NULL ) )
  1354. return true;
  1355. }
  1356. return LookerCouldSeeTargetInDarkness( this, pEntity );
  1357. }
  1358. //-----------------------------------------------------------------------------
  1359. //-----------------------------------------------------------------------------
  1360. bool CNPC_Alyx::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC)
  1361. {
  1362. if ( HL2GameRules()->IsAlyxInDarknessMode() )
  1363. {
  1364. if ( !CanSeeEntityInDarkness( pEntity ) )
  1365. return false;
  1366. }
  1367. return BaseClass::QuerySeeEntity(pEntity, bOnlyHateOrFearIfNPC);
  1368. }
  1369. //-----------------------------------------------------------------------------
  1370. //-----------------------------------------------------------------------------
  1371. bool CNPC_Alyx::IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition )
  1372. {
  1373. return BaseClass::IsCoverPosition( vecThreat, vecPosition );
  1374. }
  1375. //-----------------------------------------------------------------------------
  1376. // Purpose:
  1377. //-----------------------------------------------------------------------------
  1378. Activity CNPC_Alyx::NPC_TranslateActivity( Activity activity )
  1379. {
  1380. activity = BaseClass::NPC_TranslateActivity( activity );
  1381. if ( activity == ACT_RUN && GetEnemy() && GetEnemy()->Classify() == CLASS_COMBINE_GUNSHIP )
  1382. {
  1383. // Always cower from gunship!
  1384. if ( HaveSequenceForActivity( ACT_RUN_PROTECTED ) )
  1385. activity = ACT_RUN_PROTECTED;
  1386. }
  1387. switch ( activity )
  1388. {
  1389. // !!!HACK - Alyx doesn't have the required animations for shotguns,
  1390. // so trick her into using the rifle counterparts for now (sjb)
  1391. case ACT_RUN_AIM_SHOTGUN: return ACT_RUN_AIM_RIFLE;
  1392. case ACT_WALK_AIM_SHOTGUN: return ACT_WALK_AIM_RIFLE;
  1393. case ACT_IDLE_ANGRY_SHOTGUN: return ACT_IDLE_ANGRY_SMG1;
  1394. case ACT_RANGE_ATTACK_SHOTGUN_LOW: return ACT_RANGE_ATTACK_SMG1_LOW;
  1395. case ACT_PICKUP_RACK: return (Activity)ACT_ALYX_PICKUP_RACK;
  1396. case ACT_DROP_WEAPON: if ( HasShotgun() ) return (Activity)ACT_DROP_WEAPON_SHOTGUN;
  1397. }
  1398. return activity;
  1399. }
  1400. bool CNPC_Alyx::ShouldDeferToFollowBehavior()
  1401. {
  1402. return BaseClass::ShouldDeferToFollowBehavior();
  1403. }
  1404. void CNPC_Alyx::BuildScheduleTestBits()
  1405. {
  1406. bool bIsInteracting = false;
  1407. bIsInteracting = ( IsCurSchedule(SCHED_ALYX_PREPARE_TO_INTERACT_WITH_TARGET, false) ||
  1408. IsCurSchedule(SCHED_ALYX_WAIT_TO_INTERACT_WITH_TARGET, false) ||
  1409. IsCurSchedule(SCHED_ALYX_INTERACT_WITH_TARGET, false) ||
  1410. IsCurSchedule(SCHED_ALYX_INTERACTION_INTERRUPTED, false) ||
  1411. IsCurSchedule(SCHED_ALYX_FINISH_INTERACTING_WITH_TARGET, false) );
  1412. if( !bIsInteracting && IsAllowedToInteract() )
  1413. {
  1414. switch( m_NPCState )
  1415. {
  1416. case NPC_STATE_COMBAT:
  1417. SetCustomInterruptCondition( COND_ALYX_HAS_INTERACT_TARGET );
  1418. SetCustomInterruptCondition( COND_ALYX_CAN_INTERACT_WITH_TARGET );
  1419. break;
  1420. case NPC_STATE_ALERT:
  1421. case NPC_STATE_IDLE:
  1422. SetCustomInterruptCondition( COND_ALYX_HAS_INTERACT_TARGET );
  1423. SetCustomInterruptCondition( COND_ALYX_CAN_INTERACT_WITH_TARGET );
  1424. break;
  1425. }
  1426. }
  1427. // This nugget fixes a bug where Alyx will continue to attack an enemy she no longer hates in the
  1428. // case where her relationship with the enemy changes while she's running a SCHED_SCENE_GENERIC.
  1429. // Since we don't run ChooseEnemy() when we're running a schedule that doesn't interrupt on COND_NEW_ENEMY,
  1430. // we also do not re-evaluate and flush enemies we don't hate anymore. (sjb 6/9/2005)
  1431. if( IsCurSchedule(SCHED_SCENE_GENERIC) && GetEnemy() && GetEnemy()->VPhysicsGetObject() )
  1432. {
  1433. if( GetEnemy()->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  1434. {
  1435. SetCustomInterruptCondition( COND_NEW_ENEMY );
  1436. }
  1437. }
  1438. if( GetCurSchedule()->HasInterrupt( COND_IDLE_INTERRUPT ) )
  1439. {
  1440. SetCustomInterruptCondition( COND_BETTER_WEAPON_AVAILABLE );
  1441. }
  1442. // If we're not in a script, keep an eye out for falling
  1443. if ( m_NPCState != NPC_STATE_SCRIPT && !IsInAVehicle() && !IsCurSchedule(SCHED_ALYX_FALL_TO_GROUND,false) )
  1444. {
  1445. SetCustomInterruptCondition( COND_FLOATING_OFF_GROUND );
  1446. }
  1447. BaseClass::BuildScheduleTestBits();
  1448. }
  1449. //-----------------------------------------------------------------------------
  1450. //-----------------------------------------------------------------------------
  1451. bool CNPC_Alyx::ShouldBehaviorSelectSchedule( CAI_BehaviorBase *pBehavior )
  1452. {
  1453. if( pBehavior == &m_AssaultBehavior )
  1454. {
  1455. if( HasCondition( COND_MOBBED_BY_ENEMIES ))
  1456. return false;
  1457. }
  1458. return BaseClass::ShouldBehaviorSelectSchedule( pBehavior );
  1459. }
  1460. //-----------------------------------------------------------------------------
  1461. //-----------------------------------------------------------------------------
  1462. int CNPC_Alyx::SelectSchedule( void )
  1463. {
  1464. // If we're in darkness mode, and the player has the flashlight off, and we hear a zombie footstep,
  1465. // and the player isn't nearby, deliberately turn away from the zombie to let the zombie grab me.
  1466. if ( HL2GameRules()->IsAlyxInDarknessMode() && m_NPCState == NPC_STATE_ALERT )
  1467. {
  1468. if ( HasCondition ( COND_HEAR_COMBAT ) && !HasCondition(COND_SEE_PLAYER) )
  1469. {
  1470. CSound *pBestSound = GetBestSound();
  1471. if ( pBestSound && pBestSound->m_hOwner )
  1472. {
  1473. if ( pBestSound->m_hOwner->Classify() == CLASS_ZOMBIE && pBestSound->SoundChannel() == SOUNDENT_CHANNEL_NPC_FOOTSTEP )
  1474. return SCHED_ALYX_ALERT_FACE_AWAYFROM_BESTSOUND;
  1475. }
  1476. }
  1477. }
  1478. if( HasCondition(COND_ALYX_CAN_INTERACT_WITH_TARGET) )
  1479. return SCHED_ALYX_INTERACT_WITH_TARGET;
  1480. if( HasCondition(COND_ALYX_HAS_INTERACT_TARGET) && HasCondition(COND_SEE_PLAYER) && IsAllowedToInteract() )
  1481. {
  1482. ExpireCurrentRandomLookTarget();
  1483. if( IsEMPHolstered() )
  1484. {
  1485. return SCHED_ALYX_PREPARE_TO_INTERACT_WITH_TARGET;
  1486. }
  1487. return SCHED_ALYX_WAIT_TO_INTERACT_WITH_TARGET;
  1488. }
  1489. if( !IsEMPHolstered() && !HasInteractTarget() && !m_ActBusyBehavior.IsActive() )
  1490. return SCHED_ALYX_HOLSTER_EMP;
  1491. if ( HasCondition(COND_BETTER_WEAPON_AVAILABLE) )
  1492. {
  1493. if( m_iszPendingWeapon != NULL_STRING )
  1494. {
  1495. return SCHED_SWITCH_TO_PENDING_WEAPON;
  1496. }
  1497. else
  1498. {
  1499. CBaseHLCombatWeapon *pWeapon = dynamic_cast<CBaseHLCombatWeapon *>(Weapon_FindUsable( WEAPON_SEARCH_DELTA ));
  1500. if ( pWeapon )
  1501. {
  1502. m_flNextWeaponSearchTime = gpGlobals->curtime + 10.0;
  1503. // Now lock the weapon for several seconds while we go to pick it up.
  1504. pWeapon->Lock( 10.0, this );
  1505. SetTarget( pWeapon );
  1506. return SCHED_ALYX_NEW_WEAPON;
  1507. }
  1508. }
  1509. }
  1510. if ( HasCondition(COND_ENEMY_OCCLUDED) )
  1511. {
  1512. //Warning("CROUCH: Standing, enemy is occluded.\n" );
  1513. Stand();
  1514. }
  1515. return BaseClass::SelectSchedule();
  1516. }
  1517. //-----------------------------------------------------------------------------
  1518. //-----------------------------------------------------------------------------
  1519. int CNPC_Alyx::SelectScheduleDanger( void )
  1520. {
  1521. if( HasCondition( COND_HEAR_DANGER ) )
  1522. {
  1523. CSound *pSound;
  1524. pSound = GetBestSound( SOUND_DANGER );
  1525. ASSERT( pSound != NULL );
  1526. if ( pSound && (pSound->m_iType & SOUND_DANGER) && ( pSound->SoundChannel() == SOUNDENT_CHANNEL_ZOMBINE_GRENADE ) )
  1527. {
  1528. SpeakIfAllowed( TLK_DANGER_ZOMBINE_GRENADE );
  1529. }
  1530. }
  1531. return BaseClass::SelectScheduleDanger();
  1532. }
  1533. //-----------------------------------------------------------------------------
  1534. //-----------------------------------------------------------------------------
  1535. int CNPC_Alyx::TranslateSchedule( int scheduleType )
  1536. {
  1537. switch( scheduleType )
  1538. {
  1539. case SCHED_ALERT_FACE_BESTSOUND:
  1540. return SCHED_ALYX_ALERT_FACE_BESTSOUND;
  1541. break;
  1542. case SCHED_COMBAT_FACE:
  1543. if ( !HasCondition(COND_TASK_FAILED) && !IsCrouching() )
  1544. return SCHED_ALYX_COMBAT_FACE;
  1545. break;
  1546. case SCHED_WAKE_ANGRY:
  1547. return SCHED_ALYX_WAKE_ANGRY;
  1548. break;
  1549. case SCHED_FALL_TO_GROUND:
  1550. return SCHED_ALYX_FALL_TO_GROUND;
  1551. break;
  1552. case SCHED_ALERT_REACT_TO_COMBAT_SOUND:
  1553. return SCHED_ALYX_ALERT_REACT_TO_COMBAT_SOUND;
  1554. break;
  1555. case SCHED_COWER:
  1556. case SCHED_PC_COWER:
  1557. // Alyx doesn't have cower animations.
  1558. return SCHED_FAIL;
  1559. case SCHED_RANGE_ATTACK1:
  1560. {
  1561. if ( GetEnemy() )
  1562. {
  1563. CBaseEntity *pEnemy = GetEnemy();
  1564. if ( !IsCrouching() )
  1565. {
  1566. // Does my enemy have enough health to warrant crouching?
  1567. if ( pEnemy->GetHealth() > ALYX_MIN_ENEMY_HEALTH_TO_CROUCH )
  1568. {
  1569. // And are they far enough away? Expand the min dist so we don't crouch & stand immediately.
  1570. if ( EnemyDistance( pEnemy ) > (ALYX_MIN_ENEMY_DIST_TO_CROUCH * 1.5) && (pEnemy->GetFlags() & FL_ONGROUND) )
  1571. {
  1572. //Warning("CROUCH: Desiring due to enemy far away.\n" );
  1573. DesireCrouch();
  1574. }
  1575. }
  1576. }
  1577. // Are we supposed to be crouching?
  1578. if ( IsCrouching() || ( CrouchIsDesired() && !HasCondition( COND_HEAVY_DAMAGE ) ) )
  1579. {
  1580. // See if they're a valid crouch target
  1581. if ( EnemyIsValidCrouchTarget( pEnemy ) )
  1582. {
  1583. Crouch();
  1584. }
  1585. else
  1586. {
  1587. //Warning("CROUCH: Standing, enemy not valid crouch target.\n" );
  1588. Stand();
  1589. }
  1590. }
  1591. else
  1592. {
  1593. //Warning("CROUCH: Standing, no enemy.\n" );
  1594. Stand();
  1595. }
  1596. }
  1597. return SCHED_ALYX_RANGE_ATTACK1;
  1598. }
  1599. break;
  1600. case SCHED_HIDE_AND_RELOAD:
  1601. {
  1602. if ( HL2GameRules()->IsAlyxInDarknessMode() )
  1603. return SCHED_RELOAD;
  1604. // If I don't have a ranged attacker as an enemy, don't try to hide
  1605. AIEnemiesIter_t iter;
  1606. for ( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  1607. {
  1608. CAI_BaseNPC *pEnemy = pEMemory->hEnemy.Get()->MyNPCPointer();
  1609. if ( !pEnemy )
  1610. continue;
  1611. // Ignore enemies that don't hate me
  1612. if ( pEnemy->IRelationType( this ) != D_HT )
  1613. continue;
  1614. // Look for enemies with ranged capabilities
  1615. if ( pEnemy->CapabilitiesGet() & ( bits_CAP_WEAPON_RANGE_ATTACK1 | bits_CAP_WEAPON_RANGE_ATTACK2 | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 ) )
  1616. return SCHED_HIDE_AND_RELOAD;
  1617. }
  1618. return SCHED_RELOAD;
  1619. }
  1620. break;
  1621. case SCHED_RUN_FROM_ENEMY:
  1622. if ( HasCondition( COND_MOBBED_BY_ENEMIES ) )
  1623. {
  1624. return SCHED_RUN_FROM_ENEMY_MOB;
  1625. }
  1626. break;
  1627. case SCHED_IDLE_STAND:
  1628. return SCHED_ALYX_IDLE_STAND;
  1629. #ifdef HL2_EPISODIC
  1630. case SCHED_RUN_RANDOM:
  1631. if( GetEnemy() && HasCondition(COND_SEE_ENEMY) && GetActiveWeapon() )
  1632. {
  1633. // SCHED_RUN_RANDOM is a last ditch effort, it's the bottom of a chain of
  1634. // sequential schedule failures. Since this can cause Alyx to freeze up,
  1635. // just let her fight if she can. (sjb).
  1636. return SCHED_RANGE_ATTACK1;
  1637. }
  1638. break;
  1639. #endif// HL2_EPISODIC
  1640. }
  1641. return BaseClass::TranslateSchedule( scheduleType );
  1642. }
  1643. //-----------------------------------------------------------------------------
  1644. // Purpose:
  1645. //-----------------------------------------------------------------------------
  1646. void CNPC_Alyx::StartTask( const Task_t *pTask )
  1647. {
  1648. switch( pTask->iTask )
  1649. {
  1650. case TASK_SOUND_WAKE:
  1651. LocateEnemySound();
  1652. // Don't do the half second wait here that the PlayerCompanion class does. (sbj) 1/4/2006
  1653. TaskComplete();
  1654. break;
  1655. case TASK_ANNOUNCE_ATTACK:
  1656. {
  1657. SpeakAttacking();
  1658. BaseClass::StartTask( pTask );
  1659. break;
  1660. }
  1661. case TASK_ALYX_BUILD_COMBAT_FACE_PATH:
  1662. {
  1663. if ( GetEnemy() && !FInAimCone( GetEnemyLKP() ) && FVisible( GetEnemyLKP() ) )
  1664. {
  1665. Vector vecToEnemy = GetEnemyLKP() - GetAbsOrigin();
  1666. VectorNormalize( vecToEnemy );
  1667. Vector vecMoveGoal = GetAbsOrigin() - (vecToEnemy * 24.0f);
  1668. if ( !GetNavigator()->SetGoal( vecMoveGoal ) )
  1669. {
  1670. TaskFail(FAIL_NO_ROUTE);
  1671. }
  1672. else
  1673. {
  1674. GetMotor()->SetIdealYawToTarget( GetEnemy()->WorldSpaceCenter() );
  1675. GetNavigator()->SetArrivalDirection( GetEnemy() );
  1676. TaskComplete();
  1677. }
  1678. }
  1679. else
  1680. {
  1681. TaskFail("Defaulting To BaseClass::CombatFace");
  1682. }
  1683. }
  1684. break;
  1685. case TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL:
  1686. {
  1687. // If we don't have the alyx gun, throw away our current,
  1688. // since the alyx gun is the only one we can tuck away.
  1689. if ( HasAlyxgun() )
  1690. {
  1691. SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED_DESTROYED );
  1692. }
  1693. else
  1694. {
  1695. Weapon_Drop( GetActiveWeapon() );
  1696. }
  1697. SetWait( 1 ); // Wait while she does it.
  1698. }
  1699. break;
  1700. case TASK_STOP_MOVING:
  1701. if ( npc_alyx_force_stop_moving.GetBool() )
  1702. {
  1703. if ( ( GetNavigator()->IsGoalSet() && GetNavigator()->IsGoalActive() ) || GetNavType() == NAV_JUMP )
  1704. {
  1705. DbgNavMsg( this, "Start TASK_STOP_MOVING\n" );
  1706. DbgNavMsg( this, "Initiating stopping path\n" );
  1707. GetNavigator()->StopMoving( false );
  1708. // E3 Hack
  1709. if ( HasPoseMoveYaw() )
  1710. {
  1711. SetPoseParameter( m_poseMove_Yaw, 0 );
  1712. }
  1713. }
  1714. else
  1715. {
  1716. if ( GetNavigator()->SetGoalFromStoppingPath() )
  1717. {
  1718. DbgNavMsg( this, "Start TASK_STOP_MOVING\n" );
  1719. DbgNavMsg( this, "Initiating stopping path\n" );
  1720. }
  1721. else
  1722. {
  1723. GetNavigator()->ClearGoal();
  1724. if ( IsMoving() )
  1725. {
  1726. SetIdealActivity( GetStoppedActivity() );
  1727. }
  1728. TaskComplete();
  1729. }
  1730. }
  1731. }
  1732. else
  1733. {
  1734. BaseClass::StartTask( pTask );
  1735. }
  1736. break;
  1737. case TASK_REACT_TO_COMBAT_SOUND:
  1738. {
  1739. CSound *pSound = GetBestSound();
  1740. if( pSound && pSound->IsSoundType(SOUND_COMBAT) && pSound->IsSoundType(SOUND_CONTEXT_GUNFIRE) )
  1741. {
  1742. AnalyzeGunfireSound(pSound);
  1743. }
  1744. TaskComplete();
  1745. }
  1746. break;
  1747. case TASK_ALYX_HOLSTER_PISTOL:
  1748. HolsterPistol();
  1749. TaskComplete();
  1750. break;
  1751. case TASK_ALYX_DRAW_PISTOL:
  1752. DrawPistol();
  1753. TaskComplete();
  1754. break;
  1755. case TASK_ALYX_WAIT_HACKING:
  1756. SetWait( pTask->flTaskData );
  1757. break;
  1758. case TASK_ALYX_GET_PATH_TO_INTERACT_TARGET:
  1759. {
  1760. if( !HasInteractTarget() )
  1761. {
  1762. TaskFail("No interact target");
  1763. return;
  1764. }
  1765. AI_NavGoal_t goal;
  1766. goal.type = GOALTYPE_LOCATION;
  1767. goal.dest = GetInteractTarget()->WorldSpaceCenter();
  1768. goal.pTarget = GetInteractTarget();
  1769. GetNavigator()->SetGoal( goal );
  1770. }
  1771. break;
  1772. case TASK_ALYX_ANNOUNCE_HACK:
  1773. SpeakIfAllowed( CONCEPT_ALYX_REQUEST_ITEM );
  1774. TaskComplete();
  1775. break;
  1776. case TASK_ALYX_BEGIN_INTERACTION:
  1777. {
  1778. INPCInteractive *pInteractive = dynamic_cast<INPCInteractive *>(GetInteractTarget());
  1779. if ( pInteractive )
  1780. {
  1781. EmpZapTarget( GetInteractTarget() );
  1782. pInteractive->AlyxStartedInteraction();
  1783. pInteractive->NotifyInteraction(this);
  1784. pInteractive->AlyxFinishedInteraction();
  1785. m_OnFinishInteractWithObject.FireOutput( GetInteractTarget(), this );
  1786. }
  1787. TaskComplete();
  1788. }
  1789. break;
  1790. case TASK_ALYX_COMPLETE_INTERACTION:
  1791. {
  1792. INPCInteractive *pInteractive = dynamic_cast<INPCInteractive *>(GetInteractTarget());
  1793. if( pInteractive )
  1794. {
  1795. for( int i = 0 ; i < 3 ; i++ )
  1796. {
  1797. g_pEffects->Sparks( GetInteractTarget()->WorldSpaceCenter() );
  1798. }
  1799. GetInteractTarget()->EmitSound("DoSpark");
  1800. Speak( CONCEPT_ALYX_INTERACTION_DONE );
  1801. SetInteractTarget(NULL);
  1802. }
  1803. TaskComplete();
  1804. }
  1805. break;
  1806. case TASK_ALYX_SET_IDLE_ACTIVITY:
  1807. {
  1808. Activity goalActivity = (Activity)((int)pTask->flTaskData);
  1809. if ( IsActivityFinished() )
  1810. {
  1811. SetIdealActivity( goalActivity );
  1812. }
  1813. }
  1814. break;
  1815. case TASK_ALYX_FALL_TO_GROUND:
  1816. // If we wait this long without landing, we'll fall to our death
  1817. SetWait(2);
  1818. break;
  1819. default:
  1820. BaseClass::StartTask(pTask);
  1821. break;
  1822. }
  1823. }
  1824. //-----------------------------------------------------------------------------
  1825. //-----------------------------------------------------------------------------
  1826. void CNPC_Alyx::RunTask( const Task_t *pTask )
  1827. {
  1828. switch( pTask->iTask )
  1829. {
  1830. case TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL:
  1831. if( IsWaitFinished() )
  1832. TaskComplete();
  1833. break;
  1834. case TASK_STOP_MOVING:
  1835. {
  1836. if ( npc_alyx_force_stop_moving.GetBool() )
  1837. {
  1838. ChainRunTask( TASK_WAIT_FOR_MOVEMENT );
  1839. if ( !TaskIsRunning() )
  1840. {
  1841. DbgNavMsg( this, "TASK_STOP_MOVING Complete\n" );
  1842. }
  1843. }
  1844. else
  1845. {
  1846. BaseClass::RunTask( pTask );
  1847. }
  1848. break;
  1849. }
  1850. case TASK_ALYX_WAIT_HACKING:
  1851. if( GetInteractTarget() && random->RandomInt(0, 3) == 0 )
  1852. {
  1853. g_pEffects->Sparks( GetInteractTarget()->WorldSpaceCenter() );
  1854. GetInteractTarget()->EmitSound("DoSpark");
  1855. }
  1856. if ( IsWaitFinished() )
  1857. {
  1858. TaskComplete();
  1859. }
  1860. break;
  1861. case TASK_ALYX_SET_IDLE_ACTIVITY:
  1862. {
  1863. if ( IsActivityStarted() )
  1864. {
  1865. TaskComplete();
  1866. }
  1867. }
  1868. break;
  1869. case TASK_ALYX_FALL_TO_GROUND:
  1870. if ( GetFlags() & FL_ONGROUND )
  1871. {
  1872. TaskComplete();
  1873. }
  1874. else if( IsWaitFinished() )
  1875. {
  1876. // Call back to the base class & see if it can find a ground for us
  1877. // If it can't, we'll fall to our death
  1878. ChainRunTask( TASK_FALL_TO_GROUND );
  1879. if ( TaskIsRunning() )
  1880. {
  1881. CTakeDamageInfo info;
  1882. info.SetDamage( m_iHealth );
  1883. info.SetAttacker( this );
  1884. info.SetInflictor( this );
  1885. info.SetDamageType( DMG_GENERIC );
  1886. TakeDamage( info );
  1887. }
  1888. }
  1889. break;
  1890. default:
  1891. BaseClass::RunTask(pTask);
  1892. break;
  1893. }
  1894. }
  1895. //-----------------------------------------------------------------------------
  1896. //-----------------------------------------------------------------------------
  1897. void CNPC_Alyx::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
  1898. {
  1899. switch( NewState )
  1900. {
  1901. case NPC_STATE_COMBAT:
  1902. {
  1903. m_fCombatStartTime = gpGlobals->curtime;
  1904. }
  1905. break;
  1906. default:
  1907. if( OldState == NPC_STATE_COMBAT )
  1908. {
  1909. // coming out of combat state.
  1910. m_fCombatEndTime = gpGlobals->curtime + 2.0f;
  1911. }
  1912. break;
  1913. }
  1914. }
  1915. //-----------------------------------------------------------------------------
  1916. // Purpose:
  1917. //-----------------------------------------------------------------------------
  1918. void CNPC_Alyx::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  1919. {
  1920. BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
  1921. // FIXME: hack until some way of removing decals after healing
  1922. m_fNoDamageDecal = true;
  1923. }
  1924. //-----------------------------------------------------------------------------
  1925. //-----------------------------------------------------------------------------
  1926. bool CNPC_Alyx::CanBeHitByMeleeAttack( CBaseEntity *pAttacker )
  1927. {
  1928. if( IsCurSchedule(SCHED_DUCK_DODGE) )
  1929. {
  1930. return false;
  1931. }
  1932. return BaseClass::CanBeHitByMeleeAttack( pAttacker );
  1933. }
  1934. //-----------------------------------------------------------------------------
  1935. //-----------------------------------------------------------------------------
  1936. int CNPC_Alyx::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  1937. {
  1938. //!!!HACKHACK - EP1 - Stop alyx taking all physics damage to prevent her dying
  1939. // in freak accidents resembling spontaneous stress damage death (which are now impossible)
  1940. // Also stop her taking damage from flames: Fixes her being burnt to death from entity flames
  1941. // attached to random debris chunks while inside scripted sequences.
  1942. if( info.GetDamageType() & (DMG_CRUSH | DMG_BURN) )
  1943. return 0;
  1944. // If we're in commentary mode, prevent her taking damage from other NPCs
  1945. if ( IsInCommentaryMode() && info.GetAttacker() && info.GetAttacker()->IsNPC() )
  1946. return 0;
  1947. int taken = BaseClass::OnTakeDamage_Alive(info);
  1948. if ( taken && HL2GameRules()->IsAlyxInDarknessMode() && !HasCondition( COND_TALKER_PLAYER_DEAD ) )
  1949. {
  1950. if ( !HasCondition(COND_SEE_ENEMY) && (info.GetDamageType() & (DMG_SLASH | DMG_CLUB) ) )
  1951. {
  1952. // I've taken melee damage. If I haven't seen the enemy for a few seconds, make some noise.
  1953. float flLastTimeSeen = GetEnemies()->LastTimeSeen( info.GetAttacker(), false );
  1954. if ( flLastTimeSeen == AI_INVALID_TIME || gpGlobals->curtime - flLastTimeSeen > 3.0 )
  1955. {
  1956. SpeakIfAllowed( "TLK_DARKNESS_UNKNOWN_WOUND" );
  1957. m_fTimeUntilNextDarknessFoundPlayer = gpGlobals->curtime + RandomFloat( 3, 5 );
  1958. }
  1959. }
  1960. }
  1961. if( taken && (info.GetDamageType() & DMG_BLAST) )
  1962. {
  1963. if ( HasShotgun() )
  1964. {
  1965. if ( !IsPlayingGesture(ACT_GESTURE_FLINCH_BLAST) && !IsPlayingGesture(ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN) )
  1966. {
  1967. RestartGesture( ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN );
  1968. GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + SequenceDuration( ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN ) + 0.5f );
  1969. }
  1970. }
  1971. else
  1972. {
  1973. if ( !IsPlayingGesture(ACT_GESTURE_FLINCH_BLAST) && !IsPlayingGesture(ACT_GESTURE_FLINCH_BLAST_DAMAGED) )
  1974. {
  1975. RestartGesture( ACT_GESTURE_FLINCH_BLAST_DAMAGED );
  1976. GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + SequenceDuration( ACT_GESTURE_FLINCH_BLAST_DAMAGED ) + 0.5f );
  1977. }
  1978. }
  1979. }
  1980. return taken;
  1981. }
  1982. //------------------------------------------------------------------------------
  1983. //------------------------------------------------------------------------------
  1984. bool CNPC_Alyx::FCanCheckAttacks()
  1985. {
  1986. if( GetEnemy() && IsGunship( GetEnemy() ) )
  1987. {
  1988. // Don't attack gunships
  1989. return false;
  1990. }
  1991. return BaseClass::FCanCheckAttacks();
  1992. }
  1993. //-----------------------------------------------------------------------------
  1994. // Purpose: Half damage against Combine Soldiers in outland_10
  1995. //-----------------------------------------------------------------------------
  1996. float CNPC_Alyx::GetAttackDamageScale( CBaseEntity *pVictim )
  1997. {
  1998. if( g_HackOutland10DamageHack && pVictim->Classify() == CLASS_COMBINE )
  1999. {
  2000. return 0.75f;
  2001. }
  2002. return BaseClass::GetAttackDamageScale( pVictim );
  2003. }
  2004. //-----------------------------------------------------------------------------
  2005. //-----------------------------------------------------------------------------
  2006. bool CNPC_Alyx::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
  2007. {
  2008. if( interactionType == g_interactionZombieMeleeWarning && IsAllowedToDodge() )
  2009. {
  2010. // If a zombie is attacking, ditch my current schedule and duck if I'm running a schedule that will
  2011. // be interrupted if I'm hit.
  2012. if( ConditionInterruptsCurSchedule(COND_LIGHT_DAMAGE) || ConditionInterruptsCurSchedule( COND_HEAVY_DAMAGE) )
  2013. {
  2014. //Only dodge an NPC you can see attacking.
  2015. if( sourceEnt && FInViewCone(sourceEnt) )
  2016. {
  2017. SetSchedule(SCHED_DUCK_DODGE);
  2018. }
  2019. }
  2020. return true;
  2021. }
  2022. if( interactionType == g_interactionPlayerPuntedHeavyObject )
  2023. {
  2024. // Try to get Alyx out of the way when player is punting cars around.
  2025. CBaseEntity *pProp = (CBaseEntity*)(data);
  2026. if( pProp )
  2027. {
  2028. float distToProp = pProp->WorldSpaceCenter().DistTo( GetAbsOrigin() );
  2029. float distToPlayer = sourceEnt->WorldSpaceCenter().DistTo( GetAbsOrigin() );
  2030. // Do this if the prop is within 60 feet, and is closer to me than the player is.
  2031. if( distToProp < (60.0f * 12.0f) && (distToProp < distToPlayer) )
  2032. {
  2033. if( fabs(pProp->WorldSpaceCenter().z - WorldSpaceCenter().z) <= 120.0f )
  2034. {
  2035. if( sourceEnt->FInViewCone(this) )
  2036. {
  2037. CSoundEnt::InsertSound( SOUND_MOVE_AWAY, EarPosition(), 16, 1.0f, pProp );
  2038. }
  2039. }
  2040. }
  2041. }
  2042. return true;
  2043. }
  2044. return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
  2045. }
  2046. //-----------------------------------------------------------------------------
  2047. //-----------------------------------------------------------------------------
  2048. void CNPC_Alyx::HolsterPistol()
  2049. {
  2050. if( GetActiveWeapon() )
  2051. {
  2052. GetActiveWeapon()->AddEffects(EF_NODRAW);
  2053. }
  2054. }
  2055. //-----------------------------------------------------------------------------
  2056. //-----------------------------------------------------------------------------
  2057. void CNPC_Alyx::DrawPistol()
  2058. {
  2059. if( GetActiveWeapon() )
  2060. {
  2061. GetActiveWeapon()->RemoveEffects(EF_NODRAW);
  2062. }
  2063. }
  2064. //-----------------------------------------------------------------------------
  2065. //-----------------------------------------------------------------------------
  2066. void CNPC_Alyx::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget, const Vector *pVelocity )
  2067. {
  2068. BaseClass::Weapon_Drop( pWeapon, pvecTarget, pVelocity );
  2069. if( pWeapon && pWeapon->ClassMatches( CLASSNAME_ALYXGUN ) )
  2070. {
  2071. pWeapon->SUB_Remove();
  2072. }
  2073. m_WeaponType = WT_NONE;
  2074. }
  2075. //-----------------------------------------------------------------------------
  2076. //-----------------------------------------------------------------------------
  2077. bool CNPC_Alyx::IsAllowedToAim()
  2078. {
  2079. // Alyx can aim only if fully agitated
  2080. if( GetReadinessLevel() != AIRL_AGITATED )
  2081. return false;
  2082. return BaseClass::IsAllowedToAim();
  2083. }
  2084. //-----------------------------------------------------------------------------
  2085. void CNPC_Alyx::PainSound( const CTakeDamageInfo &info )
  2086. {
  2087. // Alex has specific sounds for when attacked in the dark
  2088. if ( !HasCondition( COND_ALYX_IN_DARK ) )
  2089. {
  2090. // set up the speech modifiers
  2091. CFmtStrN<128> modifiers( "damageammo:%s", info.GetAmmoName() );
  2092. SpeakIfAllowed( TLK_WOUND, modifiers );
  2093. }
  2094. }
  2095. //-----------------------------------------------------------------------------
  2096. void CNPC_Alyx::DeathSound( const CTakeDamageInfo &info )
  2097. {
  2098. // Sentences don't play on dead NPCs
  2099. SentenceStop();
  2100. if ( !SpokeConcept( TLK_SELF_IN_BARNACLE ) )
  2101. {
  2102. EmitSound( "npc_alyx.die" );
  2103. }
  2104. }
  2105. //-----------------------------------------------------------------------------
  2106. // Purpose:
  2107. //-----------------------------------------------------------------------------
  2108. void CNPC_Alyx::OnSeeEntity( CBaseEntity *pEntity )
  2109. {
  2110. BaseClass::OnSeeEntity(pEntity);
  2111. if( pEntity->IsPlayer() && pEntity->IsEFlagSet(EFL_IS_BEING_LIFTED_BY_BARNACLE) )
  2112. {
  2113. SpeakIfAllowed( TLK_ALLY_IN_BARNACLE );
  2114. }
  2115. }
  2116. //---------------------------------------------------------
  2117. // A sort of trivial rejection, this function tells us whether
  2118. // this object is something Alyx can interact with at all.
  2119. // (Alyx's state and the object's state are not considered
  2120. // at this stage)
  2121. //---------------------------------------------------------
  2122. bool CNPC_Alyx::IsValidInteractTarget( CBaseEntity *pTarget )
  2123. {
  2124. INPCInteractive *pInteractive = dynamic_cast<INPCInteractive *>(pTarget);
  2125. if( !pInteractive )
  2126. {
  2127. // Not an INPCInteractive entity.
  2128. return false;
  2129. }
  2130. if( !pInteractive->CanInteractWith(this) )
  2131. {
  2132. return false;
  2133. }
  2134. if( pInteractive->HasBeenInteractedWith() )
  2135. {
  2136. // Already been interacted with.
  2137. return false;
  2138. }
  2139. IPhysicsObject *pPhysics;
  2140. pPhysics = pTarget->VPhysicsGetObject();
  2141. if( pPhysics )
  2142. {
  2143. if( !(pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
  2144. {
  2145. // Player isn't holding this physics object
  2146. return false;
  2147. }
  2148. }
  2149. if( GetAbsOrigin().DistToSqr(pTarget->WorldSpaceCenter()) > (360.0f * 360.0f) )
  2150. {
  2151. // Too far away!
  2152. return false;
  2153. }
  2154. return true;
  2155. }
  2156. //-----------------------------------------------------------------------------
  2157. // Purpose:
  2158. //-----------------------------------------------------------------------------
  2159. void CNPC_Alyx::SetInteractTarget( CBaseEntity *pTarget )
  2160. {
  2161. if( !pTarget )
  2162. {
  2163. ClearCondition( COND_ALYX_HAS_INTERACT_TARGET );
  2164. ClearCondition( COND_ALYX_CAN_INTERACT_WITH_TARGET );
  2165. SetCondition( COND_ALYX_NO_INTERACT_TARGET );
  2166. SetCondition( COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET );
  2167. }
  2168. m_hHackTarget.Set(pTarget);
  2169. }
  2170. //-----------------------------------------------------------------------------
  2171. //-----------------------------------------------------------------------------
  2172. void CNPC_Alyx::EmpZapTarget( CBaseEntity *pTarget )
  2173. {
  2174. g_pEffects->Sparks( pTarget->WorldSpaceCenter() );
  2175. CAlyxEmpEffect *pEmpEffect = (CAlyxEmpEffect*)CreateEntityByName( "env_alyxemp" );
  2176. if( pEmpEffect )
  2177. {
  2178. pEmpEffect->Spawn();
  2179. pEmpEffect->ActivateAutomatic( this, pTarget );
  2180. }
  2181. }
  2182. //-----------------------------------------------------------------------------
  2183. // Purpose:
  2184. //-----------------------------------------------------------------------------
  2185. bool CNPC_Alyx::CanInteractWithTarget( CBaseEntity *pTarget )
  2186. {
  2187. if( !IsValidInteractTarget(pTarget) )
  2188. return false;
  2189. float flDist;
  2190. flDist = (WorldSpaceCenter() - pTarget->WorldSpaceCenter()).Length();
  2191. if( flDist > 80.0f )
  2192. {
  2193. return false;
  2194. }
  2195. if( !IsAllowedToInteract() )
  2196. {
  2197. SpeakIfAllowed( TLK_CANT_INTERACT_NOW );
  2198. return false;
  2199. }
  2200. if( IsEMPHolstered() )
  2201. return false;
  2202. return true;
  2203. }
  2204. //-----------------------------------------------------------------------------
  2205. // Purpose: Player has illuminated this NPC with the flashlight
  2206. //-----------------------------------------------------------------------------
  2207. void CNPC_Alyx::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot )
  2208. {
  2209. if ( m_bIsFlashlightBlind )
  2210. return;
  2211. if ( !CanBeBlindedByFlashlight( true ) )
  2212. return;
  2213. // Ignore the flashlight if it's not shining at my eyes
  2214. if ( PlayerFlashlightOnMyEyes( pPlayer ) )
  2215. {
  2216. char szResponse[AI_Response::MAX_RESPONSE_NAME];
  2217. // Only say the blinding speech if it's time to
  2218. if ( SpeakIfAllowed( "TLK_FLASHLIGHT_ILLUM", NULL, false, szResponse, AI_Response::MAX_RESPONSE_NAME ) )
  2219. {
  2220. m_iszCurrentBlindScene = AllocPooledString( szResponse );
  2221. ADD_DEBUG_HISTORY( HISTORY_ALYX_BLIND, UTIL_VarArgs( "(%0.2f) Alyx: start flashlight blind scene '%s'\n", gpGlobals->curtime, STRING(m_iszCurrentBlindScene) ) );
  2222. GetShotRegulator()->DisableShooting();
  2223. m_bIsFlashlightBlind = true;
  2224. m_fStayBlindUntil = gpGlobals->curtime + 0.1f;
  2225. }
  2226. }
  2227. }
  2228. //-----------------------------------------------------------------------------
  2229. // Purpose: Check if player has illuminated this NPC with a flare
  2230. //-----------------------------------------------------------------------------
  2231. void CNPC_Alyx::CheckBlindedByFlare( void )
  2232. {
  2233. if ( m_bIsFlashlightBlind )
  2234. return;
  2235. if ( !CanBeBlindedByFlashlight( false ) )
  2236. return;
  2237. // Ignore the flare if it's not too close
  2238. if ( BlindedByFlare() )
  2239. {
  2240. char szResponse[AI_Response::MAX_RESPONSE_NAME];
  2241. // Only say the blinding speech if it's time to
  2242. if ( SpeakIfAllowed( "TLK_FLASHLIGHT_ILLUM", NULL, false, szResponse, AI_Response::MAX_RESPONSE_NAME ) )
  2243. {
  2244. m_iszCurrentBlindScene = AllocPooledString( szResponse );
  2245. ADD_DEBUG_HISTORY( HISTORY_ALYX_BLIND, UTIL_VarArgs( "(%0.2f) Alyx: start flare blind scene '%s'\n", gpGlobals->curtime,
  2246. STRING(m_iszCurrentBlindScene) ) );
  2247. GetShotRegulator()->DisableShooting();
  2248. m_bIsFlashlightBlind = true;
  2249. m_fStayBlindUntil = gpGlobals->curtime + 0.1f;
  2250. }
  2251. }
  2252. }
  2253. //-----------------------------------------------------------------------------
  2254. // Purpose:
  2255. // Input: bCheckLightSources - if true, checks if any light darkness lightsources are near
  2256. //-----------------------------------------------------------------------------
  2257. bool CNPC_Alyx::CanBeBlindedByFlashlight( bool bCheckLightSources )
  2258. {
  2259. // Can't be blinded if we're not in alyx darkness mode
  2260. /*
  2261. if ( !HL2GameRules()->IsAlyxInDarknessMode() )
  2262. return false;
  2263. */
  2264. // Can't be blinded if I'm in a script, or in combat
  2265. if ( IsInAScript() || GetState() == NPC_STATE_COMBAT || GetState() == NPC_STATE_SCRIPT )
  2266. return false;
  2267. if ( IsSpeaking() )
  2268. return false;
  2269. // can't be blinded if Alyx is near a light source
  2270. if ( bCheckLightSources && DarknessLightSourceWithinRadius( this, 500 ) )
  2271. return false;
  2272. // Not during an actbusy
  2273. if ( m_ActBusyBehavior.IsActive() )
  2274. return false;
  2275. if ( m_OperatorBehavior.IsRunning() )
  2276. return false;
  2277. // Can't be blinded if I've been in combat recently, to fix anim snaps
  2278. if ( GetLastEnemyTime() != 0.0 )
  2279. {
  2280. if ( (gpGlobals->curtime - GetLastEnemyTime()) < 2 )
  2281. return false;
  2282. }
  2283. // Can't be blinded if I'm reloading
  2284. if ( IsCurSchedule(SCHED_RELOAD, false) )
  2285. return false;
  2286. // Can't be blinded right after being blind, to prevent oscillation
  2287. if ( gpGlobals->curtime < m_flDontBlindUntil )
  2288. return false;
  2289. return true;
  2290. }
  2291. //-----------------------------------------------------------------------------
  2292. // Purpose:
  2293. // Input : *pPlayer -
  2294. // Output : Returns true on success, false on failure.
  2295. //-----------------------------------------------------------------------------
  2296. bool CNPC_Alyx::PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer )
  2297. {
  2298. Vector vecEyes, vecPlayerForward;
  2299. vecEyes = EyePosition();
  2300. pPlayer->EyeVectors( &vecPlayerForward );
  2301. Vector vecToEyes = (vecEyes - pPlayer->EyePosition());
  2302. float flDist = VectorNormalize( vecToEyes );
  2303. // We can be blinded in daylight, but only at close range
  2304. if ( HL2GameRules()->IsAlyxInDarknessMode() == false )
  2305. {
  2306. if ( flDist > (8*12.0f) )
  2307. return false;
  2308. }
  2309. float flDot = DotProduct( vecPlayerForward, vecToEyes );
  2310. if ( flDot < 0.98 )
  2311. return false;
  2312. // Check facing to ensure we're in front of her
  2313. Vector los = ( pPlayer->EyePosition() - vecEyes );
  2314. los.z = 0;
  2315. VectorNormalize( los );
  2316. Vector facingDir = EyeDirection2D();
  2317. flDot = DotProduct( los, facingDir );
  2318. return ( flDot > 0.3 );
  2319. }
  2320. //-----------------------------------------------------------------------------
  2321. // Purpose: Checks if Alyx is blinded by a flare
  2322. // Input :
  2323. // Output : Returns true on success, false on failure.
  2324. //-----------------------------------------------------------------------------
  2325. bool CNPC_Alyx::BlindedByFlare( void )
  2326. {
  2327. Vector vecEyes = EyePosition();
  2328. Vector los;
  2329. Vector vecToEyes;
  2330. Vector facingDir = EyeDirection2D();
  2331. // use a wider radius when she's already blind to help with edge cases
  2332. // where she flickers back and forth due to animation
  2333. float fBlindDist = ( m_bIsFlashlightBlind ) ? 35.0f : 30.0f;
  2334. CFlare *pFlare = CFlare::GetActiveFlares();
  2335. while( pFlare != NULL )
  2336. {
  2337. vecToEyes = (vecEyes - pFlare->GetAbsOrigin());
  2338. float fDist = VectorNormalize( vecToEyes );
  2339. if ( fDist < fBlindDist )
  2340. {
  2341. // Check facing to ensure we're in front of her
  2342. los = ( pFlare->GetAbsOrigin() - vecEyes );
  2343. los.z = 0;
  2344. VectorNormalize( los );
  2345. float flDot = DotProduct( los, facingDir );
  2346. if ( ( flDot > 0.3 ) && FVisible( pFlare ) )
  2347. {
  2348. return true;
  2349. }
  2350. }
  2351. pFlare = pFlare->GetNextFlare();
  2352. }
  2353. return false;
  2354. }
  2355. //-----------------------------------------------------------------------------
  2356. // Purpose:
  2357. // Output : Returns true on success, false on failure.
  2358. //-----------------------------------------------------------------------------
  2359. bool CNPC_Alyx::CanReload( void )
  2360. {
  2361. if ( m_bIsFlashlightBlind )
  2362. return false;
  2363. return BaseClass::CanReload();
  2364. }
  2365. //-----------------------------------------------------------------------------
  2366. //-----------------------------------------------------------------------------
  2367. bool CNPC_Alyx::PickTacticalLookTarget( AILookTargetArgs_t *pArgs )
  2368. {
  2369. if( HasInteractTarget() )
  2370. {
  2371. pArgs->hTarget = GetInteractTarget();
  2372. pArgs->flInfluence = 0.8f;
  2373. pArgs->flDuration = 3.0f;
  2374. return true;
  2375. }
  2376. if( m_ActBusyBehavior.IsActive() && m_ActBusyBehavior.IsCombatActBusy() )
  2377. {
  2378. return false;
  2379. }
  2380. return BaseClass::PickTacticalLookTarget( pArgs );
  2381. }
  2382. //-----------------------------------------------------------------------------
  2383. // Purpose:
  2384. //-----------------------------------------------------------------------------
  2385. void CNPC_Alyx::OnSelectedLookTarget( AILookTargetArgs_t *pArgs )
  2386. {
  2387. if ( pArgs && pArgs->hTarget )
  2388. {
  2389. // If it's a stealth target, we want to go into stealth mode
  2390. CAI_Hint *pHint = dynamic_cast<CAI_Hint *>(pArgs->hTarget.Get());
  2391. if ( pHint && pHint->HintType() == HINT_WORLD_VISUALLY_INTERESTING_STEALTH )
  2392. {
  2393. SetReadinessLevel( AIRL_STEALTH, true, true );
  2394. pArgs->flDuration = 9999999;
  2395. m_hStealthLookTarget = pHint;
  2396. return;
  2397. }
  2398. }
  2399. // If we're in stealth mode, break out now
  2400. if ( GetReadinessLevel() == AIRL_STEALTH )
  2401. {
  2402. SetReadinessLevel( AIRL_STIMULATED, true, true );
  2403. if ( m_hStealthLookTarget )
  2404. {
  2405. ClearLookTarget( m_hStealthLookTarget );
  2406. m_hStealthLookTarget = NULL;
  2407. }
  2408. }
  2409. }
  2410. //-----------------------------------------------------------------------------
  2411. // Output : Behavior to use
  2412. //-----------------------------------------------------------------------------
  2413. CAI_FollowBehavior &CNPC_Alyx::GetFollowBehavior( void )
  2414. {
  2415. // Use the base class
  2416. return m_FollowBehavior;
  2417. }
  2418. //-----------------------------------------------------------------------------
  2419. // Purpose:
  2420. //-----------------------------------------------------------------------------
  2421. void CNPC_Alyx::AimGun( void )
  2422. {
  2423. if (m_FuncTankBehavior.IsMounted())
  2424. {
  2425. m_FuncTankBehavior.AimGun();
  2426. return;
  2427. }
  2428. // Always allow the passenger behavior to handle this
  2429. if ( m_PassengerBehavior.IsEnabled() )
  2430. {
  2431. m_PassengerBehavior.AimGun();
  2432. return;
  2433. }
  2434. if( !GetEnemy() )
  2435. {
  2436. if ( GetReadinessLevel() == AIRL_STEALTH && m_hStealthLookTarget != NULL )
  2437. {
  2438. // Only aim if we're not far from the node
  2439. Vector vecAimDir = m_hStealthLookTarget->GetAbsOrigin() - Weapon_ShootPosition();
  2440. if ( VectorNormalize( vecAimDir ) > 80 )
  2441. {
  2442. // Ignore nodes that are behind her
  2443. Vector vecForward;
  2444. GetVectors( &vecForward, NULL, NULL );
  2445. float flDot = DotProduct( vecAimDir, vecForward );
  2446. if ( flDot > 0 )
  2447. {
  2448. SetAim( vecAimDir);
  2449. return;
  2450. }
  2451. }
  2452. }
  2453. }
  2454. BaseClass::AimGun();
  2455. }
  2456. //-----------------------------------------------------------------------------
  2457. //-----------------------------------------------------------------------------
  2458. Vector CNPC_Alyx::GetActualShootPosition( const Vector &shootOrigin )
  2459. {
  2460. if( HasShotgun() && GetEnemy() && GetEnemy()->Classify() == CLASS_ZOMBIE && random->RandomInt( 0, 1 ) == 1 )
  2461. {
  2462. // 50-50 zombie headshots with shotgun!
  2463. return GetEnemy()->HeadTarget( shootOrigin );
  2464. }
  2465. return BaseClass::GetActualShootPosition( shootOrigin );
  2466. }
  2467. //-----------------------------------------------------------------------------
  2468. // Purpose:
  2469. //-----------------------------------------------------------------------------
  2470. bool CNPC_Alyx::EnemyIsValidCrouchTarget( CBaseEntity *pEnemy )
  2471. {
  2472. // Don't crouch to shoot flying enemies (or jumping antlions)
  2473. if ( !(pEnemy->GetFlags() & FL_ONGROUND) )
  2474. return false;
  2475. // Don't crouch to shoot if we couldn't see them while crouching
  2476. if ( !CouldShootIfCrouching( pEnemy ) )
  2477. {
  2478. //Warning("CROUCH: Not valid due to crouch-no-LOS.\n" );
  2479. return false;
  2480. }
  2481. // Don't crouch to shoot enemies that are close to me
  2482. if ( EnemyDistance( pEnemy ) <= ALYX_MIN_ENEMY_DIST_TO_CROUCH )
  2483. {
  2484. //Warning("CROUCH: Not valid due to enemy-too-close.\n" );
  2485. return false;
  2486. }
  2487. // Don't crouch to shoot enemies that are too far off my vertical plane
  2488. if ( fabs( pEnemy->GetAbsOrigin().z - GetAbsOrigin().z ) > 64 )
  2489. return false;
  2490. return true;
  2491. }
  2492. //-----------------------------------------------------------------------------
  2493. // Purpose: degrees to turn in 0.1 seconds
  2494. //-----------------------------------------------------------------------------
  2495. float CNPC_Alyx::MaxYawSpeed( void )
  2496. {
  2497. if ( IsCrouching() )
  2498. return 10;
  2499. return BaseClass::MaxYawSpeed();
  2500. }
  2501. //-----------------------------------------------------------------------------
  2502. // Purpose:
  2503. //-----------------------------------------------------------------------------
  2504. bool CNPC_Alyx::Stand( void )
  2505. {
  2506. bool bWasCrouching = IsCrouching();
  2507. if ( !BaseClass::Stand() )
  2508. return false;
  2509. if ( bWasCrouching )
  2510. {
  2511. m_flNextCrouchTime = gpGlobals->curtime + ALYX_CROUCH_DELAY;
  2512. OnUpdateShotRegulator();
  2513. }
  2514. return true;
  2515. }
  2516. //-----------------------------------------------------------------------------
  2517. // Purpose:
  2518. //-----------------------------------------------------------------------------
  2519. bool CNPC_Alyx::Crouch( void )
  2520. {
  2521. if ( !npc_alyx_crouch.GetBool() )
  2522. return false;
  2523. // Alyx will ignore crouch requests while she has the shotgun
  2524. if ( HasShotgun() )
  2525. return false;
  2526. bool bWasStanding = !IsCrouching();
  2527. if ( !BaseClass::Crouch() )
  2528. return false;
  2529. if ( bWasStanding )
  2530. {
  2531. OnUpdateShotRegulator();
  2532. }
  2533. return true;
  2534. }
  2535. //-----------------------------------------------------------------------------
  2536. // Purpose:
  2537. //-----------------------------------------------------------------------------
  2538. void CNPC_Alyx::DesireCrouch( void )
  2539. {
  2540. // Ignore crouch desire if we've been crouching recently to reduce oscillation
  2541. if ( m_flNextCrouchTime > gpGlobals->curtime )
  2542. return;
  2543. BaseClass::DesireCrouch();
  2544. }
  2545. //-----------------------------------------------------------------------------
  2546. // Purpose: Tack on extra criteria for responses
  2547. //-----------------------------------------------------------------------------
  2548. void CNPC_Alyx::ModifyOrAppendCriteria( AI_CriteriaSet &set )
  2549. {
  2550. AIEnemiesIter_t iter;
  2551. float fLengthOfLastCombat;
  2552. int iNumEnemies;
  2553. if ( GetState() == NPC_STATE_COMBAT )
  2554. {
  2555. fLengthOfLastCombat = gpGlobals->curtime - m_fCombatStartTime;
  2556. }
  2557. else
  2558. {
  2559. fLengthOfLastCombat = m_fCombatEndTime - m_fCombatStartTime;
  2560. }
  2561. set.AppendCriteria( "combat_length", UTIL_VarArgs( "%.3f", fLengthOfLastCombat ) );
  2562. iNumEnemies = 0;
  2563. for ( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  2564. {
  2565. if ( pEMemory->hEnemy->IsAlive() && ( pEMemory->hEnemy->Classify() != CLASS_BULLSEYE ) )
  2566. {
  2567. iNumEnemies++;
  2568. }
  2569. }
  2570. set.AppendCriteria( "num_enemies", UTIL_VarArgs( "%d", iNumEnemies ) );
  2571. set.AppendCriteria( "darkness_mode", UTIL_VarArgs( "%d", HasCondition( COND_ALYX_IN_DARK ) ) );
  2572. set.AppendCriteria( "water_level", UTIL_VarArgs( "%d", GetWaterLevel() ) );
  2573. CHL2_Player *pPlayer = assert_cast<CHL2_Player*>( UTIL_PlayerByIndex( 1 ) );
  2574. set.AppendCriteria( "num_companions", UTIL_VarArgs( "%d", pPlayer ? pPlayer->GetNumSquadCommandables() : 0 ) );
  2575. set.AppendCriteria( "flashlight_on", UTIL_VarArgs( "%d", pPlayer ? pPlayer->FlashlightIsOn() : 0 ) );
  2576. BaseClass::ModifyOrAppendCriteria( set );
  2577. }
  2578. //-----------------------------------------------------------------------------
  2579. // Purpose: Turn off Alyx's readiness when she's around a vehicle
  2580. //-----------------------------------------------------------------------------
  2581. bool CNPC_Alyx::IsReadinessCapable( void )
  2582. {
  2583. // Let the convar decide
  2584. return npc_alyx_readiness.GetBool();;
  2585. }
  2586. //-----------------------------------------------------------------------------
  2587. //-----------------------------------------------------------------------------
  2588. bool CNPC_Alyx::IsAllowedToInteract()
  2589. {
  2590. if ( RunningPassengerBehavior() )
  2591. return false;
  2592. if( IsInAScript() )
  2593. return false;
  2594. if( IsCurSchedule(SCHED_SCENE_GENERIC) )
  2595. return false;
  2596. if( GetEnemy() )
  2597. {
  2598. if( GetEnemy()->GetAbsOrigin().DistTo( GetAbsOrigin() ) <= 240.0f )
  2599. {
  2600. // Enemy is nearby!
  2601. return false;
  2602. }
  2603. }
  2604. return m_bInteractionAllowed;
  2605. }
  2606. //-----------------------------------------------------------------------------
  2607. // Purpose: Allows the NPC to react to being given a weapon
  2608. // Input : *pNewWeapon - Weapon given
  2609. //-----------------------------------------------------------------------------
  2610. void CNPC_Alyx::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon )
  2611. {
  2612. m_WeaponType = ComputeWeaponType();
  2613. BaseClass::OnChangeActiveWeapon( pOldWeapon, pNewWeapon );
  2614. }
  2615. //-----------------------------------------------------------------------------
  2616. // Purpose: Allows the NPC to react to being given a weapon
  2617. // Input : *pNewWeapon - Weapon given
  2618. //-----------------------------------------------------------------------------
  2619. void CNPC_Alyx::OnGivenWeapon( CBaseCombatWeapon *pNewWeapon )
  2620. {
  2621. // HACK: This causes Alyx to pull her gun from a holstered position
  2622. if ( pNewWeapon->ClassMatches( CLASSNAME_ALYXGUN ) )
  2623. {
  2624. // Put it away so we can pull it out properly
  2625. GetActiveWeapon()->Holster();
  2626. SetActiveWeapon( NULL );
  2627. // Draw the weapon when we're next able to
  2628. SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED );
  2629. }
  2630. }
  2631. //-----------------------------------------------------------------------------
  2632. //-----------------------------------------------------------------------------
  2633. void CNPC_Alyx::Weapon_Equip( CBaseCombatWeapon *pWeapon )
  2634. {
  2635. m_WeaponType = ComputeWeaponType( pWeapon );
  2636. BaseClass::Weapon_Equip( pWeapon );
  2637. }
  2638. //-----------------------------------------------------------------------------
  2639. //-----------------------------------------------------------------------------
  2640. bool CNPC_Alyx::Weapon_CanUse( CBaseCombatWeapon *pWeapon )
  2641. {
  2642. if( !pWeapon->ClassMatches( CLASSNAME_SHOTGUN ) )
  2643. return false;
  2644. return BaseClass::Weapon_CanUse( pWeapon );
  2645. }
  2646. //-----------------------------------------------------------------------------
  2647. // Purpose:
  2648. // Input : -
  2649. //-----------------------------------------------------------------------------
  2650. void CNPC_Alyx::OnUpdateShotRegulator( )
  2651. {
  2652. BaseClass::OnUpdateShotRegulator();
  2653. if ( !HasShotgun() && IsCrouching() )
  2654. {
  2655. // While crouching, Alyx fires longer bursts
  2656. int iMinBurst, iMaxBurst;
  2657. GetShotRegulator()->GetBurstShotCountRange( &iMinBurst, &iMaxBurst );
  2658. GetShotRegulator()->SetBurstShotCountRange( iMinBurst * 2, iMaxBurst * 2 );
  2659. }
  2660. }
  2661. //-----------------------------------------------------------------------------
  2662. //-----------------------------------------------------------------------------
  2663. void CNPC_Alyx::BarnacleDeathSound( void )
  2664. {
  2665. Speak( TLK_SELF_IN_BARNACLE );
  2666. }
  2667. //-----------------------------------------------------------------------------
  2668. // Purpose:
  2669. // Output : PassengerState_e
  2670. //-----------------------------------------------------------------------------
  2671. PassengerState_e CNPC_Alyx::GetPassengerState( void )
  2672. {
  2673. return m_PassengerBehavior.GetPassengerState();
  2674. }
  2675. //-----------------------------------------------------------------------------
  2676. //-----------------------------------------------------------------------------
  2677. void CNPC_Alyx::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  2678. {
  2679. // if I'm in the vehicle, the player is probably trying to use the vehicle
  2680. if ( GetPassengerState() == PASSENGER_STATE_INSIDE && pActivator->IsPlayer() && GetParent() )
  2681. {
  2682. GetParent()->Use( pActivator, pCaller, useType, value );
  2683. return;
  2684. }
  2685. m_bDontUseSemaphore = true;
  2686. SpeakIfAllowed( TLK_USE );
  2687. m_bDontUseSemaphore = false;
  2688. m_OnPlayerUse.FireOutput( pActivator, pCaller );
  2689. }
  2690. //-----------------------------------------------------------------------------
  2691. // Purpose:
  2692. //-----------------------------------------------------------------------------
  2693. bool CNPC_Alyx::PlayerInSpread( const Vector &sourcePos, const Vector &targetPos, float flSpread, float maxDistOffCenter, bool ignoreHatedPlayers )
  2694. {
  2695. // loop through all players
  2696. for (int i = 1; i <= gpGlobals->maxClients; i++ )
  2697. {
  2698. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  2699. if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) != D_HT ) )
  2700. {
  2701. //If the player is being lifted by a barnacle then go ahead and ignore the player and shoot.
  2702. #ifdef HL2_EPISODIC
  2703. if ( pPlayer->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
  2704. return false;
  2705. #endif
  2706. if ( PointInSpread( pPlayer, sourcePos, targetPos, pPlayer->WorldSpaceCenter(), flSpread, maxDistOffCenter ) )
  2707. return true;
  2708. }
  2709. }
  2710. return false;
  2711. }
  2712. //-----------------------------------------------------------------------------
  2713. // Purpose:
  2714. //-----------------------------------------------------------------------------
  2715. bool CNPC_Alyx::IsCrouchedActivity( Activity activity )
  2716. {
  2717. Activity realActivity = TranslateActivity(activity);
  2718. switch ( realActivity )
  2719. {
  2720. case ACT_RELOAD_LOW:
  2721. case ACT_COVER_LOW:
  2722. case ACT_COVER_PISTOL_LOW:
  2723. case ACT_COVER_SMG1_LOW:
  2724. case ACT_RELOAD_SMG1_LOW:
  2725. // Aren't these supposed to be a little higher than the above?
  2726. case ACT_RANGE_ATTACK1_LOW:
  2727. case ACT_RANGE_ATTACK2_LOW:
  2728. case ACT_RANGE_ATTACK_AR2_LOW:
  2729. case ACT_RANGE_ATTACK_SMG1_LOW:
  2730. case ACT_RANGE_ATTACK_SHOTGUN_LOW:
  2731. case ACT_RANGE_ATTACK_PISTOL_LOW:
  2732. case ACT_RANGE_AIM_LOW:
  2733. case ACT_RANGE_AIM_SMG1_LOW:
  2734. case ACT_RANGE_AIM_PISTOL_LOW:
  2735. case ACT_RANGE_AIM_AR2_LOW:
  2736. return true;
  2737. }
  2738. return false;
  2739. }
  2740. //-----------------------------------------------------------------------------
  2741. // Purpose:
  2742. //-----------------------------------------------------------------------------
  2743. bool CNPC_Alyx::OnBeginMoveAndShoot()
  2744. {
  2745. if ( BaseClass::OnBeginMoveAndShoot() )
  2746. {
  2747. SpeakAttacking();
  2748. return true;
  2749. }
  2750. return false;
  2751. }
  2752. //-----------------------------------------------------------------------------
  2753. // Purpose:
  2754. //-----------------------------------------------------------------------------
  2755. void CNPC_Alyx::SpeakAttacking( void )
  2756. {
  2757. if ( GetActiveWeapon() && m_AnnounceAttackTimer.Expired() )
  2758. {
  2759. SpeakIfAllowed( TLK_ATTACKING, UTIL_VarArgs("attacking_with_weapon:%s", GetActiveWeapon()->GetClassname()) );
  2760. m_AnnounceAttackTimer.Set( 3, 5 );
  2761. }
  2762. }
  2763. //-----------------------------------------------------------------------------
  2764. // Purpose:
  2765. // Input : *lpszInteractionName -
  2766. // *pOther -
  2767. // Output : Returns true on success, false on failure.
  2768. //-----------------------------------------------------------------------------
  2769. bool CNPC_Alyx::ForceVehicleInteraction( const char *lpszInteractionName, CBaseCombatCharacter *pOther )
  2770. {
  2771. return m_PassengerBehavior.ForceVehicleInteraction( lpszInteractionName, pOther );
  2772. }
  2773. //-----------------------------------------------------------------------------
  2774. //-----------------------------------------------------------------------------
  2775. CNPC_Alyx::WeaponType_t CNPC_Alyx::ComputeWeaponType( CBaseEntity *pWeapon )
  2776. {
  2777. if ( !pWeapon )
  2778. {
  2779. pWeapon = GetActiveWeapon();
  2780. }
  2781. if ( !pWeapon )
  2782. {
  2783. return WT_NONE;
  2784. }
  2785. if ( pWeapon->ClassMatches( CLASSNAME_ALYXGUN ) )
  2786. {
  2787. return WT_ALYXGUN;
  2788. }
  2789. if ( pWeapon->ClassMatches( CLASSNAME_SMG1 ) )
  2790. {
  2791. return WT_SMG1;
  2792. }
  2793. if ( pWeapon->ClassMatches( CLASSNAME_SHOTGUN ) )
  2794. {
  2795. return WT_SHOTGUN;
  2796. }
  2797. if ( pWeapon->ClassMatches( CLASSNAME_AR2 ) )
  2798. {
  2799. return WT_AR2;
  2800. }
  2801. return WT_OTHER;
  2802. }
  2803. //-----------------------------------------------------------------------------
  2804. // Purpose: Complain about being punted
  2805. //-----------------------------------------------------------------------------
  2806. void CNPC_Alyx::InputVehiclePunted( inputdata_t &inputdata )
  2807. {
  2808. // If we're in a vehicle, complain about being punted
  2809. if ( IsInAVehicle() && GetVehicleEntity() == inputdata.pCaller )
  2810. {
  2811. // FIXME: Pass this up into the behavior?
  2812. SpeakIfAllowed( TLK_PASSENGER_PUNTED );
  2813. }
  2814. }
  2815. //-----------------------------------------------------------------------------
  2816. // Purpose:
  2817. // Input : &inputdata -
  2818. //-----------------------------------------------------------------------------
  2819. void CNPC_Alyx::InputOutsideTransition( inputdata_t &inputdata )
  2820. {
  2821. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  2822. if ( pPlayer && pPlayer->IsInAVehicle() )
  2823. {
  2824. if ( ShouldAlwaysTransition() == false )
  2825. return;
  2826. // Enter immediately
  2827. EnterVehicle( pPlayer->GetVehicleEntity(), true );
  2828. return;
  2829. }
  2830. // If the player is in the vehicle and we're not, then we need to enter the vehicle immediately
  2831. BaseClass::InputOutsideTransition( inputdata );
  2832. }
  2833. //=========================================================
  2834. // AI Schedules Specific to this NPC
  2835. //=========================================================
  2836. AI_BEGIN_CUSTOM_NPC( npc_alyx, CNPC_Alyx )
  2837. DECLARE_TASK( TASK_ALYX_BEGIN_INTERACTION )
  2838. DECLARE_TASK( TASK_ALYX_COMPLETE_INTERACTION )
  2839. DECLARE_TASK( TASK_ALYX_ANNOUNCE_HACK )
  2840. DECLARE_TASK( TASK_ALYX_GET_PATH_TO_INTERACT_TARGET )
  2841. DECLARE_TASK( TASK_ALYX_WAIT_HACKING )
  2842. DECLARE_TASK( TASK_ALYX_DRAW_PISTOL )
  2843. DECLARE_TASK( TASK_ALYX_HOLSTER_PISTOL )
  2844. DECLARE_TASK( TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL )
  2845. DECLARE_TASK( TASK_ALYX_BUILD_COMBAT_FACE_PATH )
  2846. DECLARE_TASK( TASK_ALYX_SET_IDLE_ACTIVITY )
  2847. DECLARE_TASK( TASK_ALYX_FALL_TO_GROUND )
  2848. DECLARE_ANIMEVENT( AE_ALYX_EMPTOOL_ATTACHMENT )
  2849. DECLARE_ANIMEVENT( AE_ALYX_EMPTOOL_SEQUENCE )
  2850. DECLARE_ANIMEVENT( AE_ALYX_EMPTOOL_USE )
  2851. DECLARE_ANIMEVENT( COMBINE_AE_BEGIN_ALTFIRE )
  2852. DECLARE_ANIMEVENT( COMBINE_AE_ALTFIRE )
  2853. DECLARE_CONDITION( COND_ALYX_HAS_INTERACT_TARGET )
  2854. DECLARE_CONDITION( COND_ALYX_NO_INTERACT_TARGET )
  2855. DECLARE_CONDITION( COND_ALYX_CAN_INTERACT_WITH_TARGET )
  2856. DECLARE_CONDITION( COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET )
  2857. DECLARE_CONDITION( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT )
  2858. DECLARE_CONDITION( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT )
  2859. DECLARE_CONDITION( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED )
  2860. DECLARE_CONDITION( COND_ALYX_IN_DARK )
  2861. DECLARE_ACTIVITY( ACT_ALYX_DRAW_TOOL )
  2862. DECLARE_ACTIVITY( ACT_ALYX_IDLE_TOOL )
  2863. DECLARE_ACTIVITY( ACT_ALYX_ZAP_TOOL )
  2864. DECLARE_ACTIVITY( ACT_ALYX_HOLSTER_TOOL )
  2865. DECLARE_ACTIVITY( ACT_ALYX_PICKUP_RACK )
  2866. DEFINE_SCHEDULE
  2867. (
  2868. SCHED_ALYX_PREPARE_TO_INTERACT_WITH_TARGET,
  2869. " Tasks"
  2870. " TASK_STOP_MOVING 0"
  2871. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALYX_DRAW_TOOL"
  2872. " TASK_SET_ACTIVITY ACTIVITY:ACT_ALYX_IDLE_TOOL"
  2873. " TASK_FACE_PLAYER 0"
  2874. ""
  2875. " Interrupts"
  2876. ""
  2877. )
  2878. DEFINE_SCHEDULE
  2879. (
  2880. SCHED_ALYX_WAIT_TO_INTERACT_WITH_TARGET,
  2881. " Tasks"
  2882. " TASK_STOP_MOVING 0"
  2883. " TASK_ALYX_ANNOUNCE_HACK 0"
  2884. " TASK_FACE_PLAYER 0"
  2885. " TASK_SET_ACTIVITY ACTIVITY:ACT_ALYX_IDLE_TOOL"
  2886. " TASK_WAIT 2"
  2887. ""
  2888. " Interrupts"
  2889. " COND_ALYX_CAN_INTERACT_WITH_TARGET"
  2890. " COND_ALYX_NO_INTERACT_TARGET"
  2891. " COND_LIGHT_DAMAGE"
  2892. " COND_HEAVY_DAMAGE"
  2893. )
  2894. DEFINE_SCHEDULE
  2895. (
  2896. SCHED_ALYX_INTERACT_WITH_TARGET,
  2897. " Tasks"
  2898. " TASK_STOP_MOVING 0"
  2899. " TASK_FACE_PLAYER 0"
  2900. " TASK_ALYX_BEGIN_INTERACTION 0"
  2901. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALYX_ZAP_TOOL"
  2902. " TASK_SET_SCHEDULE SCHEDULE:SCHED_ALYX_FINISH_INTERACTING_WITH_TARGET"
  2903. ""
  2904. " Interrupts"
  2905. " COND_ALYX_NO_INTERACT_TARGET"
  2906. " COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET"
  2907. )
  2908. DEFINE_SCHEDULE
  2909. (
  2910. SCHED_ALYX_FINISH_INTERACTING_WITH_TARGET,
  2911. " Tasks"
  2912. " TASK_ALYX_COMPLETE_INTERACTION 0"
  2913. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALYX_HOLSTER_TOOL"
  2914. ""
  2915. " Interrupts"
  2916. ""
  2917. )
  2918. DEFINE_SCHEDULE
  2919. (
  2920. SCHED_ALYX_HOLSTER_EMP,
  2921. " Tasks"
  2922. " TASK_STOP_MOVING 0"
  2923. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALYX_HOLSTER_TOOL"
  2924. " TASK_ALYX_DRAW_PISTOL 0"
  2925. ""
  2926. " Interrupts"
  2927. ""
  2928. )
  2929. DEFINE_SCHEDULE
  2930. (
  2931. SCHED_ALYX_INTERACTION_INTERRUPTED,
  2932. " Tasks"
  2933. " TASK_STOP_MOVING 0"
  2934. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  2935. " TASK_FACE_PLAYER 0"
  2936. " TASK_WAIT 2"
  2937. ""
  2938. " Interrupts"
  2939. )
  2940. DEFINE_SCHEDULE
  2941. (
  2942. SCHED_ALYX_ALERT_FACE_AWAYFROM_BESTSOUND,
  2943. " Tasks"
  2944. " TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0"
  2945. " TASK_STOP_MOVING 0"
  2946. " TASK_FACE_AWAY_FROM_SAVEPOSITION 0"
  2947. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  2948. " TASK_WAIT 10.0"
  2949. " TASK_FACE_REASONABLE 0"
  2950. ""
  2951. " Interrupts"
  2952. " COND_NEW_ENEMY"
  2953. " COND_SEE_FEAR"
  2954. " COND_LIGHT_DAMAGE"
  2955. " COND_HEAVY_DAMAGE"
  2956. " COND_PROVOKED"
  2957. )
  2958. //===============================================
  2959. // > RangeAttack1
  2960. //===============================================
  2961. DEFINE_SCHEDULE
  2962. (
  2963. SCHED_ALYX_RANGE_ATTACK1,
  2964. " Tasks"
  2965. " TASK_STOP_MOVING 0"
  2966. " TASK_FACE_ENEMY 0"
  2967. " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
  2968. " TASK_RANGE_ATTACK1 0"
  2969. ""
  2970. " Interrupts"
  2971. " COND_ENEMY_WENT_NULL"
  2972. " COND_HEAVY_DAMAGE"
  2973. " COND_ENEMY_OCCLUDED"
  2974. " COND_NO_PRIMARY_AMMO"
  2975. " COND_HEAR_DANGER"
  2976. " COND_WEAPON_BLOCKED_BY_FRIEND"
  2977. " COND_WEAPON_SIGHT_OCCLUDED"
  2978. )
  2979. //===============================================
  2980. // > SCHED_ALYX_ALERT_REACT_TO_COMBAT_SOUND
  2981. //===============================================
  2982. DEFINE_SCHEDULE
  2983. (
  2984. SCHED_ALYX_ALERT_REACT_TO_COMBAT_SOUND,
  2985. " Tasks"
  2986. " TASK_REACT_TO_COMBAT_SOUND 0"
  2987. " TASK_SET_SCHEDULE SCHEDULE:SCHED_ALERT_FACE_BESTSOUND"
  2988. ""
  2989. " Interrupts"
  2990. " COND_NEW_ENEMY"
  2991. )
  2992. //=========================================================
  2993. // > SCHED_ALYX_COMBAT_FACE
  2994. //=========================================================
  2995. DEFINE_SCHEDULE
  2996. (
  2997. SCHED_ALYX_COMBAT_FACE,
  2998. " Tasks"
  2999. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
  3000. " TASK_STOP_MOVING 0"
  3001. " TASK_ALYX_BUILD_COMBAT_FACE_PATH 0"
  3002. " TASK_RUN_PATH 0"
  3003. " TASK_FACE_IDEAL 0"
  3004. " TASK_WAIT_FOR_MOVEMENT 0"
  3005. ""
  3006. " Interrupts"
  3007. " COND_CAN_RANGE_ATTACK1"
  3008. " COND_CAN_RANGE_ATTACK2"
  3009. " COND_CAN_MELEE_ATTACK1"
  3010. " COND_CAN_MELEE_ATTACK2"
  3011. " COND_NEW_ENEMY"
  3012. " COND_ENEMY_DEAD"
  3013. )
  3014. //=========================================================
  3015. // > SCHED_ALYX_WAKE_ANGRY
  3016. //=========================================================
  3017. DEFINE_SCHEDULE
  3018. (
  3019. SCHED_ALYX_WAKE_ANGRY,
  3020. " Tasks"
  3021. " TASK_STOP_MOVING 0"
  3022. " TASK_SOUND_WAKE 0"
  3023. ""
  3024. " Interrupts"
  3025. )
  3026. //===============================================
  3027. // > NewWeapon
  3028. //===============================================
  3029. DEFINE_SCHEDULE
  3030. (
  3031. SCHED_ALYX_NEW_WEAPON,
  3032. " Tasks"
  3033. " TASK_STOP_MOVING 0"
  3034. " TASK_SET_TOLERANCE_DISTANCE 5"
  3035. " TASK_GET_PATH_TO_TARGET_WEAPON 0"
  3036. " TASK_WEAPON_RUN_PATH 0"
  3037. " TASK_STOP_MOVING 0"
  3038. " TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL 0"
  3039. " TASK_FACE_TARGET 0"
  3040. " TASK_WEAPON_PICKUP 0"
  3041. " TASK_WAIT 1"// Don't move before done standing up
  3042. ""
  3043. " Interrupts"
  3044. )
  3045. //===============================================
  3046. // > Alyx_Idle_Stand
  3047. //===============================================
  3048. DEFINE_SCHEDULE
  3049. (
  3050. SCHED_ALYX_IDLE_STAND,
  3051. " Tasks"
  3052. " TASK_STOP_MOVING 0"
  3053. " TASK_ALYX_SET_IDLE_ACTIVITY ACTIVITY:ACT_IDLE"
  3054. " TASK_WAIT 5"
  3055. " TASK_WAIT_PVS 0"
  3056. ""
  3057. " Interrupts"
  3058. " COND_NEW_ENEMY"
  3059. " COND_SEE_FEAR"
  3060. " COND_LIGHT_DAMAGE"
  3061. " COND_HEAVY_DAMAGE"
  3062. " COND_SMELL"
  3063. " COND_PROVOKED"
  3064. " COND_GIVE_WAY"
  3065. " COND_HEAR_PLAYER"
  3066. " COND_HEAR_DANGER"
  3067. " COND_HEAR_COMBAT"
  3068. " COND_HEAR_BULLET_IMPACT"
  3069. " COND_IDLE_INTERRUPT"
  3070. )
  3071. //===============================================
  3072. // Makes Alyx die if she falls too long
  3073. //===============================================
  3074. DEFINE_SCHEDULE
  3075. (
  3076. SCHED_ALYX_FALL_TO_GROUND,
  3077. " Tasks"
  3078. " TASK_ALYX_FALL_TO_GROUND 0"
  3079. ""
  3080. " Interrupts"
  3081. )
  3082. DEFINE_SCHEDULE
  3083. (
  3084. SCHED_ALYX_ALERT_FACE_BESTSOUND,
  3085. " Tasks"
  3086. " TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0"
  3087. " TASK_STOP_MOVING 0"
  3088. " TASK_FACE_SAVEPOSITION 0"
  3089. ""
  3090. " Interrupts"
  3091. " COND_NEW_ENEMY"
  3092. " COND_SEE_FEAR"
  3093. " COND_LIGHT_DAMAGE"
  3094. " COND_HEAVY_DAMAGE"
  3095. " COND_PROVOKED"
  3096. );
  3097. AI_END_CUSTOM_NPC()