diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/server/hl2/npc_zombie.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-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.cpp | 2002 |
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() + +//============================================================================= |