source-engine/game/server/hl2/npc_zombie.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

1002 lines
26 KiB
C++

//========= 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()
//=============================================================================