aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/server/hl2/npc_zombie.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/server/hl2/npc_zombie.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/server/hl2/npc_zombie.cpp')
-rw-r--r--sp/src/game/server/hl2/npc_zombie.cpp2002
1 files changed, 1001 insertions, 1001 deletions
diff --git a/sp/src/game/server/hl2/npc_zombie.cpp b/sp/src/game/server/hl2/npc_zombie.cpp
index 399c11cd..6762c45c 100644
--- a/sp/src/game/server/hl2/npc_zombie.cpp
+++ b/sp/src/game/server/hl2/npc_zombie.cpp
@@ -1,1001 +1,1001 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: A slow-moving, once-human headcrab victim with only melee attacks.
-//
-//=============================================================================//
-
-#include "cbase.h"
-
-#include "doors.h"
-
-#include "simtimer.h"
-#include "npc_BaseZombie.h"
-#include "ai_hull.h"
-#include "ai_navigator.h"
-#include "ai_memory.h"
-#include "gib.h"
-#include "soundenvelope.h"
-#include "engine/IEngineSound.h"
-#include "ammodef.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-// ACT_FLINCH_PHYSICS
-
-
-ConVar sk_zombie_health( "sk_zombie_health","0");
-
-envelopePoint_t envZombieMoanVolumeFast[] =
-{
- { 7.0f, 7.0f,
- 0.1f, 0.1f,
- },
- { 0.0f, 0.0f,
- 0.2f, 0.3f,
- },
-};
-
-envelopePoint_t envZombieMoanVolume[] =
-{
- { 1.0f, 1.0f,
- 0.1f, 0.1f,
- },
- { 1.0f, 1.0f,
- 0.2f, 0.2f,
- },
- { 0.0f, 0.0f,
- 0.3f, 0.4f,
- },
-};
-
-envelopePoint_t envZombieMoanVolumeLong[] =
-{
- { 1.0f, 1.0f,
- 0.3f, 0.5f,
- },
- { 1.0f, 1.0f,
- 0.6f, 1.0f,
- },
- { 0.0f, 0.0f,
- 0.3f, 0.4f,
- },
-};
-
-envelopePoint_t envZombieMoanIgnited[] =
-{
- { 1.0f, 1.0f,
- 0.5f, 1.0f,
- },
- { 1.0f, 1.0f,
- 30.0f, 30.0f,
- },
- { 0.0f, 0.0f,
- 0.5f, 1.0f,
- },
-};
-
-
-//=============================================================================
-//=============================================================================
-
-class CZombie : public CAI_BlendingHost<CNPC_BaseZombie>
-{
- DECLARE_DATADESC();
- DECLARE_CLASS( CZombie, CAI_BlendingHost<CNPC_BaseZombie> );
-
-public:
- CZombie()
- : m_DurationDoorBash( 2, 6),
- m_NextTimeToStartDoorBash( 3.0 )
- {
- }
-
- void Spawn( void );
- void Precache( void );
-
- void SetZombieModel( void );
- void MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize );
- bool ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold );
- bool CanBecomeLiveTorso() { return !m_fIsHeadless; }
-
- void GatherConditions( void );
-
- int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
- int TranslateSchedule( int scheduleType );
-
-#ifndef HL2_EPISODIC
- void CheckFlinches() {} // Zombie has custom flinch code
-#endif // HL2_EPISODIC
-
- Activity NPC_TranslateActivity( Activity newActivity );
-
- void OnStateChange( NPC_STATE OldState, NPC_STATE NewState );
-
- void StartTask( const Task_t *pTask );
- void RunTask( const Task_t *pTask );
-
- virtual const char *GetLegsModel( void );
- virtual const char *GetTorsoModel( void );
- virtual const char *GetHeadcrabClassname( void );
- virtual const char *GetHeadcrabModel( void );
-
- virtual bool OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal,
- CBaseDoor *pDoor,
- float distClear,
- AIMoveResult_t *pResult );
-
- Activity SelectDoorBash();
-
- void Ignite( float flFlameLifetime, bool bNPCOnly = true, float flSize = 0.0f, bool bCalledByLevelDesigner = false );
- void Extinguish();
- int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
- bool IsHeavyDamage( const CTakeDamageInfo &info );
- bool IsSquashed( const CTakeDamageInfo &info );
- void BuildScheduleTestBits( void );
-
- void PrescheduleThink( void );
- int SelectSchedule ( void );
-
- 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 );
-
- const char *GetMoanSound( int nSound );
-
-public:
- DEFINE_CUSTOM_AI;
-
-protected:
- static const char *pMoanSounds[];
-
-
-private:
- CHandle< CBaseDoor > m_hBlockingDoor;
- float m_flDoorBashYaw;
-
- CRandSimTimer m_DurationDoorBash;
- CSimTimer m_NextTimeToStartDoorBash;
-
- Vector m_vPositionCharged;
-};
-
-LINK_ENTITY_TO_CLASS( npc_zombie, CZombie );
-LINK_ENTITY_TO_CLASS( npc_zombie_torso, CZombie );
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-const char *CZombie::pMoanSounds[] =
-{
- "NPC_BaseZombie.Moan1",
- "NPC_BaseZombie.Moan2",
- "NPC_BaseZombie.Moan3",
- "NPC_BaseZombie.Moan4",
-};
-
-//=========================================================
-// Conditions
-//=========================================================
-enum
-{
- COND_BLOCKED_BY_DOOR = LAST_BASE_ZOMBIE_CONDITION,
- COND_DOOR_OPENED,
- COND_ZOMBIE_CHARGE_TARGET_MOVED,
-};
-
-//=========================================================
-// Schedules
-//=========================================================
-enum
-{
- SCHED_ZOMBIE_BASH_DOOR = LAST_BASE_ZOMBIE_SCHEDULE,
- SCHED_ZOMBIE_WANDER_ANGRILY,
- SCHED_ZOMBIE_CHARGE_ENEMY,
- SCHED_ZOMBIE_FAIL,
-};
-
-//=========================================================
-// Tasks
-//=========================================================
-enum
-{
- TASK_ZOMBIE_EXPRESS_ANGER = LAST_BASE_ZOMBIE_TASK,
- TASK_ZOMBIE_YAW_TO_DOOR,
- TASK_ZOMBIE_ATTACK_DOOR,
- TASK_ZOMBIE_CHARGE_ENEMY,
-};
-
-//-----------------------------------------------------------------------------
-
-int ACT_ZOMBIE_TANTRUM;
-int ACT_ZOMBIE_WALLPOUND;
-
-BEGIN_DATADESC( CZombie )
-
- DEFINE_FIELD( m_hBlockingDoor, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flDoorBashYaw, FIELD_FLOAT ),
- DEFINE_EMBEDDED( m_DurationDoorBash ),
- DEFINE_EMBEDDED( m_NextTimeToStartDoorBash ),
- DEFINE_FIELD( m_vPositionCharged, FIELD_POSITION_VECTOR ),
-
-END_DATADESC()
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CZombie::Precache( void )
-{
- BaseClass::Precache();
-
- PrecacheModel( "models/zombie/classic.mdl" );
- PrecacheModel( "models/zombie/classic_torso.mdl" );
- PrecacheModel( "models/zombie/classic_legs.mdl" );
-
- PrecacheScriptSound( "Zombie.FootstepRight" );
- PrecacheScriptSound( "Zombie.FootstepLeft" );
- PrecacheScriptSound( "Zombie.FootstepLeft" );
- PrecacheScriptSound( "Zombie.ScuffRight" );
- PrecacheScriptSound( "Zombie.ScuffLeft" );
- PrecacheScriptSound( "Zombie.AttackHit" );
- PrecacheScriptSound( "Zombie.AttackMiss" );
- PrecacheScriptSound( "Zombie.Pain" );
- PrecacheScriptSound( "Zombie.Die" );
- PrecacheScriptSound( "Zombie.Alert" );
- PrecacheScriptSound( "Zombie.Idle" );
- PrecacheScriptSound( "Zombie.Attack" );
-
- PrecacheScriptSound( "NPC_BaseZombie.Moan1" );
- PrecacheScriptSound( "NPC_BaseZombie.Moan2" );
- PrecacheScriptSound( "NPC_BaseZombie.Moan3" );
- PrecacheScriptSound( "NPC_BaseZombie.Moan4" );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CZombie::Spawn( void )
-{
- Precache();
-
- if( FClassnameIs( this, "npc_zombie" ) )
- {
- m_fIsTorso = false;
- }
- else
- {
- // This was placed as an npc_zombie_torso
- m_fIsTorso = true;
- }
-
- m_fIsHeadless = false;
-
-#ifdef HL2_EPISODIC
- SetBloodColor( BLOOD_COLOR_ZOMBIE );
-#else
- SetBloodColor( BLOOD_COLOR_GREEN );
-#endif // HL2_EPISODIC
-
- m_iHealth = sk_zombie_health.GetFloat();
- m_flFieldOfView = 0.2;
-
- CapabilitiesClear();
-
- //GetNavigator()->SetRememberStaleNodes( false );
-
- BaseClass::Spawn();
-
- m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 1.0, 4.0 );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CZombie::PrescheduleThink( void )
-{
- if( gpGlobals->curtime > m_flNextMoanSound )
- {
- if( CanPlayMoanSound() )
- {
- // Classic guy idles instead of moans.
- IdleSound();
-
- m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 2.0, 5.0 );
- }
- else
- {
- m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 1.0, 2.0 );
- }
- }
-
- BaseClass::PrescheduleThink();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-int CZombie::SelectSchedule ( void )
-{
- if( HasCondition( COND_PHYSICS_DAMAGE ) && !m_ActBusyBehavior.IsActive() )
- {
- return SCHED_FLINCH_PHYSICS;
- }
-
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sound of a footstep
-//-----------------------------------------------------------------------------
-void CZombie::FootstepSound( bool fRightFoot )
-{
- if( fRightFoot )
- {
- EmitSound( "Zombie.FootstepRight" );
- }
- else
- {
- EmitSound( "Zombie.FootstepLeft" );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sound of a foot sliding/scraping
-//-----------------------------------------------------------------------------
-void CZombie::FootscuffSound( bool fRightFoot )
-{
- if( fRightFoot )
- {
- EmitSound( "Zombie.ScuffRight" );
- }
- else
- {
- EmitSound( "Zombie.ScuffLeft" );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Play a random attack hit sound
-//-----------------------------------------------------------------------------
-void CZombie::AttackHitSound( void )
-{
- EmitSound( "Zombie.AttackHit" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Play a random attack miss sound
-//-----------------------------------------------------------------------------
-void CZombie::AttackMissSound( void )
-{
- // Play a random attack miss sound
- EmitSound( "Zombie.AttackMiss" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CZombie::PainSound( const CTakeDamageInfo &info )
-{
- // We're constantly taking damage when we are on fire. Don't make all those noises!
- if ( IsOnFire() )
- {
- return;
- }
-
- EmitSound( "Zombie.Pain" );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CZombie::DeathSound( const CTakeDamageInfo &info )
-{
- EmitSound( "Zombie.Die" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CZombie::AlertSound( void )
-{
- EmitSound( "Zombie.Alert" );
-
- // Don't let a moan sound cut off the alert sound.
- m_flNextMoanSound += random->RandomFloat( 2.0, 4.0 );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns a moan sound for this class of zombie.
-//-----------------------------------------------------------------------------
-const char *CZombie::GetMoanSound( int nSound )
-{
- return pMoanSounds[ nSound % ARRAYSIZE( pMoanSounds ) ];
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Play a random idle sound.
-//-----------------------------------------------------------------------------
-void CZombie::IdleSound( void )
-{
- if( GetState() == NPC_STATE_IDLE && random->RandomFloat( 0, 1 ) == 0 )
- {
- // Moan infrequently in IDLE state.
- return;
- }
-
- if( IsSlumped() )
- {
- // Sleeping zombies are quiet.
- return;
- }
-
- EmitSound( "Zombie.Idle" );
- MakeAISpookySound( 360.0f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Play a random attack sound.
-//-----------------------------------------------------------------------------
-void CZombie::AttackSound( void )
-{
- EmitSound( "Zombie.Attack" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns the classname (ie "npc_headcrab") to spawn when our headcrab bails.
-//-----------------------------------------------------------------------------
-const char *CZombie::GetHeadcrabClassname( void )
-{
- return "npc_headcrab";
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-const char *CZombie::GetHeadcrabModel( void )
-{
- return "models/headcrabclassic.mdl";
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-const char *CZombie::GetLegsModel( void )
-{
- return "models/zombie/classic_legs.mdl";
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-const char *CZombie::GetTorsoModel( void )
-{
- return "models/zombie/classic_torso.mdl";
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CZombie::SetZombieModel( void )
-{
- Hull_t lastHull = GetHullType();
-
- if ( m_fIsTorso )
- {
- SetModel( "models/zombie/classic_torso.mdl" );
- SetHullType( HULL_TINY );
- }
- else
- {
- SetModel( "models/zombie/classic.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();
- }
- }
-}
-
-//---------------------------------------------------------
-// Classic zombie only uses moan sound if on fire.
-//---------------------------------------------------------
-void CZombie::MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize )
-{
- if( IsOnFire() )
- {
- BaseClass::MoanSound( pEnvelope, iEnvelopeSize );
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-bool CZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold )
-{
- if( IsSlumped() )
- {
- // Never break apart a slouched zombie. This is because the most fun
- // slouched zombies to kill are ones sleeping leaning against explosive
- // barrels. If you break them in half in the blast, the force of being
- // so close to the explosion makes the body pieces fly at ridiculous
- // velocities because the pieces weigh less than the whole.
- return false;
- }
-
- return BaseClass::ShouldBecomeTorso( info, flDamageThreshold );
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CZombie::GatherConditions( void )
-{
- BaseClass::GatherConditions();
-
- static int conditionsToClear[] =
- {
- COND_BLOCKED_BY_DOOR,
- COND_DOOR_OPENED,
- COND_ZOMBIE_CHARGE_TARGET_MOVED,
- };
-
- ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
-
- if ( m_hBlockingDoor == NULL ||
- ( m_hBlockingDoor->m_toggle_state == TS_AT_TOP ||
- m_hBlockingDoor->m_toggle_state == TS_GOING_UP ) )
- {
- ClearCondition( COND_BLOCKED_BY_DOOR );
- if ( m_hBlockingDoor != NULL )
- {
- SetCondition( COND_DOOR_OPENED );
- m_hBlockingDoor = NULL;
- }
- }
- else
- SetCondition( COND_BLOCKED_BY_DOOR );
-
- if ( ConditionInterruptsCurSchedule( COND_ZOMBIE_CHARGE_TARGET_MOVED ) )
- {
- if ( GetNavigator()->IsGoalActive() )
- {
- const float CHARGE_RESET_TOLERANCE = 60.0;
- if ( !GetEnemy() ||
- ( m_vPositionCharged - GetEnemyLKP() ).Length() > CHARGE_RESET_TOLERANCE )
- {
- SetCondition( COND_ZOMBIE_CHARGE_TARGET_MOVED );
- }
-
- }
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-
-int CZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
-{
- if ( HasCondition( COND_BLOCKED_BY_DOOR ) && m_hBlockingDoor != NULL )
- {
- ClearCondition( COND_BLOCKED_BY_DOOR );
- if ( m_NextTimeToStartDoorBash.Expired() && failedSchedule != SCHED_ZOMBIE_BASH_DOOR )
- return SCHED_ZOMBIE_BASH_DOOR;
- m_hBlockingDoor = NULL;
- }
-
- if ( failedSchedule != SCHED_ZOMBIE_CHARGE_ENEMY &&
- IsPathTaskFailure( taskFailCode ) &&
- random->RandomInt( 1, 100 ) < 50 )
- {
- return SCHED_ZOMBIE_CHARGE_ENEMY;
- }
-
- if ( failedSchedule != SCHED_ZOMBIE_WANDER_ANGRILY &&
- ( failedSchedule == SCHED_TAKE_COVER_FROM_ENEMY ||
- failedSchedule == SCHED_CHASE_ENEMY_FAILED ) )
- {
- return SCHED_ZOMBIE_WANDER_ANGRILY;
- }
-
- return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-
-int CZombie::TranslateSchedule( int scheduleType )
-{
- if ( scheduleType == SCHED_COMBAT_FACE && IsUnreachable( GetEnemy() ) )
- return SCHED_TAKE_COVER_FROM_ENEMY;
-
- if ( !m_fIsTorso && scheduleType == SCHED_FAIL )
- return SCHED_ZOMBIE_FAIL;
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-//---------------------------------------------------------
-
-Activity CZombie::NPC_TranslateActivity( Activity newActivity )
-{
- newActivity = BaseClass::NPC_TranslateActivity( newActivity );
-
- if ( newActivity == ACT_RUN )
- return ACT_WALK;
-
- if ( m_fIsTorso && ( newActivity == ACT_ZOMBIE_TANTRUM ) )
- return ACT_IDLE;
-
- return newActivity;
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CZombie::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
-{
- BaseClass::OnStateChange( OldState, NewState );
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-
-void CZombie::StartTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_ZOMBIE_EXPRESS_ANGER:
- {
- if ( random->RandomInt( 1, 4 ) == 2 )
- {
- SetIdealActivity( (Activity)ACT_ZOMBIE_TANTRUM );
- }
- else
- {
- TaskComplete();
- }
-
- break;
- }
-
- case TASK_ZOMBIE_YAW_TO_DOOR:
- {
- AssertMsg( m_hBlockingDoor != NULL, "Expected condition handling to break schedule before landing here" );
- if ( m_hBlockingDoor != NULL )
- {
- GetMotor()->SetIdealYaw( m_flDoorBashYaw );
- }
- TaskComplete();
- break;
- }
-
- case TASK_ZOMBIE_ATTACK_DOOR:
- {
- m_DurationDoorBash.Reset();
- SetIdealActivity( SelectDoorBash() );
- break;
- }
-
- case TASK_ZOMBIE_CHARGE_ENEMY:
- {
- if ( !GetEnemy() )
- TaskFail( FAIL_NO_ENEMY );
- else if ( GetNavigator()->SetVectorGoalFromTarget( GetEnemy()->GetLocalOrigin() ) )
- {
- m_vPositionCharged = GetEnemy()->GetLocalOrigin();
- TaskComplete();
- }
- else
- TaskFail( FAIL_NO_ROUTE );
- break;
- }
-
- default:
- BaseClass::StartTask( pTask );
- break;
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-
-void CZombie::RunTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_ZOMBIE_ATTACK_DOOR:
- {
- if ( IsActivityFinished() )
- {
- if ( m_DurationDoorBash.Expired() )
- {
- TaskComplete();
- m_NextTimeToStartDoorBash.Reset();
- }
- else
- ResetIdealActivity( SelectDoorBash() );
- }
- break;
- }
-
- case TASK_ZOMBIE_CHARGE_ENEMY:
- {
- break;
- }
-
- case TASK_ZOMBIE_EXPRESS_ANGER:
- {
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- default:
- BaseClass::RunTask( pTask );
- break;
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-
-bool CZombie::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor,
- float distClear, AIMoveResult_t *pResult )
-{
- if ( BaseClass::OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) )
- {
- if ( IsMoveBlocked( *pResult ) && pMoveGoal->directTrace.vHitNormal != vec3_origin )
- {
- m_hBlockingDoor = pDoor;
- m_flDoorBashYaw = UTIL_VecToYaw( pMoveGoal->directTrace.vHitNormal * -1 );
- }
- return true;
- }
-
- return false;
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-
-Activity CZombie::SelectDoorBash()
-{
- if ( random->RandomInt( 1, 3 ) == 1 )
- return ACT_MELEE_ATTACK1;
- return (Activity)ACT_ZOMBIE_WALLPOUND;
-}
-
-//---------------------------------------------------------
-// Zombies should scream continuously while burning, so long
-// as they are alive... but NOT IN GERMANY!
-//---------------------------------------------------------
-void CZombie::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
-{
- if( !IsOnFire() && IsAlive() )
- {
- BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
-
- if ( !UTIL_IsLowViolence() )
- {
- RemoveSpawnFlags( SF_NPC_GAG );
-
- MoanSound( envZombieMoanIgnited, ARRAYSIZE( envZombieMoanIgnited ) );
-
- if ( m_pMoanSound )
- {
- ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, 120, 1.0 );
- ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1, 1.0 );
- }
- }
- }
-}
-
-//---------------------------------------------------------
-// If a zombie stops burning and hasn't died, quiet him down
-//---------------------------------------------------------
-void CZombie::Extinguish()
-{
- if( m_pMoanSound )
- {
- ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0, 2.0 );
- ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, 100, 2.0 );
- m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 2.0, 4.0 );
- }
-
- BaseClass::Extinguish();
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CZombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
-{
-#ifndef HL2_EPISODIC
- if ( inputInfo.GetDamageType() & DMG_BUCKSHOT )
- {
- if( !m_fIsTorso && inputInfo.GetDamage() > (m_iMaxHealth/3) )
- {
- // Always flinch if damaged a lot by buckshot, even if not shot in the head.
- // The reason for making sure we did at least 1/3rd of the zombie's max health
- // is so the zombie doesn't flinch every time the odd shotgun pellet hits them,
- // and so the maximum number of times you'll see a zombie flinch like this is 2.(sjb)
- AddGesture( ACT_GESTURE_FLINCH_HEAD );
- }
- }
-#endif // HL2_EPISODIC
-
- return BaseClass::OnTakeDamage_Alive( inputInfo );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CZombie::IsHeavyDamage( const CTakeDamageInfo &info )
-{
-#ifdef HL2_EPISODIC
- if ( info.GetDamageType() & DMG_BUCKSHOT )
- {
- if ( !m_fIsTorso && info.GetDamage() > (m_iMaxHealth/3) )
- return true;
- }
-
- // Randomly treat all damage as heavy
- if ( info.GetDamageType() & (DMG_BULLET | DMG_BUCKSHOT) )
- {
- // Don't randomly flinch if I'm melee attacking
- if ( !HasCondition(COND_CAN_MELEE_ATTACK1) && (RandomFloat() > 0.5) )
- {
- // Randomly forget I've flinched, so that I'll be forced to play a big flinch
- // If this doesn't happen, it means I may not fully flinch if I recently flinched
- if ( RandomFloat() > 0.75 )
- {
- Forget(bits_MEMORY_FLINCHED);
- }
-
- return true;
- }
- }
-#endif // HL2_EPISODIC
-
- return BaseClass::IsHeavyDamage(info);
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-#define ZOMBIE_SQUASH_MASS 300.0f // Anything this heavy or heavier squashes a zombie good. (show special fx)
-bool CZombie::IsSquashed( const CTakeDamageInfo &info )
-{
- if( GetHealth() > 0 )
- {
- return false;
- }
-
- if( info.GetDamageType() & DMG_CRUSH )
- {
- IPhysicsObject *pCrusher = info.GetInflictor()->VPhysicsGetObject();
- if( pCrusher && pCrusher->GetMass() >= ZOMBIE_SQUASH_MASS && info.GetInflictor()->WorldSpaceCenter().z > EyePosition().z )
- {
- // This heuristic detects when a zombie has been squashed from above by a heavy
- // item. Done specifically so we can add gore effects to Ravenholm cartraps.
- // The zombie must take physics damage from a 300+kg object that is centered above its eyes (comes from above)
- return true;
- }
- }
-
- return false;
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CZombie::BuildScheduleTestBits( void )
-{
- BaseClass::BuildScheduleTestBits();
-
- if( !m_fIsTorso && !IsCurSchedule( SCHED_FLINCH_PHYSICS ) && !m_ActBusyBehavior.IsActive() )
- {
- SetCustomInterruptCondition( COND_PHYSICS_DAMAGE );
- }
-}
-
-
-//=============================================================================
-
-AI_BEGIN_CUSTOM_NPC( npc_zombie, CZombie )
-
- DECLARE_CONDITION( COND_BLOCKED_BY_DOOR )
- DECLARE_CONDITION( COND_DOOR_OPENED )
- DECLARE_CONDITION( COND_ZOMBIE_CHARGE_TARGET_MOVED )
-
- DECLARE_TASK( TASK_ZOMBIE_EXPRESS_ANGER )
- DECLARE_TASK( TASK_ZOMBIE_YAW_TO_DOOR )
- DECLARE_TASK( TASK_ZOMBIE_ATTACK_DOOR )
- DECLARE_TASK( TASK_ZOMBIE_CHARGE_ENEMY )
-
- DECLARE_ACTIVITY( ACT_ZOMBIE_TANTRUM );
- DECLARE_ACTIVITY( ACT_ZOMBIE_WALLPOUND );
-
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_BASH_DOOR,
-
- " Tasks"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_ZOMBIE_TANTRUM"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
- " TASK_ZOMBIE_YAW_TO_DOOR 0"
- " TASK_FACE_IDEAL 0"
- " TASK_ZOMBIE_ATTACK_DOOR 0"
- ""
- " Interrupts"
- " COND_ZOMBIE_RELEASECRAB"
- " COND_ENEMY_DEAD"
- " COND_NEW_ENEMY"
- " COND_DOOR_OPENED"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_WANDER_ANGRILY,
-
- " Tasks"
- " TASK_WANDER 480240" // 48 units to 240 units.
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 4"
- ""
- " Interrupts"
- " COND_ZOMBIE_RELEASECRAB"
- " COND_ENEMY_DEAD"
- " COND_NEW_ENEMY"
- " COND_DOOR_OPENED"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_CHARGE_ENEMY,
-
-
- " Tasks"
- " TASK_ZOMBIE_CHARGE_ENEMY 0"
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ZOMBIE_TANTRUM" /* placeholder until frustration/rage/fence shake animation available */
- ""
- " Interrupts"
- " COND_ZOMBIE_RELEASECRAB"
- " COND_ENEMY_DEAD"
- " COND_NEW_ENEMY"
- " COND_DOOR_OPENED"
- " COND_ZOMBIE_CHARGE_TARGET_MOVED"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_FAIL,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_ZOMBIE_TANTRUM"
- " TASK_WAIT 1"
- " TASK_WAIT_PVS 0"
- ""
- " Interrupts"
- " COND_CAN_RANGE_ATTACK1 "
- " COND_CAN_RANGE_ATTACK2 "
- " COND_CAN_MELEE_ATTACK1 "
- " COND_CAN_MELEE_ATTACK2"
- " COND_GIVE_WAY"
- " COND_DOOR_OPENED"
- )
-
-AI_END_CUSTOM_NPC()
-
-//=============================================================================
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A slow-moving, once-human headcrab victim with only melee attacks.
+//
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "doors.h"
+
+#include "simtimer.h"
+#include "npc_BaseZombie.h"
+#include "ai_hull.h"
+#include "ai_navigator.h"
+#include "ai_memory.h"
+#include "gib.h"
+#include "soundenvelope.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// ACT_FLINCH_PHYSICS
+
+
+ConVar sk_zombie_health( "sk_zombie_health","0");
+
+envelopePoint_t envZombieMoanVolumeFast[] =
+{
+ { 7.0f, 7.0f,
+ 0.1f, 0.1f,
+ },
+ { 0.0f, 0.0f,
+ 0.2f, 0.3f,
+ },
+};
+
+envelopePoint_t envZombieMoanVolume[] =
+{
+ { 1.0f, 1.0f,
+ 0.1f, 0.1f,
+ },
+ { 1.0f, 1.0f,
+ 0.2f, 0.2f,
+ },
+ { 0.0f, 0.0f,
+ 0.3f, 0.4f,
+ },
+};
+
+envelopePoint_t envZombieMoanVolumeLong[] =
+{
+ { 1.0f, 1.0f,
+ 0.3f, 0.5f,
+ },
+ { 1.0f, 1.0f,
+ 0.6f, 1.0f,
+ },
+ { 0.0f, 0.0f,
+ 0.3f, 0.4f,
+ },
+};
+
+envelopePoint_t envZombieMoanIgnited[] =
+{
+ { 1.0f, 1.0f,
+ 0.5f, 1.0f,
+ },
+ { 1.0f, 1.0f,
+ 30.0f, 30.0f,
+ },
+ { 0.0f, 0.0f,
+ 0.5f, 1.0f,
+ },
+};
+
+
+//=============================================================================
+//=============================================================================
+
+class CZombie : public CAI_BlendingHost<CNPC_BaseZombie>
+{
+ DECLARE_DATADESC();
+ DECLARE_CLASS( CZombie, CAI_BlendingHost<CNPC_BaseZombie> );
+
+public:
+ CZombie()
+ : m_DurationDoorBash( 2, 6),
+ m_NextTimeToStartDoorBash( 3.0 )
+ {
+ }
+
+ void Spawn( void );
+ void Precache( void );
+
+ void SetZombieModel( void );
+ void MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize );
+ bool ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold );
+ bool CanBecomeLiveTorso() { return !m_fIsHeadless; }
+
+ void GatherConditions( void );
+
+ int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
+ int TranslateSchedule( int scheduleType );
+
+#ifndef HL2_EPISODIC
+ void CheckFlinches() {} // Zombie has custom flinch code
+#endif // HL2_EPISODIC
+
+ Activity NPC_TranslateActivity( Activity newActivity );
+
+ void OnStateChange( NPC_STATE OldState, NPC_STATE NewState );
+
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+
+ virtual const char *GetLegsModel( void );
+ virtual const char *GetTorsoModel( void );
+ virtual const char *GetHeadcrabClassname( void );
+ virtual const char *GetHeadcrabModel( void );
+
+ virtual bool OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal,
+ CBaseDoor *pDoor,
+ float distClear,
+ AIMoveResult_t *pResult );
+
+ Activity SelectDoorBash();
+
+ void Ignite( float flFlameLifetime, bool bNPCOnly = true, float flSize = 0.0f, bool bCalledByLevelDesigner = false );
+ void Extinguish();
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+ bool IsHeavyDamage( const CTakeDamageInfo &info );
+ bool IsSquashed( const CTakeDamageInfo &info );
+ void BuildScheduleTestBits( void );
+
+ void PrescheduleThink( void );
+ int SelectSchedule ( void );
+
+ 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 );
+
+ const char *GetMoanSound( int nSound );
+
+public:
+ DEFINE_CUSTOM_AI;
+
+protected:
+ static const char *pMoanSounds[];
+
+
+private:
+ CHandle< CBaseDoor > m_hBlockingDoor;
+ float m_flDoorBashYaw;
+
+ CRandSimTimer m_DurationDoorBash;
+ CSimTimer m_NextTimeToStartDoorBash;
+
+ Vector m_vPositionCharged;
+};
+
+LINK_ENTITY_TO_CLASS( npc_zombie, CZombie );
+LINK_ENTITY_TO_CLASS( npc_zombie_torso, CZombie );
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+const char *CZombie::pMoanSounds[] =
+{
+ "NPC_BaseZombie.Moan1",
+ "NPC_BaseZombie.Moan2",
+ "NPC_BaseZombie.Moan3",
+ "NPC_BaseZombie.Moan4",
+};
+
+//=========================================================
+// Conditions
+//=========================================================
+enum
+{
+ COND_BLOCKED_BY_DOOR = LAST_BASE_ZOMBIE_CONDITION,
+ COND_DOOR_OPENED,
+ COND_ZOMBIE_CHARGE_TARGET_MOVED,
+};
+
+//=========================================================
+// Schedules
+//=========================================================
+enum
+{
+ SCHED_ZOMBIE_BASH_DOOR = LAST_BASE_ZOMBIE_SCHEDULE,
+ SCHED_ZOMBIE_WANDER_ANGRILY,
+ SCHED_ZOMBIE_CHARGE_ENEMY,
+ SCHED_ZOMBIE_FAIL,
+};
+
+//=========================================================
+// Tasks
+//=========================================================
+enum
+{
+ TASK_ZOMBIE_EXPRESS_ANGER = LAST_BASE_ZOMBIE_TASK,
+ TASK_ZOMBIE_YAW_TO_DOOR,
+ TASK_ZOMBIE_ATTACK_DOOR,
+ TASK_ZOMBIE_CHARGE_ENEMY,
+};
+
+//-----------------------------------------------------------------------------
+
+int ACT_ZOMBIE_TANTRUM;
+int ACT_ZOMBIE_WALLPOUND;
+
+BEGIN_DATADESC( CZombie )
+
+ DEFINE_FIELD( m_hBlockingDoor, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flDoorBashYaw, FIELD_FLOAT ),
+ DEFINE_EMBEDDED( m_DurationDoorBash ),
+ DEFINE_EMBEDDED( m_NextTimeToStartDoorBash ),
+ DEFINE_FIELD( m_vPositionCharged, FIELD_POSITION_VECTOR ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CZombie::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheModel( "models/zombie/classic.mdl" );
+ PrecacheModel( "models/zombie/classic_torso.mdl" );
+ PrecacheModel( "models/zombie/classic_legs.mdl" );
+
+ PrecacheScriptSound( "Zombie.FootstepRight" );
+ PrecacheScriptSound( "Zombie.FootstepLeft" );
+ PrecacheScriptSound( "Zombie.FootstepLeft" );
+ PrecacheScriptSound( "Zombie.ScuffRight" );
+ PrecacheScriptSound( "Zombie.ScuffLeft" );
+ PrecacheScriptSound( "Zombie.AttackHit" );
+ PrecacheScriptSound( "Zombie.AttackMiss" );
+ PrecacheScriptSound( "Zombie.Pain" );
+ PrecacheScriptSound( "Zombie.Die" );
+ PrecacheScriptSound( "Zombie.Alert" );
+ PrecacheScriptSound( "Zombie.Idle" );
+ PrecacheScriptSound( "Zombie.Attack" );
+
+ PrecacheScriptSound( "NPC_BaseZombie.Moan1" );
+ PrecacheScriptSound( "NPC_BaseZombie.Moan2" );
+ PrecacheScriptSound( "NPC_BaseZombie.Moan3" );
+ PrecacheScriptSound( "NPC_BaseZombie.Moan4" );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CZombie::Spawn( void )
+{
+ Precache();
+
+ if( FClassnameIs( this, "npc_zombie" ) )
+ {
+ m_fIsTorso = false;
+ }
+ else
+ {
+ // This was placed as an npc_zombie_torso
+ m_fIsTorso = true;
+ }
+
+ m_fIsHeadless = false;
+
+#ifdef HL2_EPISODIC
+ SetBloodColor( BLOOD_COLOR_ZOMBIE );
+#else
+ SetBloodColor( BLOOD_COLOR_GREEN );
+#endif // HL2_EPISODIC
+
+ m_iHealth = sk_zombie_health.GetFloat();
+ m_flFieldOfView = 0.2;
+
+ CapabilitiesClear();
+
+ //GetNavigator()->SetRememberStaleNodes( false );
+
+ BaseClass::Spawn();
+
+ m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 1.0, 4.0 );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CZombie::PrescheduleThink( void )
+{
+ if( gpGlobals->curtime > m_flNextMoanSound )
+ {
+ if( CanPlayMoanSound() )
+ {
+ // Classic guy idles instead of moans.
+ IdleSound();
+
+ m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 2.0, 5.0 );
+ }
+ else
+ {
+ m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 1.0, 2.0 );
+ }
+ }
+
+ BaseClass::PrescheduleThink();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CZombie::SelectSchedule ( void )
+{
+ if( HasCondition( COND_PHYSICS_DAMAGE ) && !m_ActBusyBehavior.IsActive() )
+ {
+ return SCHED_FLINCH_PHYSICS;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sound of a footstep
+//-----------------------------------------------------------------------------
+void CZombie::FootstepSound( bool fRightFoot )
+{
+ if( fRightFoot )
+ {
+ EmitSound( "Zombie.FootstepRight" );
+ }
+ else
+ {
+ EmitSound( "Zombie.FootstepLeft" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sound of a foot sliding/scraping
+//-----------------------------------------------------------------------------
+void CZombie::FootscuffSound( bool fRightFoot )
+{
+ if( fRightFoot )
+ {
+ EmitSound( "Zombie.ScuffRight" );
+ }
+ else
+ {
+ EmitSound( "Zombie.ScuffLeft" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack hit sound
+//-----------------------------------------------------------------------------
+void CZombie::AttackHitSound( void )
+{
+ EmitSound( "Zombie.AttackHit" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack miss sound
+//-----------------------------------------------------------------------------
+void CZombie::AttackMissSound( void )
+{
+ // Play a random attack miss sound
+ EmitSound( "Zombie.AttackMiss" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CZombie::PainSound( const CTakeDamageInfo &info )
+{
+ // We're constantly taking damage when we are on fire. Don't make all those noises!
+ if ( IsOnFire() )
+ {
+ return;
+ }
+
+ EmitSound( "Zombie.Pain" );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CZombie::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "Zombie.Die" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CZombie::AlertSound( void )
+{
+ EmitSound( "Zombie.Alert" );
+
+ // Don't let a moan sound cut off the alert sound.
+ m_flNextMoanSound += random->RandomFloat( 2.0, 4.0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a moan sound for this class of zombie.
+//-----------------------------------------------------------------------------
+const char *CZombie::GetMoanSound( int nSound )
+{
+ return pMoanSounds[ nSound % ARRAYSIZE( pMoanSounds ) ];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random idle sound.
+//-----------------------------------------------------------------------------
+void CZombie::IdleSound( void )
+{
+ if( GetState() == NPC_STATE_IDLE && random->RandomFloat( 0, 1 ) == 0 )
+ {
+ // Moan infrequently in IDLE state.
+ return;
+ }
+
+ if( IsSlumped() )
+ {
+ // Sleeping zombies are quiet.
+ return;
+ }
+
+ EmitSound( "Zombie.Idle" );
+ MakeAISpookySound( 360.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play a random attack sound.
+//-----------------------------------------------------------------------------
+void CZombie::AttackSound( void )
+{
+ EmitSound( "Zombie.Attack" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the classname (ie "npc_headcrab") to spawn when our headcrab bails.
+//-----------------------------------------------------------------------------
+const char *CZombie::GetHeadcrabClassname( void )
+{
+ return "npc_headcrab";
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+const char *CZombie::GetHeadcrabModel( void )
+{
+ return "models/headcrabclassic.mdl";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CZombie::GetLegsModel( void )
+{
+ return "models/zombie/classic_legs.mdl";
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+const char *CZombie::GetTorsoModel( void )
+{
+ return "models/zombie/classic_torso.mdl";
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CZombie::SetZombieModel( void )
+{
+ Hull_t lastHull = GetHullType();
+
+ if ( m_fIsTorso )
+ {
+ SetModel( "models/zombie/classic_torso.mdl" );
+ SetHullType( HULL_TINY );
+ }
+ else
+ {
+ SetModel( "models/zombie/classic.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();
+ }
+ }
+}
+
+//---------------------------------------------------------
+// Classic zombie only uses moan sound if on fire.
+//---------------------------------------------------------
+void CZombie::MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize )
+{
+ if( IsOnFire() )
+ {
+ BaseClass::MoanSound( pEnvelope, iEnvelopeSize );
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+bool CZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold )
+{
+ if( IsSlumped() )
+ {
+ // Never break apart a slouched zombie. This is because the most fun
+ // slouched zombies to kill are ones sleeping leaning against explosive
+ // barrels. If you break them in half in the blast, the force of being
+ // so close to the explosion makes the body pieces fly at ridiculous
+ // velocities because the pieces weigh less than the whole.
+ return false;
+ }
+
+ return BaseClass::ShouldBecomeTorso( info, flDamageThreshold );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CZombie::GatherConditions( void )
+{
+ BaseClass::GatherConditions();
+
+ static int conditionsToClear[] =
+ {
+ COND_BLOCKED_BY_DOOR,
+ COND_DOOR_OPENED,
+ COND_ZOMBIE_CHARGE_TARGET_MOVED,
+ };
+
+ ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
+
+ if ( m_hBlockingDoor == NULL ||
+ ( m_hBlockingDoor->m_toggle_state == TS_AT_TOP ||
+ m_hBlockingDoor->m_toggle_state == TS_GOING_UP ) )
+ {
+ ClearCondition( COND_BLOCKED_BY_DOOR );
+ if ( m_hBlockingDoor != NULL )
+ {
+ SetCondition( COND_DOOR_OPENED );
+ m_hBlockingDoor = NULL;
+ }
+ }
+ else
+ SetCondition( COND_BLOCKED_BY_DOOR );
+
+ if ( ConditionInterruptsCurSchedule( COND_ZOMBIE_CHARGE_TARGET_MOVED ) )
+ {
+ if ( GetNavigator()->IsGoalActive() )
+ {
+ const float CHARGE_RESET_TOLERANCE = 60.0;
+ if ( !GetEnemy() ||
+ ( m_vPositionCharged - GetEnemyLKP() ).Length() > CHARGE_RESET_TOLERANCE )
+ {
+ SetCondition( COND_ZOMBIE_CHARGE_TARGET_MOVED );
+ }
+
+ }
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+
+int CZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ if ( HasCondition( COND_BLOCKED_BY_DOOR ) && m_hBlockingDoor != NULL )
+ {
+ ClearCondition( COND_BLOCKED_BY_DOOR );
+ if ( m_NextTimeToStartDoorBash.Expired() && failedSchedule != SCHED_ZOMBIE_BASH_DOOR )
+ return SCHED_ZOMBIE_BASH_DOOR;
+ m_hBlockingDoor = NULL;
+ }
+
+ if ( failedSchedule != SCHED_ZOMBIE_CHARGE_ENEMY &&
+ IsPathTaskFailure( taskFailCode ) &&
+ random->RandomInt( 1, 100 ) < 50 )
+ {
+ return SCHED_ZOMBIE_CHARGE_ENEMY;
+ }
+
+ if ( failedSchedule != SCHED_ZOMBIE_WANDER_ANGRILY &&
+ ( failedSchedule == SCHED_TAKE_COVER_FROM_ENEMY ||
+ failedSchedule == SCHED_CHASE_ENEMY_FAILED ) )
+ {
+ return SCHED_ZOMBIE_WANDER_ANGRILY;
+ }
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+
+int CZombie::TranslateSchedule( int scheduleType )
+{
+ if ( scheduleType == SCHED_COMBAT_FACE && IsUnreachable( GetEnemy() ) )
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+
+ if ( !m_fIsTorso && scheduleType == SCHED_FAIL )
+ return SCHED_ZOMBIE_FAIL;
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//---------------------------------------------------------
+
+Activity CZombie::NPC_TranslateActivity( Activity newActivity )
+{
+ newActivity = BaseClass::NPC_TranslateActivity( newActivity );
+
+ if ( newActivity == ACT_RUN )
+ return ACT_WALK;
+
+ if ( m_fIsTorso && ( newActivity == ACT_ZOMBIE_TANTRUM ) )
+ return ACT_IDLE;
+
+ return newActivity;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CZombie::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
+{
+ BaseClass::OnStateChange( OldState, NewState );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+
+void CZombie::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_ZOMBIE_EXPRESS_ANGER:
+ {
+ if ( random->RandomInt( 1, 4 ) == 2 )
+ {
+ SetIdealActivity( (Activity)ACT_ZOMBIE_TANTRUM );
+ }
+ else
+ {
+ TaskComplete();
+ }
+
+ break;
+ }
+
+ case TASK_ZOMBIE_YAW_TO_DOOR:
+ {
+ AssertMsg( m_hBlockingDoor != NULL, "Expected condition handling to break schedule before landing here" );
+ if ( m_hBlockingDoor != NULL )
+ {
+ GetMotor()->SetIdealYaw( m_flDoorBashYaw );
+ }
+ TaskComplete();
+ break;
+ }
+
+ case TASK_ZOMBIE_ATTACK_DOOR:
+ {
+ m_DurationDoorBash.Reset();
+ SetIdealActivity( SelectDoorBash() );
+ break;
+ }
+
+ case TASK_ZOMBIE_CHARGE_ENEMY:
+ {
+ if ( !GetEnemy() )
+ TaskFail( FAIL_NO_ENEMY );
+ else if ( GetNavigator()->SetVectorGoalFromTarget( GetEnemy()->GetLocalOrigin() ) )
+ {
+ m_vPositionCharged = GetEnemy()->GetLocalOrigin();
+ TaskComplete();
+ }
+ else
+ TaskFail( FAIL_NO_ROUTE );
+ break;
+ }
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+
+void CZombie::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_ZOMBIE_ATTACK_DOOR:
+ {
+ if ( IsActivityFinished() )
+ {
+ if ( m_DurationDoorBash.Expired() )
+ {
+ TaskComplete();
+ m_NextTimeToStartDoorBash.Reset();
+ }
+ else
+ ResetIdealActivity( SelectDoorBash() );
+ }
+ break;
+ }
+
+ case TASK_ZOMBIE_CHARGE_ENEMY:
+ {
+ break;
+ }
+
+ case TASK_ZOMBIE_EXPRESS_ANGER:
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+
+bool CZombie::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor,
+ float distClear, AIMoveResult_t *pResult )
+{
+ if ( BaseClass::OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) )
+ {
+ if ( IsMoveBlocked( *pResult ) && pMoveGoal->directTrace.vHitNormal != vec3_origin )
+ {
+ m_hBlockingDoor = pDoor;
+ m_flDoorBashYaw = UTIL_VecToYaw( pMoveGoal->directTrace.vHitNormal * -1 );
+ }
+ return true;
+ }
+
+ return false;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+
+Activity CZombie::SelectDoorBash()
+{
+ if ( random->RandomInt( 1, 3 ) == 1 )
+ return ACT_MELEE_ATTACK1;
+ return (Activity)ACT_ZOMBIE_WALLPOUND;
+}
+
+//---------------------------------------------------------
+// Zombies should scream continuously while burning, so long
+// as they are alive... but NOT IN GERMANY!
+//---------------------------------------------------------
+void CZombie::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
+{
+ if( !IsOnFire() && IsAlive() )
+ {
+ BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
+
+ if ( !UTIL_IsLowViolence() )
+ {
+ RemoveSpawnFlags( SF_NPC_GAG );
+
+ MoanSound( envZombieMoanIgnited, ARRAYSIZE( envZombieMoanIgnited ) );
+
+ if ( m_pMoanSound )
+ {
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, 120, 1.0 );
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1, 1.0 );
+ }
+ }
+ }
+}
+
+//---------------------------------------------------------
+// If a zombie stops burning and hasn't died, quiet him down
+//---------------------------------------------------------
+void CZombie::Extinguish()
+{
+ if( m_pMoanSound )
+ {
+ ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0, 2.0 );
+ ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, 100, 2.0 );
+ m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 2.0, 4.0 );
+ }
+
+ BaseClass::Extinguish();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CZombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+#ifndef HL2_EPISODIC
+ if ( inputInfo.GetDamageType() & DMG_BUCKSHOT )
+ {
+ if( !m_fIsTorso && inputInfo.GetDamage() > (m_iMaxHealth/3) )
+ {
+ // Always flinch if damaged a lot by buckshot, even if not shot in the head.
+ // The reason for making sure we did at least 1/3rd of the zombie's max health
+ // is so the zombie doesn't flinch every time the odd shotgun pellet hits them,
+ // and so the maximum number of times you'll see a zombie flinch like this is 2.(sjb)
+ AddGesture( ACT_GESTURE_FLINCH_HEAD );
+ }
+ }
+#endif // HL2_EPISODIC
+
+ return BaseClass::OnTakeDamage_Alive( inputInfo );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CZombie::IsHeavyDamage( const CTakeDamageInfo &info )
+{
+#ifdef HL2_EPISODIC
+ if ( info.GetDamageType() & DMG_BUCKSHOT )
+ {
+ if ( !m_fIsTorso && info.GetDamage() > (m_iMaxHealth/3) )
+ return true;
+ }
+
+ // Randomly treat all damage as heavy
+ if ( info.GetDamageType() & (DMG_BULLET | DMG_BUCKSHOT) )
+ {
+ // Don't randomly flinch if I'm melee attacking
+ if ( !HasCondition(COND_CAN_MELEE_ATTACK1) && (RandomFloat() > 0.5) )
+ {
+ // Randomly forget I've flinched, so that I'll be forced to play a big flinch
+ // If this doesn't happen, it means I may not fully flinch if I recently flinched
+ if ( RandomFloat() > 0.75 )
+ {
+ Forget(bits_MEMORY_FLINCHED);
+ }
+
+ return true;
+ }
+ }
+#endif // HL2_EPISODIC
+
+ return BaseClass::IsHeavyDamage(info);
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+#define ZOMBIE_SQUASH_MASS 300.0f // Anything this heavy or heavier squashes a zombie good. (show special fx)
+bool CZombie::IsSquashed( const CTakeDamageInfo &info )
+{
+ if( GetHealth() > 0 )
+ {
+ return false;
+ }
+
+ if( info.GetDamageType() & DMG_CRUSH )
+ {
+ IPhysicsObject *pCrusher = info.GetInflictor()->VPhysicsGetObject();
+ if( pCrusher && pCrusher->GetMass() >= ZOMBIE_SQUASH_MASS && info.GetInflictor()->WorldSpaceCenter().z > EyePosition().z )
+ {
+ // This heuristic detects when a zombie has been squashed from above by a heavy
+ // item. Done specifically so we can add gore effects to Ravenholm cartraps.
+ // The zombie must take physics damage from a 300+kg object that is centered above its eyes (comes from above)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CZombie::BuildScheduleTestBits( void )
+{
+ BaseClass::BuildScheduleTestBits();
+
+ if( !m_fIsTorso && !IsCurSchedule( SCHED_FLINCH_PHYSICS ) && !m_ActBusyBehavior.IsActive() )
+ {
+ SetCustomInterruptCondition( COND_PHYSICS_DAMAGE );
+ }
+}
+
+
+//=============================================================================
+
+AI_BEGIN_CUSTOM_NPC( npc_zombie, CZombie )
+
+ DECLARE_CONDITION( COND_BLOCKED_BY_DOOR )
+ DECLARE_CONDITION( COND_DOOR_OPENED )
+ DECLARE_CONDITION( COND_ZOMBIE_CHARGE_TARGET_MOVED )
+
+ DECLARE_TASK( TASK_ZOMBIE_EXPRESS_ANGER )
+ DECLARE_TASK( TASK_ZOMBIE_YAW_TO_DOOR )
+ DECLARE_TASK( TASK_ZOMBIE_ATTACK_DOOR )
+ DECLARE_TASK( TASK_ZOMBIE_CHARGE_ENEMY )
+
+ DECLARE_ACTIVITY( ACT_ZOMBIE_TANTRUM );
+ DECLARE_ACTIVITY( ACT_ZOMBIE_WALLPOUND );
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ZOMBIE_BASH_DOOR,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_ZOMBIE_TANTRUM"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
+ " TASK_ZOMBIE_YAW_TO_DOOR 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_ZOMBIE_ATTACK_DOOR 0"
+ ""
+ " Interrupts"
+ " COND_ZOMBIE_RELEASECRAB"
+ " COND_ENEMY_DEAD"
+ " COND_NEW_ENEMY"
+ " COND_DOOR_OPENED"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ZOMBIE_WANDER_ANGRILY,
+
+ " Tasks"
+ " TASK_WANDER 480240" // 48 units to 240 units.
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 4"
+ ""
+ " Interrupts"
+ " COND_ZOMBIE_RELEASECRAB"
+ " COND_ENEMY_DEAD"
+ " COND_NEW_ENEMY"
+ " COND_DOOR_OPENED"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ZOMBIE_CHARGE_ENEMY,
+
+
+ " Tasks"
+ " TASK_ZOMBIE_CHARGE_ENEMY 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ZOMBIE_TANTRUM" /* placeholder until frustration/rage/fence shake animation available */
+ ""
+ " Interrupts"
+ " COND_ZOMBIE_RELEASECRAB"
+ " COND_ENEMY_DEAD"
+ " COND_NEW_ENEMY"
+ " COND_DOOR_OPENED"
+ " COND_ZOMBIE_CHARGE_TARGET_MOVED"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ZOMBIE_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_ZOMBIE_TANTRUM"
+ " TASK_WAIT 1"
+ " TASK_WAIT_PVS 0"
+ ""
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1 "
+ " COND_CAN_RANGE_ATTACK2 "
+ " COND_CAN_MELEE_ATTACK1 "
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_GIVE_WAY"
+ " COND_DOOR_OPENED"
+ )
+
+AI_END_CUSTOM_NPC()
+
+//=============================================================================