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