diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/npc_fastzombie.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/npc_fastzombie.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/npc_fastzombie.cpp | 2156 |
1 files changed, 2156 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_fastzombie.cpp b/mp/src/game/server/hl2/npc_fastzombie.cpp new file mode 100644 index 00000000..1a00a67a --- /dev/null +++ b/mp/src/game/server/hl2/npc_fastzombie.cpp @@ -0,0 +1,2156 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_motor.h"
+#include "ai_memory.h"
+#include "ai_route.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "ai_task.h"
+#include "activitylist.h"
+#include "engine/IEngineSound.h"
+#include "npc_BaseZombie.h"
+#include "movevars_shared.h"
+#include "IEffects.h"
+#include "props.h"
+#include "physics_npc_solver.h"
+#include "physics_prop_ragdoll.h"
+
+#ifdef HL2_EPISODIC
+#include "episodic/ai_behavior_passenger_zombie.h"
+#endif // HL2_EPISODIC
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define FASTZOMBIE_IDLE_PITCH 35
+#define FASTZOMBIE_MIN_PITCH 70
+#define FASTZOMBIE_MAX_PITCH 130
+#define FASTZOMBIE_SOUND_UPDATE_FREQ 0.5
+
+#define FASTZOMBIE_MAXLEAP_Z 128
+
+#define FASTZOMBIE_EXCITE_DIST 480.0
+
+#define FASTZOMBIE_BASE_FREQ 1.5
+
+// If flying at an enemy, and this close or closer, start playing the maul animation!!
+#define FASTZOMBIE_MAUL_RANGE 300
+
+#ifdef HL2_EPISODIC
+
+int AE_PASSENGER_PHYSICS_PUSH;
+int AE_FASTZOMBIE_VEHICLE_LEAP;
+int AE_FASTZOMBIE_VEHICLE_SS_DIE; // Killed while doing scripted sequence on vehicle
+
+#endif // HL2_EPISODIC
+
+enum
+{
+ COND_FASTZOMBIE_CLIMB_TOUCH = LAST_BASE_ZOMBIE_CONDITION,
+};
+
+envelopePoint_t envFastZombieVolumeJump[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 1.0f, 1.2f,
+ },
+};
+
+envelopePoint_t envFastZombieVolumePain[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 1.0f, 1.0f,
+ },
+};
+
+envelopePoint_t envFastZombieInverseVolumePain[] =
+{
+ { 0.0f, 0.0f,
+ 0.1f, 0.1f,
+ },
+ { 1.0f, 1.0f,
+ 1.0f, 1.0f,
+ },
+};
+
+envelopePoint_t envFastZombieVolumeJumpPostApex[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 1.0f, 1.2f,
+ },
+};
+
+envelopePoint_t envFastZombieVolumeClimb[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 0.2f, 0.2f,
+ },
+};
+
+envelopePoint_t envFastZombieMoanVolumeFast[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 0.2f, 0.3f,
+ },
+};
+
+envelopePoint_t envFastZombieMoanVolume[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 1.0f, 1.0f,
+ 0.2f, 0.2f,
+ },
+ { 0.0f, 0.0f,
+ 1.0f, 0.4f,
+ },
+};
+
+envelopePoint_t envFastZombieFootstepVolume[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.7f, 0.7f,
+ 0.2f, 0.2f,
+ },
+};
+
+envelopePoint_t envFastZombieVolumeFrenzy[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 2.0f, 2.0f,
+ },
+};
+
+
+//=========================================================
+// animation events
+//=========================================================
+int AE_FASTZOMBIE_LEAP;
+int AE_FASTZOMBIE_GALLOP_LEFT;
+int AE_FASTZOMBIE_GALLOP_RIGHT;
+int AE_FASTZOMBIE_CLIMB_LEFT;
+int AE_FASTZOMBIE_CLIMB_RIGHT;
+
+//=========================================================
+// tasks
+//=========================================================
+enum
+{
+ TASK_FASTZOMBIE_DO_ATTACK = LAST_SHARED_TASK + 100, // again, my !!!HACKHACK
+ TASK_FASTZOMBIE_LAND_RECOVER,
+ TASK_FASTZOMBIE_UNSTICK_JUMP,
+ TASK_FASTZOMBIE_JUMP_BACK,
+ TASK_FASTZOMBIE_VERIFY_ATTACK,
+};
+
+//=========================================================
+// activities
+//=========================================================
+int ACT_FASTZOMBIE_LEAP_SOAR;
+int ACT_FASTZOMBIE_LEAP_STRIKE;
+int ACT_FASTZOMBIE_LAND_RIGHT;
+int ACT_FASTZOMBIE_LAND_LEFT;
+int ACT_FASTZOMBIE_FRENZY;
+int ACT_FASTZOMBIE_BIG_SLASH;
+
+//=========================================================
+// schedules
+//=========================================================
+enum
+{
+ SCHED_FASTZOMBIE_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE + 100, // hack to get past the base zombie's schedules
+ SCHED_FASTZOMBIE_UNSTICK_JUMP,
+ SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP,
+ SCHED_FASTZOMBIE_MELEE_ATTACK1,
+ SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1,
+};
+
+
+
+//=========================================================
+//=========================================================
+class CFastZombie : public CNPC_BaseZombie
+{
+ DECLARE_CLASS( CFastZombie, CNPC_BaseZombie );
+
+public:
+ void Spawn( void );
+ void Precache( void );
+
+ void SetZombieModel( void );
+ bool CanSwatPhysicsObjects( void ) { return false; }
+
+ int TranslateSchedule( int scheduleType );
+
+ Activity NPC_TranslateActivity( Activity baseAct );
+
+ void LeapAttackTouch( CBaseEntity *pOther );
+ void ClimbTouch( CBaseEntity *pOther );
+
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+ int SelectSchedule( void );
+ void OnScheduleChange( void );
+
+ void PrescheduleThink( void );
+
+ float InnateRange1MaxRange( void );
+ int RangeAttack1Conditions( float flDot, float flDist );
+ int MeleeAttack1Conditions( float flDot, float flDist );
+
+ virtual float GetClawAttackRange() const { return 50; }
+
+ bool ShouldPlayFootstepMoan( void ) { return false; }
+
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ void PostNPCInit( void );
+
+ void LeapAttack( void );
+ void LeapAttackSound( void );
+
+ void BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce );
+
+ bool IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const;
+ bool MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost );
+ bool ShouldFailNav( bool bMovementFailed );
+
+ int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
+
+ const char *GetMoanSound( int nSound );
+
+ void OnChangeActivity( Activity NewActivity );
+ void OnStateChange( NPC_STATE OldState, NPC_STATE NewState );
+ void Event_Killed( const CTakeDamageInfo &info );
+ bool ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold );
+
+ virtual Vector GetAutoAimCenter() { return WorldSpaceCenter() - Vector( 0, 0, 12.0f ); }
+
+ void PainSound( const CTakeDamageInfo &info );
+ void DeathSound( 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 ) {}; // fast guy doesn't scuff
+ void StopLoopingSounds( void );
+
+ void SoundInit( void );
+ void SetIdleSoundState( void );
+ void SetAngrySoundState( void );
+
+ void BuildScheduleTestBits( void );
+
+ void BeginNavJump( void );
+ void EndNavJump( void );
+
+ bool IsNavJumping( void ) { return m_fIsNavJumping; }
+ void OnNavJumpHitApex( void );
+
+ void BeginAttackJump( void );
+ void EndAttackJump( void );
+
+ float MaxYawSpeed( void );
+
+ virtual const char *GetHeadcrabClassname( void );
+ virtual const char *GetHeadcrabModel( void );
+ virtual const char *GetLegsModel( void );
+ virtual const char *GetTorsoModel( void );
+
+//=============================================================================
+#ifdef HL2_EPISODIC
+
+public:
+ virtual bool CreateBehaviors( void );
+ virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
+ virtual void UpdateEfficiency( bool bInPVS );
+ virtual bool IsInAVehicle( void );
+ void InputAttachToVehicle( inputdata_t &inputdata );
+ void VehicleLeapAttackTouch( CBaseEntity *pOther );
+
+private:
+ void VehicleLeapAttack( void );
+ bool CanEnterVehicle( CPropJeepEpisodic *pVehicle );
+
+ CAI_PassengerBehaviorZombie m_PassengerBehavior;
+
+#endif // HL2_EPISODIC
+//=============================================================================
+
+protected:
+
+ static const char *pMoanSounds[];
+
+ // Sound stuff
+ float m_flDistFactor;
+ unsigned char m_iClimbCount; // counts rungs climbed (for sound)
+ bool m_fIsNavJumping;
+ bool m_fIsAttackJumping;
+ bool m_fHitApex;
+ mutable float m_flJumpDist;
+
+ bool m_fHasScreamed;
+
+private:
+ float m_flNextMeleeAttack;
+ bool m_fJustJumped;
+ float m_flJumpStartAltitude;
+ float m_flTimeUpdateSound;
+
+ CSoundPatch *m_pLayer2; // used for climbing ladders, and when jumping (pre apex)
+
+public:
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( npc_fastzombie, CFastZombie );
+LINK_ENTITY_TO_CLASS( npc_fastzombie_torso, CFastZombie );
+
+
+BEGIN_DATADESC( CFastZombie )
+
+ DEFINE_FIELD( m_flDistFactor, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iClimbCount, FIELD_CHARACTER ),
+ DEFINE_FIELD( m_fIsNavJumping, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fIsAttackJumping, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fHitApex, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flJumpDist, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fHasScreamed, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flNextMeleeAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_fJustJumped, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flJumpStartAltitude, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTimeUpdateSound, FIELD_TIME ),
+
+ // Function Pointers
+ DEFINE_ENTITYFUNC( LeapAttackTouch ),
+ DEFINE_ENTITYFUNC( ClimbTouch ),
+ DEFINE_SOUNDPATCH( m_pLayer2 ),
+
+#ifdef HL2_EPISODIC
+ DEFINE_ENTITYFUNC( VehicleLeapAttackTouch ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "AttachToVehicle", InputAttachToVehicle ),
+#endif // HL2_EPISODIC
+
+END_DATADESC()
+
+
+const char *CFastZombie::pMoanSounds[] =
+{
+ "NPC_FastZombie.Moan1",
+};
+
+//-----------------------------------------------------------------------------
+// The model we use for our legs when we get blowed up.
+//-----------------------------------------------------------------------------
+static const char *s_pLegsModel = "models/gibs/fast_zombie_legs.mdl";
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CFastZombie::Precache( void )
+{
+ PrecacheModel("models/zombie/fast.mdl");
+#ifdef HL2_EPISODIC
+ PrecacheModel("models/zombie/Fast_torso.mdl");
+ PrecacheScriptSound( "NPC_FastZombie.CarEnter1" );
+ PrecacheScriptSound( "NPC_FastZombie.CarEnter2" );
+ PrecacheScriptSound( "NPC_FastZombie.CarEnter3" );
+ PrecacheScriptSound( "NPC_FastZombie.CarEnter4" );
+ PrecacheScriptSound( "NPC_FastZombie.CarScream" );
+#endif
+ PrecacheModel( "models/gibs/fast_zombie_torso.mdl" );
+ PrecacheModel( "models/gibs/fast_zombie_legs.mdl" );
+
+ PrecacheScriptSound( "NPC_FastZombie.LeapAttack" );
+ PrecacheScriptSound( "NPC_FastZombie.FootstepRight" );
+ PrecacheScriptSound( "NPC_FastZombie.FootstepLeft" );
+ PrecacheScriptSound( "NPC_FastZombie.AttackHit" );
+ PrecacheScriptSound( "NPC_FastZombie.AttackMiss" );
+ PrecacheScriptSound( "NPC_FastZombie.LeapAttack" );
+ PrecacheScriptSound( "NPC_FastZombie.Attack" );
+ PrecacheScriptSound( "NPC_FastZombie.Idle" );
+ PrecacheScriptSound( "NPC_FastZombie.AlertFar" );
+ PrecacheScriptSound( "NPC_FastZombie.AlertNear" );
+ PrecacheScriptSound( "NPC_FastZombie.GallopLeft" );
+ PrecacheScriptSound( "NPC_FastZombie.GallopRight" );
+ PrecacheScriptSound( "NPC_FastZombie.Scream" );
+ PrecacheScriptSound( "NPC_FastZombie.RangeAttack" );
+ PrecacheScriptSound( "NPC_FastZombie.Frenzy" );
+ PrecacheScriptSound( "NPC_FastZombie.NoSound" );
+ PrecacheScriptSound( "NPC_FastZombie.Die" );
+
+ PrecacheScriptSound( "NPC_FastZombie.Gurgle" );
+
+ PrecacheScriptSound( "NPC_FastZombie.Moan1" );
+
+ BaseClass::Precache();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFastZombie::OnScheduleChange( void )
+{
+ if ( m_flNextMeleeAttack > gpGlobals->curtime + 1 )
+ {
+ // Allow melee attacks again.
+ m_flNextMeleeAttack = gpGlobals->curtime + 0.5;
+ }
+
+ BaseClass::OnScheduleChange();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CFastZombie::SelectSchedule ( void )
+{
+
+// ========================================================
+#ifdef HL2_EPISODIC
+
+ // Defer all decisions to the behavior if it's running
+ if ( m_PassengerBehavior.CanSelectSchedule() )
+ {
+ DeferSchedulingToBehavior( &m_PassengerBehavior );
+ return BaseClass::SelectSchedule();
+ }
+
+#endif //HL2_EPISODIC
+// ========================================================
+
+ if ( HasCondition( COND_ZOMBIE_RELEASECRAB ) )
+ {
+ // Death waits for no man. Or zombie. Or something.
+ return SCHED_ZOMBIE_RELEASECRAB;
+ }
+
+ if ( HasCondition( COND_FASTZOMBIE_CLIMB_TOUCH ) )
+ {
+ return SCHED_FASTZOMBIE_UNSTICK_JUMP;
+ }
+
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ if ( HasCondition( COND_LOST_ENEMY ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) )
+ {
+ // Set state to alert and recurse!
+ SetState( NPC_STATE_ALERT );
+ return SelectSchedule();
+ }
+ break;
+
+ case NPC_STATE_ALERT:
+ if ( HasCondition( COND_LOST_ENEMY ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) )
+ {
+ ClearCondition( COND_LOST_ENEMY );
+ ClearCondition( COND_ENEMY_UNREACHABLE );
+ SetEnemy( NULL );
+
+#ifdef DEBUG_ZOMBIES
+ DevMsg("Wandering\n");
+#endif
+
+ // Just lost track of our enemy.
+ // Wander around a bit so we don't look like a dingus.
+ return SCHED_ZOMBIE_WANDER_MEDIUM;
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CFastZombie::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ if( GetGroundEntity() && GetGroundEntity()->Classify() == CLASS_HEADCRAB )
+ {
+ // Kill!
+ CTakeDamageInfo info;
+ info.SetDamage( GetGroundEntity()->GetHealth() );
+ info.SetAttacker( this );
+ info.SetInflictor( this );
+ info.SetDamageType( DMG_GENERIC );
+ GetGroundEntity()->TakeDamage( info );
+ }
+
+ if( m_pMoanSound && gpGlobals->curtime > m_flTimeUpdateSound )
+ {
+ // Manage the snorting sound, pitch up for closer.
+ float flDistNoBBox;
+
+ if( GetEnemy() && m_NPCState == NPC_STATE_COMBAT )
+ {
+ flDistNoBBox = ( GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter() ).Length();
+ flDistNoBBox -= WorldAlignSize().x;
+ }
+ else
+ {
+ // Calm down!
+ flDistNoBBox = FASTZOMBIE_EXCITE_DIST;
+ m_flTimeUpdateSound += 1.0;
+ }
+
+ if( flDistNoBBox >= FASTZOMBIE_EXCITE_DIST && m_flDistFactor != 1.0 )
+ {
+ // Go back to normal pitch.
+ m_flDistFactor = 1.0;
+
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_IDLE_PITCH, FASTZOMBIE_SOUND_UPDATE_FREQ );
+ }
+ else if( flDistNoBBox < FASTZOMBIE_EXCITE_DIST )
+ {
+ // Zombie is close! Recalculate pitch.
+ int iPitch;
+
+ m_flDistFactor = MIN( 1.0, 1 - flDistNoBBox / FASTZOMBIE_EXCITE_DIST );
+ iPitch = FASTZOMBIE_MIN_PITCH + ( ( FASTZOMBIE_MAX_PITCH - FASTZOMBIE_MIN_PITCH ) * m_flDistFactor);
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, iPitch, FASTZOMBIE_SOUND_UPDATE_FREQ );
+ }
+
+ m_flTimeUpdateSound = gpGlobals->curtime + FASTZOMBIE_SOUND_UPDATE_FREQ;
+ }
+
+ // Crudely detect the apex of our jump
+ if( IsNavJumping() && !m_fHitApex && GetAbsVelocity().z <= 0.0 )
+ {
+ OnNavJumpHitApex();
+ }
+
+ if( IsCurSchedule(SCHED_FASTZOMBIE_RANGE_ATTACK1, false) )
+ {
+ // Think more frequently when flying quickly through the
+ // air, to update the server's location more often.
+ SetNextThink(gpGlobals->curtime);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Startup all of the sound patches that the fast zombie uses.
+//
+//
+//-----------------------------------------------------------------------------
+void CFastZombie::SoundInit( void )
+{
+ if( !m_pMoanSound )
+ {
+ // !!!HACKHACK - kickstart the moan sound. (sjb)
+ MoanSound( envFastZombieMoanVolume, ARRAYSIZE( envFastZombieMoanVolume ) );
+
+ // Clear the commands that the base class gave the moaning sound channel.
+ ENVELOPE_CONTROLLER.CommandClear( m_pMoanSound );
+ }
+
+ CPASAttenuationFilter filter( this );
+
+ if( !m_pLayer2 )
+ {
+ // Set up layer2
+ m_pLayer2 = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_VOICE, "NPC_FastZombie.Gurgle", ATTN_NORM );
+
+ // Start silent.
+ ENVELOPE_CONTROLLER.Play( m_pLayer2, 0.0, 100 );
+ }
+
+ SetIdleSoundState();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make the zombie sound calm.
+//-----------------------------------------------------------------------------
+void CFastZombie::SetIdleSoundState( void )
+{
+ // Main looping sound
+ if ( m_pMoanSound )
+ {
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_IDLE_PITCH, 1.0 );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0.75, 1.0 );
+ }
+
+ // Second Layer
+ if ( m_pLayer2 )
+ {
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, 100, 1.0 );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pLayer2, 0.0, 1.0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Make the zombie sound pizzled
+//-----------------------------------------------------------------------------
+void CFastZombie::SetAngrySoundState( void )
+{
+ if (( !m_pMoanSound ) || ( !m_pLayer2 ))
+ {
+ return;
+ }
+
+ EmitSound( "NPC_FastZombie.LeapAttack" );
+
+ // Main looping sound
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_MIN_PITCH, 0.5 );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1.0, 0.5 );
+
+ // Second Layer
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, 100, 1.0 );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pLayer2, 0.0, 1.0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CFastZombie::Spawn( void )
+{
+ Precache();
+
+ m_fJustJumped = false;
+
+ m_fIsTorso = m_fIsHeadless = false;
+
+ if( FClassnameIs( this, "npc_fastzombie" ) )
+ {
+ m_fIsTorso = false;
+ }
+ else
+ {
+ // This was placed as an npc_fastzombie_torso
+ m_fIsTorso = true;
+ }
+
+#ifdef HL2_EPISODIC
+ SetBloodColor( BLOOD_COLOR_ZOMBIE );
+#else
+ SetBloodColor( BLOOD_COLOR_YELLOW );
+#endif // HL2_EPISODIC
+
+ m_iHealth = 50;
+ m_flFieldOfView = 0.2;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_CLIMB | bits_CAP_MOVE_JUMP | bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 /* | bits_CAP_INNATE_MELEE_ATTACK1 */);
+
+ if ( m_fIsTorso == true )
+ {
+ CapabilitiesRemove( bits_CAP_MOVE_JUMP | bits_CAP_INNATE_RANGE_ATTACK1 );
+ }
+
+ m_flNextAttack = gpGlobals->curtime;
+
+ m_pLayer2 = NULL;
+ m_iClimbCount = 0;
+
+ EndNavJump();
+
+ m_flDistFactor = 1.0;
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CFastZombie::PostNPCInit( void )
+{
+ SoundInit();
+
+ m_flTimeUpdateSound = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the classname (ie "npc_headcrab") to spawn when our headcrab bails.
+//-----------------------------------------------------------------------------
+const char *CFastZombie::GetHeadcrabClassname( void )
+{
+ return "npc_headcrab_fast";
+}
+
+const char *CFastZombie::GetHeadcrabModel( void )
+{
+ return "models/headcrab.mdl";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CFastZombie::MaxYawSpeed( void )
+{
+ switch( GetActivity() )
+ {
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 120;
+ break;
+
+ case ACT_RUN:
+ return 160;
+ break;
+
+ case ACT_WALK:
+ case ACT_IDLE:
+ return 25;
+ break;
+
+ default:
+ return 20;
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CFastZombie::SetZombieModel( void )
+{
+ Hull_t lastHull = GetHullType();
+
+ if ( m_fIsTorso )
+ {
+ SetModel( "models/zombie/fast_torso.mdl" );
+ SetHullType(HULL_TINY);
+ }
+ else
+ {
+ SetModel( "models/zombie/fast.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: Returns the model to use for our legs ragdoll when we are blown in twain.
+//-----------------------------------------------------------------------------
+const char *CFastZombie::GetLegsModel( void )
+{
+ return s_pLegsModel;
+}
+
+const char *CFastZombie::GetTorsoModel( void )
+{
+ return "models/gibs/fast_zombie_torso.mdl";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: See if I can swat the player
+//
+//
+//-----------------------------------------------------------------------------
+int CFastZombie::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ if ( !GetEnemy() )
+ {
+ return COND_NONE;
+ }
+
+ if( !(GetFlags() & FL_ONGROUND) )
+ {
+ // Have to be on the ground!
+ return COND_NONE;
+ }
+
+ if( gpGlobals->curtime < m_flNextMeleeAttack )
+ {
+ return COND_NONE;
+ }
+
+ int baseResult = BaseClass::MeleeAttack1Conditions( flDot, flDist );
+
+ // @TODO (toml 07-21-04): follow up with Steve to find out why fz was explicitly not using these conditions
+ if ( baseResult == COND_TOO_FAR_TO_ATTACK || baseResult == COND_NOT_FACING_ATTACK )
+ {
+ return COND_NONE;
+ }
+
+ return baseResult;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a moan sound for this class of zombie.
+//-----------------------------------------------------------------------------
+const char *CFastZombie::GetMoanSound( int nSound )
+{
+ return pMoanSounds[ nSound % ARRAYSIZE( pMoanSounds ) ];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sound of a footstep
+//-----------------------------------------------------------------------------
+void CFastZombie::FootstepSound( bool fRightFoot )
+{
+ if( fRightFoot )
+ {
+ EmitSound( "NPC_FastZombie.FootstepRight" );
+ }
+ else
+ {
+ EmitSound( "NPC_FastZombie.FootstepLeft" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack hit sound
+//-----------------------------------------------------------------------------
+void CFastZombie::AttackHitSound( void )
+{
+ EmitSound( "NPC_FastZombie.AttackHit" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack miss sound
+//-----------------------------------------------------------------------------
+void CFastZombie::AttackMissSound( void )
+{
+ // Play a random attack miss sound
+ EmitSound( "NPC_FastZombie.AttackMiss" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack sound.
+//-----------------------------------------------------------------------------
+void CFastZombie::LeapAttackSound( void )
+{
+ EmitSound( "NPC_FastZombie.LeapAttack" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack sound.
+//-----------------------------------------------------------------------------
+void CFastZombie::AttackSound( void )
+{
+ EmitSound( "NPC_FastZombie.Attack" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random idle sound.
+//-----------------------------------------------------------------------------
+void CFastZombie::IdleSound( void )
+{
+ EmitSound( "NPC_FastZombie.Idle" );
+ MakeAISpookySound( 360.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random pain sound.
+//-----------------------------------------------------------------------------
+void CFastZombie::PainSound( const CTakeDamageInfo &info )
+{
+ if ( m_pLayer2 )
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumePain, ARRAYSIZE(envFastZombieVolumePain) );
+ if ( m_pMoanSound )
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pMoanSound, SOUNDCTRL_CHANGE_VOLUME, envFastZombieInverseVolumePain, ARRAYSIZE(envFastZombieInverseVolumePain) );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CFastZombie::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_FastZombie.Die" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random alert sound.
+//-----------------------------------------------------------------------------
+void CFastZombie::AlertSound( void )
+{
+ CBaseEntity *pPlayer = AI_GetSinglePlayer();
+
+ if( pPlayer )
+ {
+ // Measure how far the player is, and play the appropriate type of alert sound.
+ // Doesn't matter if I'm getting mad at a different character, the player is the
+ // one that hears the sound.
+ float flDist;
+
+ flDist = ( GetAbsOrigin() - pPlayer->GetAbsOrigin() ).Length();
+
+ if( flDist > 512 )
+ {
+ EmitSound( "NPC_FastZombie.AlertFar" );
+ }
+ else
+ {
+ EmitSound( "NPC_FastZombie.AlertNear" );
+ }
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+#define FASTZOMBIE_MINLEAP 200
+#define FASTZOMBIE_MAXLEAP 300
+float CFastZombie::InnateRange1MaxRange( void )
+{
+ return FASTZOMBIE_MAXLEAP;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: See if I can make my leaping attack!!
+//
+//
+//-----------------------------------------------------------------------------
+int CFastZombie::RangeAttack1Conditions( float flDot, float flDist )
+{
+
+ if (GetEnemy() == NULL)
+ {
+ return( COND_NONE );
+ }
+
+ if( !(GetFlags() & FL_ONGROUND) )
+ {
+ return COND_NONE;
+ }
+
+ if( gpGlobals->curtime < m_flNextAttack )
+ {
+ return( COND_NONE );
+ }
+
+ // make sure the enemy isn't on a roof and I'm in the streets (Ravenholm)
+ float flZDist;
+ flZDist = fabs( GetEnemy()->GetLocalOrigin().z - GetLocalOrigin().z );
+ if( flZDist > FASTZOMBIE_MAXLEAP_Z )
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if( flDist > InnateRange1MaxRange() )
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if( flDist < FASTZOMBIE_MINLEAP )
+ {
+ return COND_NONE;
+ }
+
+ if (flDot < 0.8)
+ {
+ return COND_NONE;
+ }
+
+ if ( !IsMoving() )
+ {
+ // I Have to be running!!!
+ return COND_NONE;
+ }
+
+ // Don't jump at the player unless he's facing me.
+ // This allows the player to get away if he turns and sprints
+ CBasePlayer *pPlayer = static_cast<CBasePlayer*>( GetEnemy() );
+
+ if( pPlayer )
+ {
+ // If the enemy is a player, don't attack from behind!
+ if( !pPlayer->FInViewCone( this ) )
+ {
+ return COND_NONE;
+ }
+ }
+
+ // Drumroll please!
+ // The final check! Is the path from my position to halfway between me
+ // and the player clear?
+ trace_t tr;
+ Vector vecDirToEnemy;
+
+ vecDirToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
+ Vector vecHullMin( -16, -16, -16 );
+ Vector vecHullMax( 16, 16, 16 );
+
+ // only check half the distance. (the first part of the jump)
+ vecDirToEnemy = vecDirToEnemy * 0.5;
+
+ AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + vecDirToEnemy, vecHullMin, vecHullMax, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.fraction != 1.0 )
+ {
+ // There's some sort of obstacle pretty much right in front of me.
+ return COND_NONE;
+ }
+
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CFastZombie::HandleAnimEvent( animevent_t *pEvent )
+{
+ if ( pEvent->event == AE_FASTZOMBIE_CLIMB_LEFT || pEvent->event == AE_FASTZOMBIE_CLIMB_RIGHT )
+ {
+ if( ++m_iClimbCount % 3 == 0 )
+ {
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, random->RandomFloat( 100, 150 ), 0.0 );
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumeClimb, ARRAYSIZE(envFastZombieVolumeClimb) );
+ }
+
+ return;
+ }
+
+ if ( pEvent->event == AE_FASTZOMBIE_LEAP )
+ {
+ LeapAttack();
+ return;
+ }
+
+ if ( pEvent->event == AE_FASTZOMBIE_GALLOP_LEFT )
+ {
+ EmitSound( "NPC_FastZombie.GallopLeft" );
+ return;
+ }
+
+ if ( pEvent->event == AE_FASTZOMBIE_GALLOP_RIGHT )
+ {
+ EmitSound( "NPC_FastZombie.GallopRight" );
+ return;
+ }
+
+ if ( pEvent->event == AE_ZOMBIE_ATTACK_RIGHT )
+ {
+ Vector right;
+ AngleVectors( GetLocalAngles(), NULL, &right, NULL );
+ right = right * -50;
+
+ QAngle angle( -3, -5, -3 );
+ ClawAttack( GetClawAttackRange(), 3, angle, right, ZOMBIE_BLOOD_RIGHT_HAND );
+ return;
+ }
+
+ if ( pEvent->event == AE_ZOMBIE_ATTACK_LEFT )
+ {
+ Vector right;
+ AngleVectors( GetLocalAngles(), NULL, &right, NULL );
+ right = right * 50;
+ QAngle angle( -3, 5, -3 );
+ ClawAttack( GetClawAttackRange(), 3, angle, right, ZOMBIE_BLOOD_LEFT_HAND );
+ return;
+ }
+
+//=============================================================================
+#ifdef HL2_EPISODIC
+
+ // Do the leap attack
+ if ( pEvent->event == AE_FASTZOMBIE_VEHICLE_LEAP )
+ {
+ VehicleLeapAttack();
+ return;
+ }
+
+ // Die while doing an SS in a vehicle
+ if ( pEvent->event == AE_FASTZOMBIE_VEHICLE_SS_DIE )
+ {
+ if ( IsInAVehicle() )
+ {
+ // Get the vehicle's present speed as a baseline
+ Vector vecVelocity = vec3_origin;
+ CBaseEntity *pVehicle = m_PassengerBehavior.GetTargetVehicle();
+ if ( pVehicle )
+ {
+ pVehicle->GetVelocity( &vecVelocity, NULL );
+ }
+
+ // TODO: We need to make this content driven -- jdw
+ Vector vecForward, vecRight, vecUp;
+ GetVectors( &vecForward, &vecRight, &vecUp );
+
+ vecVelocity += ( vecForward * -2500.0f ) + ( vecRight * 200.0f ) + ( vecUp * 300 );
+
+ // Always kill
+ float flDamage = GetMaxHealth() + 10;
+
+ // Take the damage and die
+ CTakeDamageInfo info( this, this, vecVelocity * 25.0f, WorldSpaceCenter(), flDamage, (DMG_CRUSH|DMG_VEHICLE) );
+ TakeDamage( info );
+ }
+ return;
+ }
+
+#endif // HL2_EPISODIC
+//=============================================================================
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Jump at the enemy!! (stole this from the headcrab)
+//
+//
+//-----------------------------------------------------------------------------
+void CFastZombie::LeapAttack( void )
+{
+ SetGroundEntity( NULL );
+
+ BeginAttackJump();
+
+ LeapAttackSound();
+
+ //
+ // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
+ //
+ UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));
+
+ Vector vecJumpDir;
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( pEnemy )
+ {
+ Vector vecEnemyPos = pEnemy->WorldSpaceCenter();
+
+ float gravity = GetCurrentGravity();
+ if ( gravity <= 1 )
+ {
+ gravity = 1;
+ }
+
+ //
+ // How fast does the zombie need to travel to reach my enemy's eyes given gravity?
+ //
+ float height = ( vecEnemyPos.z - GetAbsOrigin().z );
+
+ if ( height < 16 )
+ {
+ height = 16;
+ }
+ else if ( height > 120 )
+ {
+ height = 120;
+ }
+ float speed = sqrt( 2 * gravity * height );
+ float time = speed / gravity;
+
+ //
+ // Scale the sideways velocity to get there at the right time
+ //
+ vecJumpDir = vecEnemyPos - GetAbsOrigin();
+ vecJumpDir = vecJumpDir / time;
+
+ //
+ // Speed to offset gravity at the desired height.
+ //
+ vecJumpDir.z = speed;
+
+ //
+ // Don't jump too far/fast.
+ //
+#define CLAMP 1000.0
+ float distance = vecJumpDir.Length();
+ if ( distance > CLAMP )
+ {
+ vecJumpDir = vecJumpDir * ( CLAMP / distance );
+ }
+
+ // try speeding up a bit.
+ SetAbsVelocity( vecJumpDir );
+ m_flNextAttack = gpGlobals->curtime + 2;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastZombie::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_FASTZOMBIE_VERIFY_ATTACK:
+ // Simply ensure that the zombie still has a valid melee attack
+ if( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ TaskFail("");
+ }
+ break;
+
+ case TASK_FASTZOMBIE_JUMP_BACK:
+ {
+ SetActivity( ACT_IDLE );
+
+ SetGroundEntity( NULL );
+
+ BeginAttackJump();
+
+ Vector forward;
+ AngleVectors( GetLocalAngles(), &forward );
+
+ //
+ // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
+ //
+ UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));
+
+ ApplyAbsVelocityImpulse( forward * -200 + Vector( 0, 0, 200 ) );
+ }
+ break;
+
+ case TASK_FASTZOMBIE_UNSTICK_JUMP:
+ {
+ SetGroundEntity( NULL );
+
+ // Call begin attack jump. A little bit later if we fail to pathfind, we check
+ // this value to see if we just jumped. If so, we assume we've jumped
+ // to someplace that's not pathing friendly, and so must jump again to get out.
+ BeginAttackJump();
+
+ //
+ // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
+ //
+ UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));
+
+ CBaseEntity *pEnemy = GetEnemy();
+ Vector vecJumpDir;
+
+ if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN )
+ {
+ // Jump off the pipe backwards!
+ Vector forward;
+
+ GetVectors( &forward, NULL, NULL );
+
+ ApplyAbsVelocityImpulse( forward * -200 );
+ }
+ else if( pEnemy )
+ {
+ vecJumpDir = pEnemy->GetLocalOrigin() - GetLocalOrigin();
+ VectorNormalize( vecJumpDir );
+ vecJumpDir.z = 0;
+
+ ApplyAbsVelocityImpulse( vecJumpDir * 300 + Vector( 0, 0, 200 ) );
+ }
+ else
+ {
+ DevMsg("UNHANDLED CASE! Stuck Fast Zombie with no enemy!\n");
+ }
+ }
+ break;
+
+ case TASK_WAIT_FOR_MOVEMENT:
+ // If we're waiting for movement, that means that pathfinding succeeded, and
+ // we're about to be moving. So we aren't stuck. So clear this flag.
+ m_fJustJumped = false;
+
+ BaseClass::StartTask( pTask );
+ break;
+
+ case TASK_FACE_ENEMY:
+ {
+ // We don't use the base class implementation of this, because GetTurnActivity
+ // stomps our landing scrabble animations (sjb)
+ Vector flEnemyLKP = GetEnemyLKP();
+ GetMotor()->SetIdealYawToTarget( flEnemyLKP );
+ }
+ break;
+
+ case TASK_FASTZOMBIE_LAND_RECOVER:
+ {
+ // Set the ideal yaw
+ Vector flEnemyLKP = GetEnemyLKP();
+ GetMotor()->SetIdealYawToTarget( flEnemyLKP );
+
+ // figure out which way to turn.
+ float flDeltaYaw = GetMotor()->DeltaIdealYaw();
+
+ if( flDeltaYaw < 0 )
+ {
+ SetIdealActivity( (Activity)ACT_FASTZOMBIE_LAND_RIGHT );
+ }
+ else
+ {
+ SetIdealActivity( (Activity)ACT_FASTZOMBIE_LAND_LEFT );
+ }
+
+
+ TaskComplete();
+ }
+ break;
+
+ case TASK_RANGE_ATTACK1:
+
+ // Make melee attacks impossible until we land!
+ m_flNextMeleeAttack = gpGlobals->curtime + 60;
+
+ SetTouch( &CFastZombie::LeapAttackTouch );
+ break;
+
+ case TASK_FASTZOMBIE_DO_ATTACK:
+ SetActivity( (Activity)ACT_FASTZOMBIE_LEAP_SOAR );
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastZombie::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_FASTZOMBIE_JUMP_BACK:
+ case TASK_FASTZOMBIE_UNSTICK_JUMP:
+ if( GetFlags() & FL_ONGROUND )
+ {
+ TaskComplete();
+ }
+ break;
+
+ case TASK_RANGE_ATTACK1:
+ if( ( GetFlags() & FL_ONGROUND ) || ( m_pfnTouch == NULL ) )
+ {
+ // All done when you touch the ground, or if our touch function has somehow cleared.
+ TaskComplete();
+
+ // Allow melee attacks again.
+ m_flNextMeleeAttack = gpGlobals->curtime + 0.5;
+ return;
+ }
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CFastZombie::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_RANGE_ATTACK1:
+ {
+ // Scream right now, cause in half a second, we're gonna jump!!
+
+ if( !m_fHasScreamed )
+ {
+ // Only play that over-the-top attack scream once per combat state.
+ EmitSound( "NPC_FastZombie.Scream" );
+ m_fHasScreamed = true;
+ }
+ else
+ {
+ EmitSound( "NPC_FastZombie.RangeAttack" );
+ }
+
+ return SCHED_FASTZOMBIE_RANGE_ATTACK1;
+ }
+ break;
+
+ case SCHED_MELEE_ATTACK1:
+ if ( m_fIsTorso == true )
+ {
+ return SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1;
+ }
+ else
+ {
+ return SCHED_FASTZOMBIE_MELEE_ATTACK1;
+ }
+ break;
+
+ case SCHED_FASTZOMBIE_UNSTICK_JUMP:
+ if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT )
+ {
+ return SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP;
+ }
+ else
+ {
+ return SCHED_FASTZOMBIE_UNSTICK_JUMP;
+ }
+ break;
+ case SCHED_MOVE_TO_WEAPON_RANGE:
+ {
+ float flZDist = fabs( GetEnemy()->GetLocalOrigin().z - GetLocalOrigin().z );
+ if ( flZDist > FASTZOMBIE_MAXLEAP_Z )
+ return SCHED_CHASE_ENEMY;
+ else // fall through to default
+ return BaseClass::TranslateSchedule( scheduleType );
+ break;
+ }
+
+ default:
+ return BaseClass::TranslateSchedule( scheduleType );
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+Activity CFastZombie::NPC_TranslateActivity( Activity baseAct )
+{
+ if ( baseAct == ACT_CLIMB_DOWN )
+ return ACT_CLIMB_UP;
+
+ return BaseClass::NPC_TranslateActivity( baseAct );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFastZombie::LeapAttackTouch( CBaseEntity *pOther )
+{
+ if ( !pOther->IsSolid() )
+ {
+ // Touching a trigger or something.
+ return;
+ }
+
+ // Stop the zombie and knock the player back
+ Vector vecNewVelocity( 0, 0, GetAbsVelocity().z );
+ SetAbsVelocity( vecNewVelocity );
+
+ Vector forward;
+ AngleVectors( GetLocalAngles(), &forward );
+ forward *= 500;
+ QAngle qaPunch( 15, random->RandomInt(-5,5), random->RandomInt(-5,5) );
+
+ ClawAttack( GetClawAttackRange(), 5, qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS );
+
+ SetTouch( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Lets us know if we touch the player while we're climbing.
+//-----------------------------------------------------------------------------
+void CFastZombie::ClimbTouch( CBaseEntity *pOther )
+{
+ if ( pOther->IsPlayer() )
+ {
+ // If I hit the player, shove him aside.
+ Vector vecDir = pOther->WorldSpaceCenter() - WorldSpaceCenter();
+ vecDir.z = 0.0; // planar
+ VectorNormalize( vecDir );
+
+ if( IsXbox() )
+ {
+ vecDir *= 400.0f;
+ }
+ else
+ {
+ vecDir *= 200.0f;
+ }
+
+ pOther->VelocityPunch( vecDir );
+
+ if ( GetActivity() != ACT_CLIMB_DISMOUNT ||
+ ( pOther->GetGroundEntity() == NULL &&
+ GetNavigator()->IsGoalActive() &&
+ pOther->GetAbsOrigin().z - GetNavigator()->GetCurWaypointPos().z < -1.0 ) )
+ {
+ SetCondition( COND_FASTZOMBIE_CLIMB_TOUCH );
+ }
+
+ SetTouch( NULL );
+ }
+ else if ( dynamic_cast<CPhysicsProp *>(pOther) )
+ {
+ NPCPhysics_CreateSolver( this, pOther, true, 5.0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Shuts down our looping sounds.
+//-----------------------------------------------------------------------------
+void CFastZombie::StopLoopingSounds( void )
+{
+ if ( m_pMoanSound )
+ {
+ ENVELOPE_CONTROLLER.SoundDestroy( m_pMoanSound );
+ m_pMoanSound = NULL;
+ }
+
+ if ( m_pLayer2 )
+ {
+ ENVELOPE_CONTROLLER.SoundDestroy( m_pLayer2 );
+ m_pLayer2 = NULL;
+ }
+
+ BaseClass::StopLoopingSounds();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Fast zombie cannot range attack when he's a torso!
+//-----------------------------------------------------------------------------
+void CFastZombie::BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce )
+{
+ CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK1 );
+ CapabilitiesRemove( bits_CAP_MOVE_JUMP );
+ CapabilitiesRemove( bits_CAP_MOVE_CLIMB );
+
+ ReleaseHeadcrab( EyePosition(), vecLegsForce * 0.5, true, true, true );
+
+ BaseClass::BecomeTorso( vecTorsoForce, vecLegsForce );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if a reasonable jumping distance
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+bool CFastZombie::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
+{
+ const float MAX_JUMP_RISE = 220.0f;
+ const float MAX_JUMP_DISTANCE = 512.0f;
+ const float MAX_JUMP_DROP = 384.0f;
+
+ if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
+ {
+ // Hang onto the jump distance. The AI is going to want it.
+ m_flJumpDist = (startPos - endPos).Length();
+
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+bool CFastZombie::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
+{
+ float delta = vecEnd.z - vecStart.z;
+
+ float multiplier = 1;
+ if ( moveType == bits_CAP_MOVE_JUMP )
+ {
+ multiplier = ( delta < 0 ) ? 0.5 : 1.5;
+ }
+ else if ( moveType == bits_CAP_MOVE_CLIMB )
+ {
+ multiplier = ( delta > 0 ) ? 0.5 : 4.0;
+ }
+
+ *pCost *= multiplier;
+
+ return ( multiplier != 1 );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+
+bool CFastZombie::ShouldFailNav( bool bMovementFailed )
+{
+ if ( !BaseClass::ShouldFailNav( bMovementFailed ) )
+ {
+ DevMsg( 2, "Fast zombie in scripted sequence probably hit bad node configuration at %s\n", VecToString( GetAbsOrigin() ) );
+
+ if ( GetNavigator()->GetPath()->CurWaypointNavType() == NAV_JUMP && GetNavigator()->RefindPathToGoal( false ) )
+ {
+ return false;
+ }
+ DevMsg( 2, "Fast zombie failed to get to scripted sequence\n" );
+ }
+
+ return true;
+}
+
+
+//---------------------------------------------------------
+// Purpose: Notifier that lets us know when the fast
+// zombie has hit the apex of a navigational jump.
+//---------------------------------------------------------
+void CFastZombie::OnNavJumpHitApex( void )
+{
+ m_fHitApex = true; // stop subsequent notifications
+}
+
+//---------------------------------------------------------
+// Purpose: Overridden to detect when the zombie goes into
+// and out of his climb state and his navigation
+// jump state.
+//---------------------------------------------------------
+void CFastZombie::OnChangeActivity( Activity NewActivity )
+{
+ if ( NewActivity == ACT_FASTZOMBIE_FRENZY )
+ {
+ // Scream!!!!
+ EmitSound( "NPC_FastZombie.Frenzy" );
+ SetPlaybackRate( random->RandomFloat( .9, 1.1 ) );
+ }
+
+ if( NewActivity == ACT_JUMP )
+ {
+ BeginNavJump();
+ }
+ else if( GetActivity() == ACT_JUMP )
+ {
+ EndNavJump();
+ }
+
+ if ( NewActivity == ACT_LAND )
+ {
+ m_flNextAttack = gpGlobals->curtime + 1.0;
+ }
+
+ if ( NewActivity == ACT_GLIDE )
+ {
+ // Started a jump.
+ BeginNavJump();
+ }
+ else if ( GetActivity() == ACT_GLIDE )
+ {
+ // Landed a jump
+ EndNavJump();
+
+ if ( m_pMoanSound )
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_MIN_PITCH, 0.3 );
+ }
+
+ if ( NewActivity == ACT_CLIMB_UP )
+ {
+ // Started a climb!
+ if ( m_pMoanSound )
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0.0, 0.2 );
+
+ SetTouch( &CFastZombie::ClimbTouch );
+ }
+ else if ( GetActivity() == ACT_CLIMB_DISMOUNT || ( GetActivity() == ACT_CLIMB_UP && NewActivity != ACT_CLIMB_DISMOUNT ) )
+ {
+ // Ended a climb
+ if ( m_pMoanSound )
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1.0, 0.2 );
+
+ SetTouch( NULL );
+ }
+
+ BaseClass::OnChangeActivity( NewActivity );
+}
+
+
+//=========================================================
+//
+//=========================================================
+int CFastZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ if ( m_fJustJumped )
+ {
+ // Assume we failed cause we jumped to a bad place.
+ m_fJustJumped = false;
+ return SCHED_FASTZOMBIE_UNSTICK_JUMP;
+ }
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+//=========================================================
+// Purpose: Do some record keeping for jumps made for
+// navigational purposes (i.e., not attack jumps)
+//=========================================================
+void CFastZombie::BeginNavJump( void )
+{
+ m_fIsNavJumping = true;
+ m_fHitApex = false;
+
+ ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumeJump, ARRAYSIZE(envFastZombieVolumeJump) );
+}
+
+//=========================================================
+//
+//=========================================================
+void CFastZombie::EndNavJump( void )
+{
+ m_fIsNavJumping = false;
+ m_fHitApex = false;
+}
+
+//=========================================================
+//
+//=========================================================
+void CFastZombie::BeginAttackJump( void )
+{
+ // Set this to true. A little bit later if we fail to pathfind, we check
+ // this value to see if we just jumped. If so, we assume we've jumped
+ // to someplace that's not pathing friendly, and so must jump again to get out.
+ m_fJustJumped = true;
+
+ m_flJumpStartAltitude = GetLocalOrigin().z;
+}
+
+//=========================================================
+//
+//=========================================================
+void CFastZombie::EndAttackJump( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFastZombie::BuildScheduleTestBits( void )
+{
+ // FIXME: This is probably the desired call to make, but it opts into an untested base class path, we'll need to
+ // revisit this and figure out if we want that. -- jdw
+ // BaseClass::BuildScheduleTestBits();
+ //
+ // For now, make sure our active behavior gets a chance to add its own bits
+ if ( GetRunningBehavior() )
+ GetRunningBehavior()->BridgeBuildScheduleTestBits();
+
+#ifdef HL2_EPISODIC
+ SetCustomInterruptCondition( COND_PROVOKED );
+#endif // HL2_EPISODIC
+
+ // Any schedule that makes us climb should break if we touch player
+ if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT)
+ {
+ SetCustomInterruptCondition( COND_FASTZOMBIE_CLIMB_TOUCH );
+ }
+ else
+ {
+ ClearCustomInterruptCondition( COND_FASTZOMBIE_CLIMB_TOUCH );
+ }
+}
+
+//=========================================================
+//
+//=========================================================
+void CFastZombie::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
+{
+ if( NewState == NPC_STATE_COMBAT )
+ {
+ SetAngrySoundState();
+ }
+ else if( (m_pMoanSound) && ( NewState == NPC_STATE_IDLE || NewState == NPC_STATE_ALERT ) ) ///!!!HACKHACK - sjb
+ {
+ // Don't make this sound while we're slumped
+ if ( IsSlumped() == false )
+ {
+ // Set it up so that if the zombie goes into combat state sometime down the road
+ // that he'll be able to scream.
+ m_fHasScreamed = false;
+
+ SetIdleSoundState();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CFastZombie::Event_Killed( const CTakeDamageInfo &info )
+{
+ // Shut up my screaming sounds.
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_FastZombie.NoSound" );
+
+ CTakeDamageInfo dInfo = info;
+
+#if 0
+
+ // Become a server-side ragdoll and create a constraint at the hand
+ if ( m_PassengerBehavior.GetPassengerState() == PASSENGER_STATE_INSIDE )
+ {
+ IPhysicsObject *pVehiclePhys = m_PassengerBehavior.GetTargetVehicle()->GetServerVehicle()->GetVehicleEnt()->VPhysicsGetObject();
+ CBaseAnimating *pVehicleAnimating = m_PassengerBehavior.GetTargetVehicle()->GetServerVehicle()->GetVehicleEnt()->GetBaseAnimating();
+ int nRightHandBone = 31;//GetBaseAnimating()->LookupBone( "ValveBiped.Bip01_R_Finger2" );
+ Vector vecRightHandPos;
+ QAngle vecRightHandAngle;
+ GetAttachment( LookupAttachment( "Blood_Right" ), vecRightHandPos, vecRightHandAngle );
+ //CTakeDamageInfo dInfo( GetEnemy(), GetEnemy(), RandomVector( -200, 200 ), WorldSpaceCenter(), 50.0f, DMG_CRUSH );
+ dInfo.SetDamageType( info.GetDamageType() | DMG_REMOVENORAGDOLL );
+ dInfo.ScaleDamageForce( 10.0f );
+ CBaseEntity *pRagdoll = CreateServerRagdoll( GetBaseAnimating(), 0, info, COLLISION_GROUP_DEBRIS );
+
+ /*
+ GetBaseAnimating()->GetBonePosition( nRightHandBone, vecRightHandPos, vecRightHandAngle );
+
+ CBaseEntity *pRagdoll = CreateServerRagdollAttached( GetBaseAnimating(),
+ vec3_origin,
+ -1,
+ COLLISION_GROUP_DEBRIS,
+ pVehiclePhys,
+ pVehicleAnimating,
+ 0,
+ vecRightHandPos,
+ nRightHandBone,
+ vec3_origin );*/
+
+ }
+#endif
+
+ BaseClass::Event_Killed( dInfo );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CFastZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold )
+{
+ if( m_fIsTorso )
+ {
+ // Already split.
+ return false;
+ }
+
+ // Break in half IF:
+ //
+ // Take half or more of max health in DMG_BLAST
+ if( (info.GetDamageType() & DMG_BLAST) && m_iHealth <= 0 )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+//=============================================================================
+#ifdef HL2_EPISODIC
+
+//-----------------------------------------------------------------------------
+// Purpose: Add the passenger behavior to our repertoire
+//-----------------------------------------------------------------------------
+bool CFastZombie::CreateBehaviors( void )
+{
+ AddBehavior( &m_PassengerBehavior );
+
+ return BaseClass::CreateBehaviors();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get on the vehicle!
+//-----------------------------------------------------------------------------
+void CFastZombie::InputAttachToVehicle( inputdata_t &inputdata )
+{
+ // Interrupt us
+ SetCondition( COND_PROVOKED );
+
+ // Find the target vehicle
+ CBaseEntity *pEntity = FindNamedEntity( inputdata.value.String() );
+ CPropJeepEpisodic *pVehicle = dynamic_cast<CPropJeepEpisodic *>(pEntity);
+
+ // Get in the car if it's valid
+ if ( pVehicle && CanEnterVehicle( pVehicle ) )
+ {
+ // Set her into a "passenger" behavior
+ m_PassengerBehavior.Enable( pVehicle );
+ m_PassengerBehavior.AttachToVehicle();
+ }
+
+ RemoveSpawnFlags( SF_NPC_GAG );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Passed along from the vehicle's callback list
+//-----------------------------------------------------------------------------
+void CFastZombie::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
+{
+ // Only do the override while riding on a vehicle
+ if ( m_PassengerBehavior.CanSelectSchedule() && m_PassengerBehavior.GetPassengerState() != PASSENGER_STATE_OUTSIDE )
+ {
+ int damageType = 0;
+ float flDamage = CalculatePhysicsImpactDamage( index, pEvent, gZombiePassengerImpactDamageTable, 1.0, true, damageType );
+
+ if ( flDamage > 0 )
+ {
+ Vector damagePos;
+ pEvent->pInternalData->GetContactPoint( damagePos );
+ Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
+ CTakeDamageInfo info( this, this, damageForce, damagePos, flDamage, (damageType|DMG_VEHICLE) );
+ TakeDamage( info );
+ }
+ return;
+ }
+
+ BaseClass::VPhysicsCollision( index, pEvent );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: FIXME: Fold this into LeapAttack using different jump targets!
+//-----------------------------------------------------------------------------
+void CFastZombie::VehicleLeapAttack( void )
+{
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( pEnemy == NULL )
+ return;
+
+ Vector vecEnemyPos;
+ UTIL_PredictedPosition( pEnemy, 1.0f, &vecEnemyPos );
+
+ // Move
+ SetGroundEntity( NULL );
+ BeginAttackJump();
+ LeapAttackSound();
+
+ // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
+ UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));
+
+ // FIXME: This should be the exact position we'll enter at, but this approximates it generally
+ //vecEnemyPos[2] += 16;
+
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ Vector vecJumpDir = VecCheckToss( this, GetAbsOrigin(), vecEnemyPos, 0.1f, 1.0f, false, &vecMins, &vecMaxs );
+
+ SetAbsVelocity( vecJumpDir );
+ m_flNextAttack = gpGlobals->curtime + 2.0f;
+ SetTouch( &CFastZombie::VehicleLeapAttackTouch );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CFastZombie::CanEnterVehicle( CPropJeepEpisodic *pVehicle )
+{
+ if ( pVehicle == NULL )
+ return false;
+
+ return pVehicle->NPC_CanEnterVehicle( this, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: FIXME: Move into behavior?
+// Input : *pOther -
+//-----------------------------------------------------------------------------
+void CFastZombie::VehicleLeapAttackTouch( CBaseEntity *pOther )
+{
+ if ( pOther->GetServerVehicle() )
+ {
+ m_PassengerBehavior.AttachToVehicle();
+
+ // HACK: Stop us cold
+ SetLocalVelocity( vec3_origin );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine whether we're in a vehicle or not
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CFastZombie::IsInAVehicle( void )
+{
+ // Must be active and getting in/out of vehicle
+ if ( m_PassengerBehavior.IsEnabled() && m_PassengerBehavior.GetPassengerState() != PASSENGER_STATE_OUTSIDE )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override our efficiency so that we don't jitter when we're in the middle
+// of our enter/exit animations.
+// Input : bInPVS - Whether we're in the PVS or not
+//-----------------------------------------------------------------------------
+void CFastZombie::UpdateEfficiency( bool bInPVS )
+{
+ // If we're transitioning and in the PVS, we override our efficiency
+ if ( IsInAVehicle() && bInPVS )
+ {
+ PassengerState_e nState = m_PassengerBehavior.GetPassengerState();
+ if ( nState == PASSENGER_STATE_ENTERING || nState == PASSENGER_STATE_EXITING )
+ {
+ SetEfficiency( AIE_NORMAL );
+ return;
+ }
+ }
+
+ // Do the default behavior
+ BaseClass::UpdateEfficiency( bInPVS );
+}
+
+#endif // HL2_EPISODIC
+//=============================================================================
+
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_fastzombie, CFastZombie )
+
+ DECLARE_ACTIVITY( ACT_FASTZOMBIE_LEAP_SOAR )
+ DECLARE_ACTIVITY( ACT_FASTZOMBIE_LEAP_STRIKE )
+ DECLARE_ACTIVITY( ACT_FASTZOMBIE_LAND_RIGHT )
+ DECLARE_ACTIVITY( ACT_FASTZOMBIE_LAND_LEFT )
+ DECLARE_ACTIVITY( ACT_FASTZOMBIE_FRENZY )
+ DECLARE_ACTIVITY( ACT_FASTZOMBIE_BIG_SLASH )
+
+ DECLARE_TASK( TASK_FASTZOMBIE_DO_ATTACK )
+ DECLARE_TASK( TASK_FASTZOMBIE_LAND_RECOVER )
+ DECLARE_TASK( TASK_FASTZOMBIE_UNSTICK_JUMP )
+ DECLARE_TASK( TASK_FASTZOMBIE_JUMP_BACK )
+ DECLARE_TASK( TASK_FASTZOMBIE_VERIFY_ATTACK )
+
+ DECLARE_CONDITION( COND_FASTZOMBIE_CLIMB_TOUCH )
+
+ //Adrian: events go here
+ DECLARE_ANIMEVENT( AE_FASTZOMBIE_LEAP )
+ DECLARE_ANIMEVENT( AE_FASTZOMBIE_GALLOP_LEFT )
+ DECLARE_ANIMEVENT( AE_FASTZOMBIE_GALLOP_RIGHT )
+ DECLARE_ANIMEVENT( AE_FASTZOMBIE_CLIMB_LEFT )
+ DECLARE_ANIMEVENT( AE_FASTZOMBIE_CLIMB_RIGHT )
+
+#ifdef HL2_EPISODIC
+ // FIXME: Move!
+ DECLARE_ANIMEVENT( AE_PASSENGER_PHYSICS_PUSH )
+ DECLARE_ANIMEVENT( AE_FASTZOMBIE_VEHICLE_LEAP )
+ DECLARE_ANIMEVENT( AE_FASTZOMBIE_VEHICLE_SS_DIE )
+#endif // HL2_EPISODIC
+
+ //=========================================================
+ //
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FASTZOMBIE_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK1"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_FASTZOMBIE_LEAP_STRIKE"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_WAIT 0.1"
+ " TASK_FASTZOMBIE_LAND_RECOVER 0" // essentially just figure out which way to turn.
+ " TASK_FACE_ENEMY 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // I have landed somewhere that's pathfinding-unfriendly
+ // just try to jump out.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FASTZOMBIE_UNSTICK_JUMP,
+
+ " Tasks"
+ " TASK_FASTZOMBIE_UNSTICK_JUMP 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FASTZOMBIE_UNSTICK_JUMP 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > Melee_Attack1
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FASTZOMBIE_MELEE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_MELEE_ATTACK1 0"
+ " TASK_MELEE_ATTACK1 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_FASTZOMBIE_FRENZY"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
+ " TASK_FASTZOMBIE_VERIFY_ATTACK 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_FASTZOMBIE_BIG_SLASH"
+
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_OCCLUDED"
+ );
+
+ //=========================================================
+ // > Melee_Attack1
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_MELEE_ATTACK1 0"
+ " TASK_MELEE_ATTACK1 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
+ " TASK_FASTZOMBIE_VERIFY_ATTACK 0"
+
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_OCCLUDED"
+ );
+
+AI_END_CUSTOM_NPC()
+
+
|