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.

1145 lines
32 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: A hideous, putrescent, pus-filled undead carcass atop which a vile
  4. // nest of filthy poisonous headcrabs lurks.
  5. //
  6. // Anyway, this guy has two range attacks: at short range, headcrabs
  7. // will leap from the nest to attack. At long range he will wrench a
  8. // headcrab from his back to throw it at his enemy.
  9. //
  10. //=============================================================================//
  11. #include "cbase.h"
  12. #include "ai_basenpc.h"
  13. #include "ai_default.h"
  14. #include "ai_schedule.h"
  15. #include "ai_hull.h"
  16. #include "ai_motor.h"
  17. #include "game.h"
  18. #include "npc_headcrab.h"
  19. #include "npcevent.h"
  20. #include "entitylist.h"
  21. #include "ai_task.h"
  22. #include "activitylist.h"
  23. #include "engine/IEngineSound.h"
  24. #include "npc_BaseZombie.h"
  25. // memdbgon must be the last include file in a .cpp file!!!
  26. #include "tier0/memdbgon.h"
  27. #define BREATH_VOL_MAX 0.6
  28. //
  29. // Controls how soon he throws the first headcrab after seeing his enemy (also when the first headcrab leaps off)
  30. //
  31. #define ZOMBIE_THROW_FIRST_MIN_DELAY 1 // min seconds before first crab throw
  32. #define ZOMBIE_THROW_FIRST_MAX_DELAY 2 // max seconds before first crab throw
  33. //
  34. // Controls how often he throws headcrabs (also how often headcrabs leap off)
  35. //
  36. #define ZOMBIE_THROW_MIN_DELAY 4 // min seconds between crab throws
  37. #define ZOMBIE_THROW_MAX_DELAY 10 // max seconds between crab throws
  38. //
  39. // Ranges for throwing headcrabs.
  40. //
  41. #define ZOMBIE_THROW_RANGE_MIN 250
  42. #define ZOMBIE_THROW_RANGE_MAX 800
  43. #define ZOMBIE_THROW_CONE 0.6
  44. //
  45. // Ranges for headcrabs leaping off.
  46. //
  47. #define ZOMBIE_HC_LEAP_RANGE_MIN 12
  48. #define ZOMBIE_HC_LEAP_RANGE_MAX 256
  49. #define ZOMBIE_HC_LEAP_CONE 0.6
  50. #define ZOMBIE_BODYGROUP_NEST_BASE 2 // First nest crab, +2 more
  51. #define ZOMBIE_BODYGROUP_THROW 5 // The crab in our hand for throwing
  52. #define ZOMBIE_ENEMY_BREATHE_DIST 300 // How close we must be to our enemy before we start breathing hard.
  53. envelopePoint_t envPoisonZombieMoanVolumeFast[] =
  54. {
  55. { 1.0f, 1.0f,
  56. 0.1f, 0.1f,
  57. },
  58. { 0.0f, 0.0f,
  59. 0.2f, 0.3f,
  60. },
  61. };
  62. //
  63. // Turns the breathing off for a second, then back on.
  64. //
  65. envelopePoint_t envPoisonZombieBreatheVolumeOffShort[] =
  66. {
  67. { 0.0f, 0.0f,
  68. 0.1f, 0.1f,
  69. },
  70. { 0.0f, 0.0f,
  71. 2.0f, 2.0f,
  72. },
  73. { BREATH_VOL_MAX, BREATH_VOL_MAX,
  74. 1.0f, 1.0f,
  75. },
  76. };
  77. //
  78. // Custom schedules.
  79. //
  80. enum
  81. {
  82. SCHED_ZOMBIE_POISON_RANGE_ATTACK2 = LAST_BASE_ZOMBIE_SCHEDULE,
  83. SCHED_ZOMBIE_POISON_RANGE_ATTACK1,
  84. };
  85. //-----------------------------------------------------------------------------
  86. // The maximum number of headcrabs we can have riding on our back.
  87. // NOTE: If you change this value you must also change the lookup table in Spawn!
  88. //-----------------------------------------------------------------------------
  89. #define MAX_CRABS 3
  90. int AE_ZOMBIE_POISON_THROW_WARN_SOUND;
  91. int AE_ZOMBIE_POISON_PICKUP_CRAB;
  92. int AE_ZOMBIE_POISON_THROW_SOUND;
  93. int AE_ZOMBIE_POISON_THROW_CRAB;
  94. int AE_ZOMBIE_POISON_SPIT;
  95. //-----------------------------------------------------------------------------
  96. // The model we use for our legs when we get blowed up.
  97. //-----------------------------------------------------------------------------
  98. static const char *s_szLegsModel = "models/zombie/classic_legs.mdl";
  99. //-----------------------------------------------------------------------------
  100. // The classname of the headcrab that jumps off of this kind of zombie.
  101. //-----------------------------------------------------------------------------
  102. static const char *s_szHeadcrabClassname = "npc_headcrab_poison";
  103. static const char *s_szHeadcrabModel = "models/headcrabblack.mdl";
  104. static const char *pMoanSounds[] =
  105. {
  106. "NPC_PoisonZombie.Moan1",
  107. };
  108. //-----------------------------------------------------------------------------
  109. // Skill settings.
  110. //-----------------------------------------------------------------------------
  111. ConVar sk_zombie_poison_health( "sk_zombie_poison_health", "0");
  112. ConVar sk_zombie_poison_dmg_spit( "sk_zombie_poison_dmg_spit","0");
  113. class CNPC_PoisonZombie : public CAI_BlendingHost<CNPC_BaseZombie>
  114. {
  115. DECLARE_CLASS( CNPC_PoisonZombie, CAI_BlendingHost<CNPC_BaseZombie> );
  116. public:
  117. //
  118. // CBaseZombie implemenation.
  119. //
  120. virtual Vector HeadTarget( const Vector &posSrc );
  121. bool ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold );
  122. virtual bool IsChopped( const CTakeDamageInfo &info ) { return false; }
  123. //
  124. // CAI_BaseNPC implementation.
  125. //
  126. virtual float MaxYawSpeed( void );
  127. virtual int RangeAttack1Conditions( float flDot, float flDist );
  128. virtual int RangeAttack2Conditions( float flDot, float flDist );
  129. virtual float GetClawAttackRange() const { return 70; }
  130. virtual void PrescheduleThink( void );
  131. virtual void BuildScheduleTestBits( void );
  132. virtual int SelectSchedule( void );
  133. virtual int SelectFailSchedule( int nFailedSchedule, int nFailedTask, AI_TaskFailureCode_t eTaskFailCode );
  134. virtual int TranslateSchedule( int scheduleType );
  135. virtual bool ShouldPlayIdleSound( void );
  136. //
  137. // CBaseAnimating implementation.
  138. //
  139. virtual void HandleAnimEvent( animevent_t *pEvent );
  140. //
  141. // CBaseEntity implementation.
  142. //
  143. virtual void Spawn( void );
  144. virtual void Precache( void );
  145. virtual void SetZombieModel( void );
  146. virtual Class_T Classify( void );
  147. virtual void Event_Killed( const CTakeDamageInfo &info );
  148. virtual int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
  149. DECLARE_DATADESC();
  150. DEFINE_CUSTOM_AI;
  151. void PainSound( const CTakeDamageInfo &info );
  152. void AlertSound( void );
  153. void IdleSound( void );
  154. void AttackSound( void );
  155. void AttackHitSound( void );
  156. void AttackMissSound( void );
  157. void FootstepSound( bool fRightFoot );
  158. void FootscuffSound( bool fRightFoot ) {};
  159. virtual void StopLoopingSounds( void );
  160. protected:
  161. virtual void MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize );
  162. virtual bool MustCloseToAttack( void );
  163. virtual const char *GetMoanSound( int nSoundIndex );
  164. virtual const char *GetLegsModel( void );
  165. virtual const char *GetTorsoModel( void );
  166. virtual const char *GetHeadcrabClassname( void );
  167. virtual const char *GetHeadcrabModel( void );
  168. private:
  169. void BreatheOffShort( void );
  170. void EnableCrab( int nCrab, bool bEnable );
  171. int RandomThrowCrab( void );
  172. void EvacuateNest( bool bExplosion, float flDamage, CBaseEntity *pAttacker );
  173. CSoundPatch *m_pFastBreathSound;
  174. CSoundPatch *m_pSlowBreathSound;
  175. int m_nCrabCount; // How many headcrabs we have on our back.
  176. bool m_bCrabs[MAX_CRABS]; // Which crabs in particular are on our back.
  177. float m_flNextCrabThrowTime; // The next time we are allowed to throw a headcrab.
  178. float m_flNextPainSoundTime;
  179. bool m_bNearEnemy;
  180. // NOT serialized:
  181. int m_nThrowCrab; // The crab we are about to throw.
  182. };
  183. LINK_ENTITY_TO_CLASS( npc_poisonzombie, CNPC_PoisonZombie );
  184. BEGIN_DATADESC( CNPC_PoisonZombie )
  185. DEFINE_SOUNDPATCH( m_pFastBreathSound ),
  186. DEFINE_SOUNDPATCH( m_pSlowBreathSound ),
  187. DEFINE_KEYFIELD( m_nCrabCount, FIELD_INTEGER, "crabcount" ),
  188. DEFINE_ARRAY( m_bCrabs, FIELD_BOOLEAN, MAX_CRABS ),
  189. DEFINE_FIELD( m_flNextCrabThrowTime, FIELD_TIME ),
  190. DEFINE_FIELD( m_flNextPainSoundTime, FIELD_TIME ),
  191. DEFINE_FIELD( m_bNearEnemy, FIELD_BOOLEAN ),
  192. // NOT serialized:
  193. //DEFINE_FIELD( m_nThrowCrab, FIELD_INTEGER ),
  194. END_DATADESC()
  195. //-----------------------------------------------------------------------------
  196. // Purpose:
  197. //-----------------------------------------------------------------------------
  198. void CNPC_PoisonZombie::Precache( void )
  199. {
  200. PrecacheModel("models/zombie/poison.mdl");
  201. PrecacheScriptSound( "NPC_PoisonZombie.Die" );
  202. PrecacheScriptSound( "NPC_PoisonZombie.ThrowWarn" );
  203. PrecacheScriptSound( "NPC_PoisonZombie.Throw" );
  204. PrecacheScriptSound( "NPC_PoisonZombie.Idle" );
  205. PrecacheScriptSound( "NPC_PoisonZombie.Pain" );
  206. PrecacheScriptSound( "NPC_PoisonZombie.Alert" );
  207. PrecacheScriptSound( "NPC_PoisonZombie.FootstepRight" );
  208. PrecacheScriptSound( "NPC_PoisonZombie.FootstepLeft" );
  209. PrecacheScriptSound( "NPC_PoisonZombie.Attack" );
  210. PrecacheScriptSound( "NPC_PoisonZombie.FastBreath" );
  211. PrecacheScriptSound( "NPC_PoisonZombie.Moan1" );
  212. PrecacheScriptSound( "Zombie.AttackHit" );
  213. PrecacheScriptSound( "Zombie.AttackMiss" );
  214. BaseClass::Precache();
  215. }
  216. //-----------------------------------------------------------------------------
  217. // Purpose:
  218. //-----------------------------------------------------------------------------
  219. void CNPC_PoisonZombie::Spawn( void )
  220. {
  221. Precache();
  222. m_fIsTorso = m_fIsHeadless = false;
  223. #ifdef HL2_EPISODIC
  224. SetBloodColor( BLOOD_COLOR_ZOMBIE );
  225. #else
  226. SetBloodColor( BLOOD_COLOR_YELLOW );
  227. #endif // HL2_EPISODIC
  228. m_iHealth = sk_zombie_poison_health.GetFloat();
  229. m_flFieldOfView = 0.2;
  230. CapabilitiesClear();
  231. CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 );
  232. BaseClass::Spawn();
  233. CPASAttenuationFilter filter( this, ATTN_IDLE );
  234. m_pFastBreathSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_ITEM, "NPC_PoisonZombie.FastBreath", ATTN_IDLE );
  235. ENVELOPE_CONTROLLER.Play( m_pFastBreathSound, 0.0f, 100 );
  236. CPASAttenuationFilter filter2( this );
  237. m_pSlowBreathSound = ENVELOPE_CONTROLLER.SoundCreate( filter2, entindex(), CHAN_ITEM, "NPC_PoisonZombie.Moan1", ATTN_NORM );
  238. ENVELOPE_CONTROLLER.Play( m_pSlowBreathSound, BREATH_VOL_MAX, 100 );
  239. int nCrabs = m_nCrabCount;
  240. if ( !nCrabs )
  241. {
  242. nCrabs = MAX_CRABS;
  243. }
  244. m_nCrabCount = 0;
  245. //
  246. // Generate a random set of crabs based on the crab count
  247. // specified by the level designer.
  248. //
  249. int nBits[] =
  250. {
  251. // One bit
  252. 0x01,
  253. 0x02,
  254. 0x04,
  255. // Two bits
  256. 0x03,
  257. 0x05,
  258. 0x06,
  259. };
  260. int nBitMask = 7;
  261. if (nCrabs == 1)
  262. {
  263. nBitMask = nBits[random->RandomInt( 0, 2 )];
  264. }
  265. else if (nCrabs == 2)
  266. {
  267. nBitMask = nBits[random->RandomInt( 3, 5 )];
  268. }
  269. for ( int i = 0; i < MAX_CRABS; i++ )
  270. {
  271. EnableCrab( i, ( nBitMask & ( 1 << i ) ) != 0 );
  272. }
  273. }
  274. //-----------------------------------------------------------------------------
  275. // Purpose: Returns a moan sound for this class of zombie.
  276. //-----------------------------------------------------------------------------
  277. const char *CNPC_PoisonZombie::GetMoanSound( int nSound )
  278. {
  279. return pMoanSounds[nSound % ARRAYSIZE( pMoanSounds )];
  280. }
  281. //-----------------------------------------------------------------------------
  282. // Purpose: Returns the model to use for our legs ragdoll when we are blown in twain.
  283. //-----------------------------------------------------------------------------
  284. const char *CNPC_PoisonZombie::GetLegsModel( void )
  285. {
  286. return s_szLegsModel;
  287. }
  288. //-----------------------------------------------------------------------------
  289. //-----------------------------------------------------------------------------
  290. const char *CNPC_PoisonZombie::GetTorsoModel( void )
  291. {
  292. return "models/zombie/classic_torso.mdl";
  293. }
  294. //-----------------------------------------------------------------------------
  295. // Purpose: Returns the classname (ie "npc_headcrab") to spawn when our headcrab bails.
  296. //-----------------------------------------------------------------------------
  297. const char *CNPC_PoisonZombie::GetHeadcrabClassname( void )
  298. {
  299. return s_szHeadcrabClassname;
  300. }
  301. const char *CNPC_PoisonZombie::GetHeadcrabModel( void )
  302. {
  303. return s_szHeadcrabModel;
  304. }
  305. //-----------------------------------------------------------------------------
  306. // Purpose: Turns the given crab on or off.
  307. //-----------------------------------------------------------------------------
  308. void CNPC_PoisonZombie::EnableCrab( int nCrab, bool bEnable )
  309. {
  310. ASSERT( ( nCrab >= 0 ) && ( nCrab < MAX_CRABS ) );
  311. if ( ( nCrab >= 0 ) && ( nCrab < MAX_CRABS ) )
  312. {
  313. if (m_bCrabs[nCrab] != bEnable)
  314. {
  315. m_nCrabCount += bEnable ? 1 : -1;
  316. }
  317. m_bCrabs[nCrab] = bEnable;
  318. SetBodygroup( ZOMBIE_BODYGROUP_NEST_BASE + nCrab, bEnable );
  319. }
  320. }
  321. //-----------------------------------------------------------------------------
  322. // Purpose:
  323. //-----------------------------------------------------------------------------
  324. void CNPC_PoisonZombie::StopLoopingSounds( void )
  325. {
  326. ENVELOPE_CONTROLLER.SoundDestroy( m_pFastBreathSound );
  327. m_pFastBreathSound = NULL;
  328. ENVELOPE_CONTROLLER.SoundDestroy( m_pSlowBreathSound );
  329. m_pSlowBreathSound = NULL;
  330. BaseClass::StopLoopingSounds();
  331. }
  332. //-----------------------------------------------------------------------------
  333. // Purpose:
  334. // Input : info -
  335. //-----------------------------------------------------------------------------
  336. void CNPC_PoisonZombie::Event_Killed( const CTakeDamageInfo &info )
  337. {
  338. if ( !( info.GetDamageType() & ( DMG_BLAST | DMG_ALWAYSGIB) ) )
  339. {
  340. EmitSound( "NPC_PoisonZombie.Die" );
  341. }
  342. if ( !m_fIsTorso )
  343. {
  344. EvacuateNest(info.GetDamageType() == DMG_BLAST, info.GetDamage(), info.GetAttacker() );
  345. }
  346. BaseClass::Event_Killed( info );
  347. }
  348. //-----------------------------------------------------------------------------
  349. // Purpose:
  350. // Input : &inputInfo -
  351. // Output : int
  352. //-----------------------------------------------------------------------------
  353. int CNPC_PoisonZombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
  354. {
  355. //
  356. // Calculate what percentage of the creature's max health
  357. // this amount of damage represents (clips at 1.0).
  358. //
  359. float flDamagePercent = MIN( 1, inputInfo.GetDamage() / m_iMaxHealth );
  360. //
  361. // Throw one crab for every 20% damage we take.
  362. //
  363. if ( flDamagePercent >= 0.2 )
  364. {
  365. m_flNextCrabThrowTime = gpGlobals->curtime;
  366. }
  367. return BaseClass::OnTakeDamage_Alive( inputInfo );
  368. }
  369. //-----------------------------------------------------------------------------
  370. // Purpose:
  371. //-----------------------------------------------------------------------------
  372. float CNPC_PoisonZombie::MaxYawSpeed( void )
  373. {
  374. return BaseClass::MaxYawSpeed();
  375. }
  376. //-----------------------------------------------------------------------------
  377. // Purpose:
  378. //-----------------------------------------------------------------------------
  379. Class_T CNPC_PoisonZombie::Classify( void )
  380. {
  381. return CLASS_ZOMBIE;
  382. }
  383. //-----------------------------------------------------------------------------
  384. // Purpose:
  385. //
  386. // NOTE: This function is still heavy with common code (found at the bottom).
  387. // we should consider moving some into the base class! (sjb)
  388. //-----------------------------------------------------------------------------
  389. void CNPC_PoisonZombie::SetZombieModel( void )
  390. {
  391. Hull_t lastHull = GetHullType();
  392. if ( m_fIsTorso )
  393. {
  394. SetModel( "models/zombie/classic_torso.mdl" );
  395. SetHullType(HULL_TINY);
  396. }
  397. else
  398. {
  399. SetModel( "models/zombie/poison.mdl" );
  400. SetHullType(HULL_HUMAN);
  401. }
  402. SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless );
  403. SetHullSizeNormal( true );
  404. SetDefaultEyeOffset();
  405. SetActivity( ACT_IDLE );
  406. // hull changed size, notify vphysics
  407. // UNDONE: Solve this generally, systematically so other
  408. // NPCs can change size
  409. if ( lastHull != GetHullType() )
  410. {
  411. if ( VPhysicsGetObject() )
  412. {
  413. SetupVPhysicsHull();
  414. }
  415. }
  416. }
  417. //-----------------------------------------------------------------------------
  418. // Purpose: Checks conditions for letting a headcrab leap off our back at our enemy.
  419. //-----------------------------------------------------------------------------
  420. int CNPC_PoisonZombie::RangeAttack1Conditions( float flDot, float flDist )
  421. {
  422. if ( !m_nCrabCount )
  423. {
  424. //DevMsg("Range1: No crabs\n");
  425. return 0;
  426. }
  427. if ( m_flNextCrabThrowTime > gpGlobals->curtime )
  428. {
  429. //DevMsg("Range1: Too soon\n");
  430. return 0;
  431. }
  432. if ( flDist < ZOMBIE_HC_LEAP_RANGE_MIN )
  433. {
  434. //DevMsg("Range1: Too close to attack\n");
  435. return COND_TOO_CLOSE_TO_ATTACK;
  436. }
  437. if ( flDist > ZOMBIE_HC_LEAP_RANGE_MAX )
  438. {
  439. //DevMsg("Range1: Too far to attack\n");
  440. return COND_TOO_FAR_TO_ATTACK;
  441. }
  442. if ( flDot < ZOMBIE_HC_LEAP_CONE )
  443. {
  444. //DevMsg("Range1: Not facing\n");
  445. return COND_NOT_FACING_ATTACK;
  446. }
  447. m_nThrowCrab = RandomThrowCrab();
  448. //DevMsg("*** Range1: Can range attack\n");
  449. return COND_CAN_RANGE_ATTACK1;
  450. }
  451. //-----------------------------------------------------------------------------
  452. // Purpose: Checks conditions for throwing a headcrab leap at our enemy.
  453. //-----------------------------------------------------------------------------
  454. int CNPC_PoisonZombie::RangeAttack2Conditions( float flDot, float flDist )
  455. {
  456. if ( !m_nCrabCount )
  457. {
  458. //DevMsg("Range2: No crabs\n");
  459. return 0;
  460. }
  461. if ( m_flNextCrabThrowTime > gpGlobals->curtime )
  462. {
  463. //DevMsg("Range2: Too soon\n");
  464. return 0;
  465. }
  466. if ( flDist < ZOMBIE_THROW_RANGE_MIN )
  467. {
  468. //DevMsg("Range2: Too close to attack\n");
  469. return COND_TOO_CLOSE_TO_ATTACK;
  470. }
  471. if ( flDist > ZOMBIE_THROW_RANGE_MAX )
  472. {
  473. //DevMsg("Range2: Too far to attack\n");
  474. return COND_TOO_FAR_TO_ATTACK;
  475. }
  476. if ( flDot < ZOMBIE_THROW_CONE )
  477. {
  478. //DevMsg("Range2: Not facing\n");
  479. return COND_NOT_FACING_ATTACK;
  480. }
  481. m_nThrowCrab = RandomThrowCrab();
  482. //DevMsg("*** Range2: Can range attack\n");
  483. return COND_CAN_RANGE_ATTACK2;
  484. }
  485. //-----------------------------------------------------------------------------
  486. //-----------------------------------------------------------------------------
  487. Vector CNPC_PoisonZombie::HeadTarget( const Vector &posSrc )
  488. {
  489. int iCrabAttachment = LookupAttachment( "headcrab1" );
  490. Assert( iCrabAttachment > 0 );
  491. Vector vecPosition;
  492. GetAttachment( iCrabAttachment, vecPosition );
  493. return vecPosition;
  494. }
  495. //-----------------------------------------------------------------------------
  496. // Purpose: Turns off our breath so we can play another vocal sound.
  497. // TODO: pass in duration
  498. //-----------------------------------------------------------------------------
  499. void CNPC_PoisonZombie::BreatheOffShort( void )
  500. {
  501. if ( m_bNearEnemy )
  502. {
  503. ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pFastBreathSound, SOUNDCTRL_CHANGE_VOLUME, envPoisonZombieBreatheVolumeOffShort, ARRAYSIZE(envPoisonZombieBreatheVolumeOffShort) );
  504. }
  505. else
  506. {
  507. ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pSlowBreathSound, SOUNDCTRL_CHANGE_VOLUME, envPoisonZombieBreatheVolumeOffShort, ARRAYSIZE(envPoisonZombieBreatheVolumeOffShort) );
  508. }
  509. }
  510. //-----------------------------------------------------------------------------
  511. // Purpose: Catches the monster-specific events that occur when tagged animation
  512. // frames are played.
  513. // Input : pEvent -
  514. //-----------------------------------------------------------------------------
  515. void CNPC_PoisonZombie::HandleAnimEvent( animevent_t *pEvent )
  516. {
  517. if ( pEvent->event == AE_ZOMBIE_POISON_PICKUP_CRAB )
  518. {
  519. EnableCrab( m_nThrowCrab, false );
  520. SetBodygroup( ZOMBIE_BODYGROUP_THROW, 1 );
  521. return;
  522. }
  523. if ( pEvent->event == AE_ZOMBIE_POISON_THROW_WARN_SOUND )
  524. {
  525. BreatheOffShort();
  526. EmitSound( "NPC_PoisonZombie.ThrowWarn" );
  527. return;
  528. }
  529. if ( pEvent->event == AE_ZOMBIE_POISON_THROW_SOUND )
  530. {
  531. BreatheOffShort();
  532. EmitSound( "NPC_PoisonZombie.Throw" );
  533. return;
  534. }
  535. if ( pEvent->event == AE_ZOMBIE_POISON_THROW_CRAB )
  536. {
  537. SetBodygroup( ZOMBIE_BODYGROUP_THROW, 0 );
  538. CBlackHeadcrab *pCrab = (CBlackHeadcrab *)CreateNoSpawn( GetHeadcrabClassname(), EyePosition(), vec3_angle, this );
  539. pCrab->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
  540. // Fade if our parent is supposed to
  541. if ( HasSpawnFlags( SF_NPC_FADE_CORPSE ) )
  542. {
  543. pCrab->AddSpawnFlags( SF_NPC_FADE_CORPSE );
  544. }
  545. // make me the crab's owner to avoid collision issues
  546. pCrab->SetOwnerEntity( this );
  547. pCrab->Spawn();
  548. pCrab->SetLocalAngles( GetLocalAngles() );
  549. pCrab->SetActivity( ACT_RANGE_ATTACK1 );
  550. pCrab->SetNextThink( gpGlobals->curtime );
  551. pCrab->PhysicsSimulate();
  552. pCrab->GetMotor()->SetIdealYaw( GetAbsAngles().y );
  553. if ( IsOnFire() )
  554. {
  555. pCrab->Ignite( 100.0 );
  556. }
  557. CBaseEntity *pEnemy = GetEnemy();
  558. if ( pEnemy )
  559. {
  560. Vector vecEnemyEyePos = pEnemy->EyePosition();
  561. pCrab->ThrowAt( vecEnemyEyePos );
  562. }
  563. if (m_nCrabCount == 0)
  564. {
  565. CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 );
  566. }
  567. m_flNextCrabThrowTime = gpGlobals->curtime + random->RandomInt( ZOMBIE_THROW_MIN_DELAY, ZOMBIE_THROW_MAX_DELAY );
  568. return;
  569. }
  570. if ( pEvent->event == AE_ZOMBIE_POISON_SPIT )
  571. {
  572. Vector forward;
  573. QAngle qaPunch( 45, random->RandomInt(-5, 5), random->RandomInt(-5, 5) );
  574. AngleVectors( GetLocalAngles(), &forward );
  575. forward = forward * 200;
  576. ClawAttack( GetClawAttackRange(), sk_zombie_poison_dmg_spit.GetFloat(), qaPunch, forward, ZOMBIE_BLOOD_BITE );
  577. return;
  578. }
  579. BaseClass::HandleAnimEvent( pEvent );
  580. }
  581. //-----------------------------------------------------------------------------
  582. // Purpose: Returns the index of a randomly chosen crab to throw.
  583. //-----------------------------------------------------------------------------
  584. int CNPC_PoisonZombie::RandomThrowCrab( void )
  585. {
  586. // FIXME: this could take a long time, theoretically
  587. int nCrab = -1;
  588. do
  589. {
  590. int nTest = random->RandomInt( 0, 2 );
  591. if ( m_bCrabs[nTest] )
  592. {
  593. nCrab = nTest;
  594. }
  595. } while ( nCrab == -1 );
  596. return nCrab;
  597. }
  598. //-----------------------------------------------------------------------------
  599. // Purpose: The nest is dead! Evacuate the nest!
  600. // Input : bExplosion - We were evicted by an explosion so we should go a-flying.
  601. // flDamage - The damage that was done to cause the evacuation.
  602. //-----------------------------------------------------------------------------
  603. void CNPC_PoisonZombie::EvacuateNest( bool bExplosion, float flDamage, CBaseEntity *pAttacker )
  604. {
  605. // HACK: if we were in mid-throw, drop the throwing crab also.
  606. if ( GetBodygroup( ZOMBIE_BODYGROUP_THROW ) )
  607. {
  608. SetBodygroup( ZOMBIE_BODYGROUP_THROW, 0 );
  609. m_nCrabCount++;
  610. }
  611. for( int i = 0; i < MAX_CRABS ; i++ )
  612. {
  613. if( m_bCrabs[i] )
  614. {
  615. Vector vecPosition;
  616. QAngle vecAngles;
  617. char szAttachment[64];
  618. switch( i )
  619. {
  620. case 0:
  621. strcpy( szAttachment, "headcrab2" );
  622. break;
  623. case 1:
  624. strcpy( szAttachment, "headcrab3" );
  625. break;
  626. case 2:
  627. strcpy( szAttachment, "headcrab4" );
  628. break;
  629. }
  630. GetAttachment( szAttachment, vecPosition, vecAngles );
  631. // Now slam the angles because the attachment point will have pitch and roll, which we can't use.
  632. vecAngles = QAngle( 0, random->RandomFloat( 0, 360 ), 0 );
  633. CBlackHeadcrab *pCrab = (CBlackHeadcrab *)CreateNoSpawn( GetHeadcrabClassname(), vecPosition, vecAngles, this );
  634. pCrab->Spawn();
  635. if( !HeadcrabFits(pCrab) )
  636. {
  637. UTIL_Remove(pCrab);
  638. continue;
  639. }
  640. float flVelocityScale = 2.0f;
  641. if ( bExplosion && ( flDamage > 10 ) )
  642. {
  643. flVelocityScale = 0.1 * flDamage;
  644. }
  645. if (IsOnFire())
  646. {
  647. pCrab->Ignite( 100.0 );
  648. }
  649. pCrab->Eject( vecAngles, flVelocityScale, pAttacker );
  650. EnableCrab( i, false );
  651. }
  652. }
  653. }
  654. //-----------------------------------------------------------------------------
  655. // Purpose:
  656. //-----------------------------------------------------------------------------
  657. void CNPC_PoisonZombie::PrescheduleThink( void )
  658. {
  659. if ( HasCondition( COND_NEW_ENEMY ) )
  660. {
  661. m_flNextCrabThrowTime = gpGlobals->curtime + random->RandomInt( ZOMBIE_THROW_FIRST_MIN_DELAY, ZOMBIE_THROW_FIRST_MAX_DELAY );
  662. }
  663. bool bNearEnemy = false;
  664. if ( GetEnemy() != NULL )
  665. {
  666. float flDist = (GetEnemy()->GetAbsOrigin() - GetAbsOrigin()).Length();
  667. if ( flDist < ZOMBIE_ENEMY_BREATHE_DIST )
  668. {
  669. bNearEnemy = true;
  670. }
  671. }
  672. if ( bNearEnemy )
  673. {
  674. if ( !m_bNearEnemy )
  675. {
  676. // Our enemy is nearby. Breathe faster.
  677. float duration = random->RandomFloat( 1.0f, 2.0f );
  678. ENVELOPE_CONTROLLER.SoundChangeVolume( m_pFastBreathSound, BREATH_VOL_MAX, duration );
  679. ENVELOPE_CONTROLLER.SoundChangePitch( m_pFastBreathSound, random->RandomInt( 100, 120 ), random->RandomFloat( 1.0f, 2.0f ) );
  680. ENVELOPE_CONTROLLER.SoundChangeVolume( m_pSlowBreathSound, 0.0f, duration );
  681. m_bNearEnemy = true;
  682. }
  683. }
  684. else if ( m_bNearEnemy )
  685. {
  686. // Our enemy is far away. Slow our breathing down.
  687. float duration = random->RandomFloat( 2.0f, 4.0f );
  688. ENVELOPE_CONTROLLER.SoundChangeVolume( m_pFastBreathSound, BREATH_VOL_MAX, duration );
  689. ENVELOPE_CONTROLLER.SoundChangeVolume( m_pSlowBreathSound, 0.0f, duration );
  690. // ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 80, 100 ), duration );
  691. m_bNearEnemy = false;
  692. }
  693. BaseClass::PrescheduleThink();
  694. }
  695. //-----------------------------------------------------------------------------
  696. // Purpose: Allows for modification of the interrupt mask for the current schedule.
  697. // In the most cases the base implementation should be called first.
  698. //-----------------------------------------------------------------------------
  699. void CNPC_PoisonZombie::BuildScheduleTestBits( void )
  700. {
  701. BaseClass::BuildScheduleTestBits();
  702. if ( IsCurSchedule( SCHED_CHASE_ENEMY ) )
  703. {
  704. SetCustomInterruptCondition( COND_LIGHT_DAMAGE );
  705. SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
  706. }
  707. else if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) || IsCurSchedule( SCHED_RANGE_ATTACK2 ) )
  708. {
  709. ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
  710. ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
  711. }
  712. }
  713. //-----------------------------------------------------------------------------
  714. // Purpose:
  715. //-----------------------------------------------------------------------------
  716. int CNPC_PoisonZombie::SelectFailSchedule( int nFailedSchedule, int nFailedTask, AI_TaskFailureCode_t eTaskFailCode )
  717. {
  718. int nSchedule = BaseClass::SelectFailSchedule( nFailedSchedule, nFailedTask, eTaskFailCode );
  719. if ( nSchedule == SCHED_CHASE_ENEMY_FAILED && m_nCrabCount > 0 )
  720. {
  721. return SCHED_ESTABLISH_LINE_OF_FIRE;
  722. }
  723. return nSchedule;
  724. }
  725. //-----------------------------------------------------------------------------
  726. // Purpose:
  727. // Output : int
  728. //-----------------------------------------------------------------------------
  729. int CNPC_PoisonZombie::SelectSchedule( void )
  730. {
  731. int nSchedule = BaseClass::SelectSchedule();
  732. if ( nSchedule == SCHED_SMALL_FLINCH )
  733. {
  734. m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
  735. }
  736. return nSchedule;
  737. }
  738. //-----------------------------------------------------------------------------
  739. // Purpose:
  740. // Input : scheduleType -
  741. // Output : int
  742. //-----------------------------------------------------------------------------
  743. int CNPC_PoisonZombie::TranslateSchedule( int scheduleType )
  744. {
  745. if ( scheduleType == SCHED_RANGE_ATTACK2 )
  746. {
  747. return SCHED_ZOMBIE_POISON_RANGE_ATTACK2;
  748. }
  749. if ( scheduleType == SCHED_RANGE_ATTACK1 )
  750. {
  751. return SCHED_ZOMBIE_POISON_RANGE_ATTACK1;
  752. }
  753. if ( scheduleType == SCHED_COMBAT_FACE && IsUnreachable( GetEnemy() ) )
  754. return SCHED_TAKE_COVER_FROM_ENEMY;
  755. // We'd simply like to shamble towards our enemy
  756. if ( scheduleType == SCHED_MOVE_TO_WEAPON_RANGE )
  757. return SCHED_CHASE_ENEMY;
  758. return BaseClass::TranslateSchedule( scheduleType );
  759. }
  760. //-----------------------------------------------------------------------------
  761. // Purpose:
  762. //-----------------------------------------------------------------------------
  763. bool CNPC_PoisonZombie::ShouldPlayIdleSound( void )
  764. {
  765. return CAI_BaseNPC::ShouldPlayIdleSound();
  766. }
  767. //-----------------------------------------------------------------------------
  768. // Purpose: Play a random attack hit sound
  769. //-----------------------------------------------------------------------------
  770. void CNPC_PoisonZombie::AttackHitSound( void )
  771. {
  772. EmitSound( "Zombie.AttackHit" );
  773. }
  774. //-----------------------------------------------------------------------------
  775. // Purpose: Play a random attack miss sound
  776. //-----------------------------------------------------------------------------
  777. void CNPC_PoisonZombie::AttackMissSound( void )
  778. {
  779. EmitSound( "Zombie.AttackMiss" );
  780. }
  781. //-----------------------------------------------------------------------------
  782. // Purpose: Play a random attack sound.
  783. //-----------------------------------------------------------------------------
  784. void CNPC_PoisonZombie::AttackSound( void )
  785. {
  786. EmitSound( "NPC_PoisonZombie.Attack" );
  787. }
  788. //-----------------------------------------------------------------------------
  789. // Purpose: Play a random idle sound.
  790. //-----------------------------------------------------------------------------
  791. void CNPC_PoisonZombie::IdleSound( void )
  792. {
  793. // HACK: base zombie code calls IdleSound even when not idle!
  794. if ( m_NPCState != NPC_STATE_COMBAT )
  795. {
  796. BreatheOffShort();
  797. EmitSound( "NPC_PoisonZombie.Idle" );
  798. MakeAISpookySound( 360.0f );
  799. }
  800. }
  801. //-----------------------------------------------------------------------------
  802. // Purpose: Play a random pain sound.
  803. //-----------------------------------------------------------------------------
  804. void CNPC_PoisonZombie::PainSound( const CTakeDamageInfo &info )
  805. {
  806. // Don't make pain sounds too often.
  807. if ( m_flNextPainSoundTime <= gpGlobals->curtime )
  808. {
  809. BreatheOffShort();
  810. EmitSound( "NPC_PoisonZombie.Pain" );
  811. m_flNextPainSoundTime = gpGlobals->curtime + random->RandomFloat( 4.0, 7.0 );
  812. }
  813. }
  814. //-----------------------------------------------------------------------------
  815. // Purpose: Play a random alert sound.
  816. //-----------------------------------------------------------------------------
  817. void CNPC_PoisonZombie::AlertSound( void )
  818. {
  819. BreatheOffShort();
  820. EmitSound( "NPC_PoisonZombie.Alert" );
  821. }
  822. //-----------------------------------------------------------------------------
  823. // Purpose: Sound of a footstep
  824. //-----------------------------------------------------------------------------
  825. void CNPC_PoisonZombie::FootstepSound( bool fRightFoot )
  826. {
  827. if( fRightFoot )
  828. {
  829. EmitSound( "NPC_PoisonZombie.FootstepRight" );
  830. }
  831. else
  832. {
  833. EmitSound( "NPC_PoisonZombie.FootstepLeft" );
  834. }
  835. if( ShouldPlayFootstepMoan() )
  836. {
  837. m_flNextMoanSound = gpGlobals->curtime;
  838. MoanSound( envPoisonZombieMoanVolumeFast, ARRAYSIZE( envPoisonZombieMoanVolumeFast ) );
  839. }
  840. }
  841. //-----------------------------------------------------------------------------
  842. // Purpose: If we don't have any headcrabs to throw, we must close to attack our enemy.
  843. //-----------------------------------------------------------------------------
  844. bool CNPC_PoisonZombie::MustCloseToAttack(void)
  845. {
  846. return (m_nCrabCount == 0);
  847. }
  848. //-----------------------------------------------------------------------------
  849. // Purpose: Open a window and let a little bit of the looping moan sound
  850. // come through.
  851. //-----------------------------------------------------------------------------
  852. void CNPC_PoisonZombie::MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize )
  853. {
  854. if( !m_pMoanSound )
  855. {
  856. // Don't set this up until the code calls for it.
  857. const char *pszSound = GetMoanSound( m_iMoanSound );
  858. m_flMoanPitch = random->RandomInt( 98, 110 );
  859. CPASAttenuationFilter filter( this, 1.5 );
  860. //m_pMoanSound = ENVELOPE_CONTROLLER.SoundCreate( entindex(), CHAN_STATIC, pszSound, ATTN_NORM );
  861. m_pMoanSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_STATIC, pszSound, 1.5 );
  862. ENVELOPE_CONTROLLER.Play( m_pMoanSound, 0.5, m_flMoanPitch );
  863. }
  864. envPoisonZombieMoanVolumeFast[ 1 ].durationMin = 0.1;
  865. envPoisonZombieMoanVolumeFast[ 1 ].durationMax = 0.4;
  866. if ( random->RandomInt( 1, 2 ) == 1 )
  867. {
  868. IdleSound();
  869. }
  870. float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pMoanSound, SOUNDCTRL_CHANGE_VOLUME, pEnvelope, iEnvelopeSize );
  871. float flPitchShift = random->RandomInt( -4, 4 );
  872. ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, m_flMoanPitch + flPitchShift, 0.3 );
  873. m_flNextMoanSound = gpGlobals->curtime + duration + 9999;
  874. }
  875. //-----------------------------------------------------------------------------
  876. // Purpose: Overloaded so that explosions don't split the poison zombie in twain.
  877. //-----------------------------------------------------------------------------
  878. bool CNPC_PoisonZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold )
  879. {
  880. return false;
  881. }
  882. int ACT_ZOMBIE_POISON_THREAT;
  883. AI_BEGIN_CUSTOM_NPC( npc_poisonzombie, CNPC_PoisonZombie )
  884. DECLARE_ACTIVITY( ACT_ZOMBIE_POISON_THREAT )
  885. //Adrian: events go here
  886. DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_THROW_WARN_SOUND )
  887. DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_PICKUP_CRAB )
  888. DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_THROW_SOUND )
  889. DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_THROW_CRAB )
  890. DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_SPIT )
  891. DEFINE_SCHEDULE
  892. (
  893. SCHED_ZOMBIE_POISON_RANGE_ATTACK2,
  894. " Tasks"
  895. " TASK_STOP_MOVING 0"
  896. " TASK_FACE_IDEAL 0"
  897. " TASK_PLAY_PRIVATE_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ZOMBIE_POISON_THREAT"
  898. " TASK_FACE_IDEAL 0"
  899. " TASK_RANGE_ATTACK2 0"
  900. " Interrupts"
  901. " COND_NO_PRIMARY_AMMO"
  902. )
  903. DEFINE_SCHEDULE
  904. (
  905. SCHED_ZOMBIE_POISON_RANGE_ATTACK1,
  906. " Tasks"
  907. " TASK_STOP_MOVING 0"
  908. " TASK_FACE_ENEMY 0"
  909. " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
  910. " TASK_RANGE_ATTACK1 0"
  911. ""
  912. " Interrupts"
  913. " COND_NO_PRIMARY_AMMO"
  914. )
  915. AI_END_CUSTOM_NPC()