aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/server/hl2/npc_PoisonZombie.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /sp/src/game/server/hl2/npc_PoisonZombie.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'sp/src/game/server/hl2/npc_PoisonZombie.cpp')
-rw-r--r--sp/src/game/server/hl2/npc_PoisonZombie.cpp1145
1 files changed, 1145 insertions, 0 deletions
diff --git a/sp/src/game/server/hl2/npc_PoisonZombie.cpp b/sp/src/game/server/hl2/npc_PoisonZombie.cpp
new file mode 100644
index 00000000..0742ee10
--- /dev/null
+++ b/sp/src/game/server/hl2/npc_PoisonZombie.cpp
@@ -0,0 +1,1145 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A hideous, putrescent, pus-filled undead carcass atop which a vile
+// nest of filthy poisonous headcrabs lurks.
+//
+// Anyway, this guy has two range attacks: at short range, headcrabs
+// will leap from the nest to attack. At long range he will wrench a
+// headcrab from his back to throw it at his enemy.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_motor.h"
+#include "game.h"
+#include "npc_headcrab.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "ai_task.h"
+#include "activitylist.h"
+#include "engine/IEngineSound.h"
+#include "npc_BaseZombie.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define BREATH_VOL_MAX 0.6
+
+//
+// Controls how soon he throws the first headcrab after seeing his enemy (also when the first headcrab leaps off)
+//
+#define ZOMBIE_THROW_FIRST_MIN_DELAY 1 // min seconds before first crab throw
+#define ZOMBIE_THROW_FIRST_MAX_DELAY 2 // max seconds before first crab throw
+
+//
+// Controls how often he throws headcrabs (also how often headcrabs leap off)
+//
+#define ZOMBIE_THROW_MIN_DELAY 4 // min seconds between crab throws
+#define ZOMBIE_THROW_MAX_DELAY 10 // max seconds between crab throws
+
+//
+// Ranges for throwing headcrabs.
+//
+#define ZOMBIE_THROW_RANGE_MIN 250
+#define ZOMBIE_THROW_RANGE_MAX 800
+#define ZOMBIE_THROW_CONE 0.6
+
+//
+// Ranges for headcrabs leaping off.
+//
+#define ZOMBIE_HC_LEAP_RANGE_MIN 12
+#define ZOMBIE_HC_LEAP_RANGE_MAX 256
+#define ZOMBIE_HC_LEAP_CONE 0.6
+
+
+#define ZOMBIE_BODYGROUP_NEST_BASE 2 // First nest crab, +2 more
+#define ZOMBIE_BODYGROUP_THROW 5 // The crab in our hand for throwing
+
+#define ZOMBIE_ENEMY_BREATHE_DIST 300 // How close we must be to our enemy before we start breathing hard.
+
+
+envelopePoint_t envPoisonZombieMoanVolumeFast[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 0.2f, 0.3f,
+ },
+};
+
+
+//
+// Turns the breathing off for a second, then back on.
+//
+envelopePoint_t envPoisonZombieBreatheVolumeOffShort[] =
+{
+ { 0.0f, 0.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 2.0f, 2.0f,
+ },
+ { BREATH_VOL_MAX, BREATH_VOL_MAX,
+ 1.0f, 1.0f,
+ },
+};
+
+
+//
+// Custom schedules.
+//
+enum
+{
+ SCHED_ZOMBIE_POISON_RANGE_ATTACK2 = LAST_BASE_ZOMBIE_SCHEDULE,
+ SCHED_ZOMBIE_POISON_RANGE_ATTACK1,
+};
+
+
+//-----------------------------------------------------------------------------
+// The maximum number of headcrabs we can have riding on our back.
+// NOTE: If you change this value you must also change the lookup table in Spawn!
+//-----------------------------------------------------------------------------
+#define MAX_CRABS 3
+
+int AE_ZOMBIE_POISON_THROW_WARN_SOUND;
+int AE_ZOMBIE_POISON_PICKUP_CRAB;
+int AE_ZOMBIE_POISON_THROW_SOUND;
+int AE_ZOMBIE_POISON_THROW_CRAB;
+int AE_ZOMBIE_POISON_SPIT;
+
+//-----------------------------------------------------------------------------
+// The model we use for our legs when we get blowed up.
+//-----------------------------------------------------------------------------
+static const char *s_szLegsModel = "models/zombie/classic_legs.mdl";
+
+
+//-----------------------------------------------------------------------------
+// The classname of the headcrab that jumps off of this kind of zombie.
+//-----------------------------------------------------------------------------
+static const char *s_szHeadcrabClassname = "npc_headcrab_poison";
+static const char *s_szHeadcrabModel = "models/headcrabblack.mdl";
+
+static const char *pMoanSounds[] =
+{
+ "NPC_PoisonZombie.Moan1",
+};
+
+//-----------------------------------------------------------------------------
+// Skill settings.
+//-----------------------------------------------------------------------------
+ConVar sk_zombie_poison_health( "sk_zombie_poison_health", "0");
+ConVar sk_zombie_poison_dmg_spit( "sk_zombie_poison_dmg_spit","0");
+
+class CNPC_PoisonZombie : public CAI_BlendingHost<CNPC_BaseZombie>
+{
+ DECLARE_CLASS( CNPC_PoisonZombie, CAI_BlendingHost<CNPC_BaseZombie> );
+
+public:
+
+ //
+ // CBaseZombie implemenation.
+ //
+ virtual Vector HeadTarget( const Vector &posSrc );
+ bool ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold );
+ virtual bool IsChopped( const CTakeDamageInfo &info ) { return false; }
+
+ //
+ // CAI_BaseNPC implementation.
+ //
+ virtual float MaxYawSpeed( void );
+
+ virtual int RangeAttack1Conditions( float flDot, float flDist );
+ virtual int RangeAttack2Conditions( float flDot, float flDist );
+
+ virtual float GetClawAttackRange() const { return 70; }
+
+ virtual void PrescheduleThink( void );
+ virtual void BuildScheduleTestBits( void );
+ virtual int SelectSchedule( void );
+ virtual int SelectFailSchedule( int nFailedSchedule, int nFailedTask, AI_TaskFailureCode_t eTaskFailCode );
+ virtual int TranslateSchedule( int scheduleType );
+
+ virtual bool ShouldPlayIdleSound( void );
+
+ //
+ // CBaseAnimating implementation.
+ //
+ virtual void HandleAnimEvent( animevent_t *pEvent );
+
+ //
+ // CBaseEntity implementation.
+ //
+ virtual void Spawn( void );
+ virtual void Precache( void );
+ virtual void SetZombieModel( void );
+
+ virtual Class_T Classify( void );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+
+ DECLARE_DATADESC();
+ DEFINE_CUSTOM_AI;
+
+ void PainSound( const CTakeDamageInfo &info );
+ void AlertSound( void );
+ void IdleSound( void );
+ void AttackSound( void );
+ void AttackHitSound( void );
+ void AttackMissSound( void );
+ void FootstepSound( bool fRightFoot );
+ void FootscuffSound( bool fRightFoot ) {};
+
+ virtual void StopLoopingSounds( void );
+
+protected:
+
+ virtual void MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize );
+ virtual bool MustCloseToAttack( void );
+
+ virtual const char *GetMoanSound( int nSoundIndex );
+ virtual const char *GetLegsModel( void );
+ virtual const char *GetTorsoModel( void );
+ virtual const char *GetHeadcrabClassname( void );
+ virtual const char *GetHeadcrabModel( void );
+
+private:
+
+ void BreatheOffShort( void );
+
+ void EnableCrab( int nCrab, bool bEnable );
+ int RandomThrowCrab( void );
+ void EvacuateNest( bool bExplosion, float flDamage, CBaseEntity *pAttacker );
+
+ CSoundPatch *m_pFastBreathSound;
+ CSoundPatch *m_pSlowBreathSound;
+
+ int m_nCrabCount; // How many headcrabs we have on our back.
+ bool m_bCrabs[MAX_CRABS]; // Which crabs in particular are on our back.
+ float m_flNextCrabThrowTime; // The next time we are allowed to throw a headcrab.
+
+ float m_flNextPainSoundTime;
+
+ bool m_bNearEnemy;
+
+ // NOT serialized:
+ int m_nThrowCrab; // The crab we are about to throw.
+};
+
+LINK_ENTITY_TO_CLASS( npc_poisonzombie, CNPC_PoisonZombie );
+
+
+BEGIN_DATADESC( CNPC_PoisonZombie )
+
+ DEFINE_SOUNDPATCH( m_pFastBreathSound ),
+ DEFINE_SOUNDPATCH( m_pSlowBreathSound ),
+ DEFINE_KEYFIELD( m_nCrabCount, FIELD_INTEGER, "crabcount" ),
+ DEFINE_ARRAY( m_bCrabs, FIELD_BOOLEAN, MAX_CRABS ),
+ DEFINE_FIELD( m_flNextCrabThrowTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flNextPainSoundTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_bNearEnemy, FIELD_BOOLEAN ),
+
+ // NOT serialized:
+ //DEFINE_FIELD( m_nThrowCrab, FIELD_INTEGER ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::Precache( void )
+{
+ PrecacheModel("models/zombie/poison.mdl");
+
+ PrecacheScriptSound( "NPC_PoisonZombie.Die" );
+ PrecacheScriptSound( "NPC_PoisonZombie.ThrowWarn" );
+ PrecacheScriptSound( "NPC_PoisonZombie.Throw" );
+ PrecacheScriptSound( "NPC_PoisonZombie.Idle" );
+ PrecacheScriptSound( "NPC_PoisonZombie.Pain" );
+ PrecacheScriptSound( "NPC_PoisonZombie.Alert" );
+ PrecacheScriptSound( "NPC_PoisonZombie.FootstepRight" );
+ PrecacheScriptSound( "NPC_PoisonZombie.FootstepLeft" );
+ PrecacheScriptSound( "NPC_PoisonZombie.Attack" );
+
+ PrecacheScriptSound( "NPC_PoisonZombie.FastBreath" );
+ PrecacheScriptSound( "NPC_PoisonZombie.Moan1" );
+
+ PrecacheScriptSound( "Zombie.AttackHit" );
+ PrecacheScriptSound( "Zombie.AttackMiss" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::Spawn( void )
+{
+ Precache();
+
+ m_fIsTorso = m_fIsHeadless = false;
+
+#ifdef HL2_EPISODIC
+ SetBloodColor( BLOOD_COLOR_ZOMBIE );
+#else
+ SetBloodColor( BLOOD_COLOR_YELLOW );
+#endif // HL2_EPISODIC
+
+ m_iHealth = sk_zombie_poison_health.GetFloat();
+ m_flFieldOfView = 0.2;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 );
+
+ BaseClass::Spawn();
+
+ CPASAttenuationFilter filter( this, ATTN_IDLE );
+ m_pFastBreathSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_ITEM, "NPC_PoisonZombie.FastBreath", ATTN_IDLE );
+ ENVELOPE_CONTROLLER.Play( m_pFastBreathSound, 0.0f, 100 );
+
+ CPASAttenuationFilter filter2( this );
+ m_pSlowBreathSound = ENVELOPE_CONTROLLER.SoundCreate( filter2, entindex(), CHAN_ITEM, "NPC_PoisonZombie.Moan1", ATTN_NORM );
+ ENVELOPE_CONTROLLER.Play( m_pSlowBreathSound, BREATH_VOL_MAX, 100 );
+
+ int nCrabs = m_nCrabCount;
+ if ( !nCrabs )
+ {
+ nCrabs = MAX_CRABS;
+ }
+ m_nCrabCount = 0;
+
+ //
+ // Generate a random set of crabs based on the crab count
+ // specified by the level designer.
+ //
+ int nBits[] =
+ {
+ // One bit
+ 0x01,
+ 0x02,
+ 0x04,
+
+ // Two bits
+ 0x03,
+ 0x05,
+ 0x06,
+ };
+
+ int nBitMask = 7;
+ if (nCrabs == 1)
+ {
+ nBitMask = nBits[random->RandomInt( 0, 2 )];
+ }
+ else if (nCrabs == 2)
+ {
+ nBitMask = nBits[random->RandomInt( 3, 5 )];
+ }
+
+ for ( int i = 0; i < MAX_CRABS; i++ )
+ {
+ EnableCrab( i, ( nBitMask & ( 1 << i ) ) != 0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a moan sound for this class of zombie.
+//-----------------------------------------------------------------------------
+const char *CNPC_PoisonZombie::GetMoanSound( int nSound )
+{
+ return pMoanSounds[nSound % ARRAYSIZE( pMoanSounds )];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the model to use for our legs ragdoll when we are blown in twain.
+//-----------------------------------------------------------------------------
+const char *CNPC_PoisonZombie::GetLegsModel( void )
+{
+ return s_szLegsModel;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+const char *CNPC_PoisonZombie::GetTorsoModel( void )
+{
+ return "models/zombie/classic_torso.mdl";
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the classname (ie "npc_headcrab") to spawn when our headcrab bails.
+//-----------------------------------------------------------------------------
+const char *CNPC_PoisonZombie::GetHeadcrabClassname( void )
+{
+ return s_szHeadcrabClassname;
+}
+
+const char *CNPC_PoisonZombie::GetHeadcrabModel( void )
+{
+ return s_szHeadcrabModel;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Turns the given crab on or off.
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::EnableCrab( int nCrab, bool bEnable )
+{
+ ASSERT( ( nCrab >= 0 ) && ( nCrab < MAX_CRABS ) );
+
+ if ( ( nCrab >= 0 ) && ( nCrab < MAX_CRABS ) )
+ {
+ if (m_bCrabs[nCrab] != bEnable)
+ {
+ m_nCrabCount += bEnable ? 1 : -1;
+ }
+
+ m_bCrabs[nCrab] = bEnable;
+ SetBodygroup( ZOMBIE_BODYGROUP_NEST_BASE + nCrab, bEnable );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::StopLoopingSounds( void )
+{
+ ENVELOPE_CONTROLLER.SoundDestroy( m_pFastBreathSound );
+ m_pFastBreathSound = NULL;
+
+ ENVELOPE_CONTROLLER.SoundDestroy( m_pSlowBreathSound );
+ m_pSlowBreathSound = NULL;
+
+ BaseClass::StopLoopingSounds();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : info -
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::Event_Killed( const CTakeDamageInfo &info )
+{
+ if ( !( info.GetDamageType() & ( DMG_BLAST | DMG_ALWAYSGIB) ) )
+ {
+ EmitSound( "NPC_PoisonZombie.Die" );
+ }
+
+ if ( !m_fIsTorso )
+ {
+ EvacuateNest(info.GetDamageType() == DMG_BLAST, info.GetDamage(), info.GetAttacker() );
+ }
+
+ BaseClass::Event_Killed( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputInfo -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_PoisonZombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ //
+ // Calculate what percentage of the creature's max health
+ // this amount of damage represents (clips at 1.0).
+ //
+ float flDamagePercent = MIN( 1, inputInfo.GetDamage() / m_iMaxHealth );
+
+ //
+ // Throw one crab for every 20% damage we take.
+ //
+ if ( flDamagePercent >= 0.2 )
+ {
+ m_flNextCrabThrowTime = gpGlobals->curtime;
+ }
+
+ return BaseClass::OnTakeDamage_Alive( inputInfo );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CNPC_PoisonZombie::MaxYawSpeed( void )
+{
+ return BaseClass::MaxYawSpeed();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Class_T CNPC_PoisonZombie::Classify( void )
+{
+ return CLASS_ZOMBIE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+// NOTE: This function is still heavy with common code (found at the bottom).
+// we should consider moving some into the base class! (sjb)
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::SetZombieModel( void )
+{
+ Hull_t lastHull = GetHullType();
+
+ if ( m_fIsTorso )
+ {
+ SetModel( "models/zombie/classic_torso.mdl" );
+ SetHullType(HULL_TINY);
+ }
+ else
+ {
+ SetModel( "models/zombie/poison.mdl" );
+ SetHullType(HULL_HUMAN);
+ }
+
+ SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless );
+
+ SetHullSizeNormal( true );
+ SetDefaultEyeOffset();
+ SetActivity( ACT_IDLE );
+
+ // hull changed size, notify vphysics
+ // UNDONE: Solve this generally, systematically so other
+ // NPCs can change size
+ if ( lastHull != GetHullType() )
+ {
+ if ( VPhysicsGetObject() )
+ {
+ SetupVPhysicsHull();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks conditions for letting a headcrab leap off our back at our enemy.
+//-----------------------------------------------------------------------------
+int CNPC_PoisonZombie::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( !m_nCrabCount )
+ {
+ //DevMsg("Range1: No crabs\n");
+ return 0;
+ }
+
+ if ( m_flNextCrabThrowTime > gpGlobals->curtime )
+ {
+ //DevMsg("Range1: Too soon\n");
+ return 0;
+ }
+
+ if ( flDist < ZOMBIE_HC_LEAP_RANGE_MIN )
+ {
+ //DevMsg("Range1: Too close to attack\n");
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+
+ if ( flDist > ZOMBIE_HC_LEAP_RANGE_MAX )
+ {
+ //DevMsg("Range1: Too far to attack\n");
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if ( flDot < ZOMBIE_HC_LEAP_CONE )
+ {
+ //DevMsg("Range1: Not facing\n");
+ return COND_NOT_FACING_ATTACK;
+ }
+
+ m_nThrowCrab = RandomThrowCrab();
+
+ //DevMsg("*** Range1: Can range attack\n");
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks conditions for throwing a headcrab leap at our enemy.
+//-----------------------------------------------------------------------------
+int CNPC_PoisonZombie::RangeAttack2Conditions( float flDot, float flDist )
+{
+ if ( !m_nCrabCount )
+ {
+ //DevMsg("Range2: No crabs\n");
+ return 0;
+ }
+
+ if ( m_flNextCrabThrowTime > gpGlobals->curtime )
+ {
+ //DevMsg("Range2: Too soon\n");
+ return 0;
+ }
+
+ if ( flDist < ZOMBIE_THROW_RANGE_MIN )
+ {
+ //DevMsg("Range2: Too close to attack\n");
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+
+ if ( flDist > ZOMBIE_THROW_RANGE_MAX )
+ {
+ //DevMsg("Range2: Too far to attack\n");
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if ( flDot < ZOMBIE_THROW_CONE )
+ {
+ //DevMsg("Range2: Not facing\n");
+ return COND_NOT_FACING_ATTACK;
+ }
+
+ m_nThrowCrab = RandomThrowCrab();
+
+ //DevMsg("*** Range2: Can range attack\n");
+ return COND_CAN_RANGE_ATTACK2;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+Vector CNPC_PoisonZombie::HeadTarget( const Vector &posSrc )
+{
+ int iCrabAttachment = LookupAttachment( "headcrab1" );
+ Assert( iCrabAttachment > 0 );
+
+ Vector vecPosition;
+
+ GetAttachment( iCrabAttachment, vecPosition );
+
+ return vecPosition;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Turns off our breath so we can play another vocal sound.
+// TODO: pass in duration
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::BreatheOffShort( void )
+{
+ if ( m_bNearEnemy )
+ {
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pFastBreathSound, SOUNDCTRL_CHANGE_VOLUME, envPoisonZombieBreatheVolumeOffShort, ARRAYSIZE(envPoisonZombieBreatheVolumeOffShort) );
+ }
+ else
+ {
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pSlowBreathSound, SOUNDCTRL_CHANGE_VOLUME, envPoisonZombieBreatheVolumeOffShort, ARRAYSIZE(envPoisonZombieBreatheVolumeOffShort) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches the monster-specific events that occur when tagged animation
+// frames are played.
+// Input : pEvent -
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::HandleAnimEvent( animevent_t *pEvent )
+{
+
+ if ( pEvent->event == AE_ZOMBIE_POISON_PICKUP_CRAB )
+ {
+ EnableCrab( m_nThrowCrab, false );
+ SetBodygroup( ZOMBIE_BODYGROUP_THROW, 1 );
+ return;
+ }
+
+ if ( pEvent->event == AE_ZOMBIE_POISON_THROW_WARN_SOUND )
+ {
+ BreatheOffShort();
+ EmitSound( "NPC_PoisonZombie.ThrowWarn" );
+ return;
+ }
+
+ if ( pEvent->event == AE_ZOMBIE_POISON_THROW_SOUND )
+ {
+ BreatheOffShort();
+ EmitSound( "NPC_PoisonZombie.Throw" );
+ return;
+ }
+
+ if ( pEvent->event == AE_ZOMBIE_POISON_THROW_CRAB )
+ {
+ SetBodygroup( ZOMBIE_BODYGROUP_THROW, 0 );
+
+ CBlackHeadcrab *pCrab = (CBlackHeadcrab *)CreateNoSpawn( GetHeadcrabClassname(), EyePosition(), vec3_angle, this );
+ pCrab->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
+
+ // Fade if our parent is supposed to
+ if ( HasSpawnFlags( SF_NPC_FADE_CORPSE ) )
+ {
+ pCrab->AddSpawnFlags( SF_NPC_FADE_CORPSE );
+ }
+
+ // make me the crab's owner to avoid collision issues
+ pCrab->SetOwnerEntity( this );
+
+ pCrab->Spawn();
+
+ pCrab->SetLocalAngles( GetLocalAngles() );
+ pCrab->SetActivity( ACT_RANGE_ATTACK1 );
+ pCrab->SetNextThink( gpGlobals->curtime );
+ pCrab->PhysicsSimulate();
+
+ pCrab->GetMotor()->SetIdealYaw( GetAbsAngles().y );
+
+ if ( IsOnFire() )
+ {
+ pCrab->Ignite( 100.0 );
+ }
+
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( pEnemy )
+ {
+ Vector vecEnemyEyePos = pEnemy->EyePosition();
+ pCrab->ThrowAt( vecEnemyEyePos );
+ }
+
+ if (m_nCrabCount == 0)
+ {
+ CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 );
+ }
+
+ m_flNextCrabThrowTime = gpGlobals->curtime + random->RandomInt( ZOMBIE_THROW_MIN_DELAY, ZOMBIE_THROW_MAX_DELAY );
+ return;
+ }
+
+ if ( pEvent->event == AE_ZOMBIE_POISON_SPIT )
+ {
+ Vector forward;
+ QAngle qaPunch( 45, random->RandomInt(-5, 5), random->RandomInt(-5, 5) );
+ AngleVectors( GetLocalAngles(), &forward );
+ forward = forward * 200;
+ ClawAttack( GetClawAttackRange(), sk_zombie_poison_dmg_spit.GetFloat(), qaPunch, forward, ZOMBIE_BLOOD_BITE );
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the index of a randomly chosen crab to throw.
+//-----------------------------------------------------------------------------
+int CNPC_PoisonZombie::RandomThrowCrab( void )
+{
+ // FIXME: this could take a long time, theoretically
+ int nCrab = -1;
+ do
+ {
+ int nTest = random->RandomInt( 0, 2 );
+ if ( m_bCrabs[nTest] )
+ {
+ nCrab = nTest;
+ }
+ } while ( nCrab == -1 );
+
+ return nCrab;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: The nest is dead! Evacuate the nest!
+// Input : bExplosion - We were evicted by an explosion so we should go a-flying.
+// flDamage - The damage that was done to cause the evacuation.
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::EvacuateNest( bool bExplosion, float flDamage, CBaseEntity *pAttacker )
+{
+ // HACK: if we were in mid-throw, drop the throwing crab also.
+ if ( GetBodygroup( ZOMBIE_BODYGROUP_THROW ) )
+ {
+ SetBodygroup( ZOMBIE_BODYGROUP_THROW, 0 );
+ m_nCrabCount++;
+ }
+
+ for( int i = 0; i < MAX_CRABS ; i++ )
+ {
+ if( m_bCrabs[i] )
+ {
+ Vector vecPosition;
+ QAngle vecAngles;
+
+ char szAttachment[64];
+
+ switch( i )
+ {
+ case 0:
+ strcpy( szAttachment, "headcrab2" );
+ break;
+ case 1:
+ strcpy( szAttachment, "headcrab3" );
+ break;
+ case 2:
+ strcpy( szAttachment, "headcrab4" );
+ break;
+ }
+
+ GetAttachment( szAttachment, vecPosition, vecAngles );
+
+ // Now slam the angles because the attachment point will have pitch and roll, which we can't use.
+ vecAngles = QAngle( 0, random->RandomFloat( 0, 360 ), 0 );
+
+ CBlackHeadcrab *pCrab = (CBlackHeadcrab *)CreateNoSpawn( GetHeadcrabClassname(), vecPosition, vecAngles, this );
+ pCrab->Spawn();
+
+ if( !HeadcrabFits(pCrab) )
+ {
+ UTIL_Remove(pCrab);
+ continue;
+ }
+
+ float flVelocityScale = 2.0f;
+ if ( bExplosion && ( flDamage > 10 ) )
+ {
+ flVelocityScale = 0.1 * flDamage;
+ }
+
+ if (IsOnFire())
+ {
+ pCrab->Ignite( 100.0 );
+ }
+
+ pCrab->Eject( vecAngles, flVelocityScale, pAttacker );
+ EnableCrab( i, false );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::PrescheduleThink( void )
+{
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ {
+ m_flNextCrabThrowTime = gpGlobals->curtime + random->RandomInt( ZOMBIE_THROW_FIRST_MIN_DELAY, ZOMBIE_THROW_FIRST_MAX_DELAY );
+ }
+
+ bool bNearEnemy = false;
+ if ( GetEnemy() != NULL )
+ {
+ float flDist = (GetEnemy()->GetAbsOrigin() - GetAbsOrigin()).Length();
+ if ( flDist < ZOMBIE_ENEMY_BREATHE_DIST )
+ {
+ bNearEnemy = true;
+ }
+ }
+
+ if ( bNearEnemy )
+ {
+ if ( !m_bNearEnemy )
+ {
+ // Our enemy is nearby. Breathe faster.
+ float duration = random->RandomFloat( 1.0f, 2.0f );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pFastBreathSound, BREATH_VOL_MAX, duration );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pFastBreathSound, random->RandomInt( 100, 120 ), random->RandomFloat( 1.0f, 2.0f ) );
+
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pSlowBreathSound, 0.0f, duration );
+
+ m_bNearEnemy = true;
+ }
+ }
+ else if ( m_bNearEnemy )
+ {
+ // Our enemy is far away. Slow our breathing down.
+ float duration = random->RandomFloat( 2.0f, 4.0f );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pFastBreathSound, BREATH_VOL_MAX, duration );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pSlowBreathSound, 0.0f, duration );
+// ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 80, 100 ), duration );
+
+ m_bNearEnemy = false;
+ }
+
+ BaseClass::PrescheduleThink();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows for modification of the interrupt mask for the current schedule.
+// In the most cases the base implementation should be called first.
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::BuildScheduleTestBits( void )
+{
+ BaseClass::BuildScheduleTestBits();
+
+ if ( IsCurSchedule( SCHED_CHASE_ENEMY ) )
+ {
+ SetCustomInterruptCondition( COND_LIGHT_DAMAGE );
+ SetCustomInterruptCondition( COND_HEAVY_DAMAGE );
+ }
+ else if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) || IsCurSchedule( SCHED_RANGE_ATTACK2 ) )
+ {
+ ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
+ ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_PoisonZombie::SelectFailSchedule( int nFailedSchedule, int nFailedTask, AI_TaskFailureCode_t eTaskFailCode )
+{
+ int nSchedule = BaseClass::SelectFailSchedule( nFailedSchedule, nFailedTask, eTaskFailCode );
+
+ if ( nSchedule == SCHED_CHASE_ENEMY_FAILED && m_nCrabCount > 0 )
+ {
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+ }
+
+ return nSchedule;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_PoisonZombie::SelectSchedule( void )
+{
+ int nSchedule = BaseClass::SelectSchedule();
+
+ if ( nSchedule == SCHED_SMALL_FLINCH )
+ {
+ m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
+ }
+
+ return nSchedule;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : scheduleType -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_PoisonZombie::TranslateSchedule( int scheduleType )
+{
+ if ( scheduleType == SCHED_RANGE_ATTACK2 )
+ {
+ return SCHED_ZOMBIE_POISON_RANGE_ATTACK2;
+ }
+
+ if ( scheduleType == SCHED_RANGE_ATTACK1 )
+ {
+ return SCHED_ZOMBIE_POISON_RANGE_ATTACK1;
+ }
+
+ if ( scheduleType == SCHED_COMBAT_FACE && IsUnreachable( GetEnemy() ) )
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+
+ // We'd simply like to shamble towards our enemy
+ if ( scheduleType == SCHED_MOVE_TO_WEAPON_RANGE )
+ return SCHED_CHASE_ENEMY;
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_PoisonZombie::ShouldPlayIdleSound( void )
+{
+ return CAI_BaseNPC::ShouldPlayIdleSound();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack hit sound
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::AttackHitSound( void )
+{
+ EmitSound( "Zombie.AttackHit" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack miss sound
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::AttackMissSound( void )
+{
+ EmitSound( "Zombie.AttackMiss" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack sound.
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::AttackSound( void )
+{
+ EmitSound( "NPC_PoisonZombie.Attack" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random idle sound.
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::IdleSound( void )
+{
+ // HACK: base zombie code calls IdleSound even when not idle!
+ if ( m_NPCState != NPC_STATE_COMBAT )
+ {
+ BreatheOffShort();
+ EmitSound( "NPC_PoisonZombie.Idle" );
+ MakeAISpookySound( 360.0f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random pain sound.
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::PainSound( const CTakeDamageInfo &info )
+{
+ // Don't make pain sounds too often.
+ if ( m_flNextPainSoundTime <= gpGlobals->curtime )
+ {
+ BreatheOffShort();
+ EmitSound( "NPC_PoisonZombie.Pain" );
+ m_flNextPainSoundTime = gpGlobals->curtime + random->RandomFloat( 4.0, 7.0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random alert sound.
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::AlertSound( void )
+{
+ BreatheOffShort();
+
+ EmitSound( "NPC_PoisonZombie.Alert" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sound of a footstep
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::FootstepSound( bool fRightFoot )
+{
+ if( fRightFoot )
+ {
+ EmitSound( "NPC_PoisonZombie.FootstepRight" );
+ }
+ else
+ {
+ EmitSound( "NPC_PoisonZombie.FootstepLeft" );
+ }
+
+ if( ShouldPlayFootstepMoan() )
+ {
+ m_flNextMoanSound = gpGlobals->curtime;
+ MoanSound( envPoisonZombieMoanVolumeFast, ARRAYSIZE( envPoisonZombieMoanVolumeFast ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: If we don't have any headcrabs to throw, we must close to attack our enemy.
+//-----------------------------------------------------------------------------
+bool CNPC_PoisonZombie::MustCloseToAttack(void)
+{
+ return (m_nCrabCount == 0);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Open a window and let a little bit of the looping moan sound
+// come through.
+//-----------------------------------------------------------------------------
+void CNPC_PoisonZombie::MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize )
+{
+ if( !m_pMoanSound )
+ {
+ // Don't set this up until the code calls for it.
+ const char *pszSound = GetMoanSound( m_iMoanSound );
+ m_flMoanPitch = random->RandomInt( 98, 110 );
+
+ CPASAttenuationFilter filter( this, 1.5 );
+ //m_pMoanSound = ENVELOPE_CONTROLLER.SoundCreate( entindex(), CHAN_STATIC, pszSound, ATTN_NORM );
+ m_pMoanSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_STATIC, pszSound, 1.5 );
+
+ ENVELOPE_CONTROLLER.Play( m_pMoanSound, 0.5, m_flMoanPitch );
+ }
+
+ envPoisonZombieMoanVolumeFast[ 1 ].durationMin = 0.1;
+ envPoisonZombieMoanVolumeFast[ 1 ].durationMax = 0.4;
+
+ if ( random->RandomInt( 1, 2 ) == 1 )
+ {
+ IdleSound();
+ }
+
+ float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pMoanSound, SOUNDCTRL_CHANGE_VOLUME, pEnvelope, iEnvelopeSize );
+
+ float flPitchShift = random->RandomInt( -4, 4 );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, m_flMoanPitch + flPitchShift, 0.3 );
+
+ m_flNextMoanSound = gpGlobals->curtime + duration + 9999;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Overloaded so that explosions don't split the poison zombie in twain.
+//-----------------------------------------------------------------------------
+bool CNPC_PoisonZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold )
+{
+ return false;
+}
+
+
+int ACT_ZOMBIE_POISON_THREAT;
+
+
+AI_BEGIN_CUSTOM_NPC( npc_poisonzombie, CNPC_PoisonZombie )
+
+ DECLARE_ACTIVITY( ACT_ZOMBIE_POISON_THREAT )
+
+ //Adrian: events go here
+ DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_THROW_WARN_SOUND )
+ DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_PICKUP_CRAB )
+ DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_THROW_SOUND )
+ DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_THROW_CRAB )
+ DECLARE_ANIMEVENT( AE_ZOMBIE_POISON_SPIT )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ZOMBIE_POISON_RANGE_ATTACK2,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_PLAY_PRIVATE_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ZOMBIE_POISON_THREAT"
+ " TASK_FACE_IDEAL 0"
+ " TASK_RANGE_ATTACK2 0"
+
+ " Interrupts"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ZOMBIE_POISON_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
+ " TASK_RANGE_ATTACK1 0"
+ ""
+ " Interrupts"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+AI_END_CUSTOM_NPC()
+
+