diff options
Diffstat (limited to 'mp/src/game/server/hl2/npc_fastzombie.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/npc_fastzombie.cpp | 4312 |
1 files changed, 2156 insertions, 2156 deletions
diff --git a/mp/src/game/server/hl2/npc_fastzombie.cpp b/mp/src/game/server/hl2/npc_fastzombie.cpp index 1a00a67a..0ecd7155 100644 --- a/mp/src/game/server/hl2/npc_fastzombie.cpp +++ b/mp/src/game/server/hl2/npc_fastzombie.cpp @@ -1,2156 +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()
-
-
+//========= 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() + + |