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 /mp/src/game/server/hl2/npc_BaseZombie.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 'mp/src/game/server/hl2/npc_BaseZombie.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/npc_BaseZombie.cpp | 6046 |
1 files changed, 3023 insertions, 3023 deletions
diff --git a/mp/src/game/server/hl2/npc_BaseZombie.cpp b/mp/src/game/server/hl2/npc_BaseZombie.cpp index 39646480..e7e5c6fc 100644 --- a/mp/src/game/server/hl2/npc_BaseZombie.cpp +++ b/mp/src/game/server/hl2/npc_BaseZombie.cpp @@ -1,3023 +1,3023 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Implements the zombie, a horrific once-human headcrab victim.
-//
-// The zombie has two main states: Full and Torso.
-//
-// In Full state, the zombie is whole and walks upright as he did in Half-Life.
-// He will try to claw the player and swat physics items at him.
-//
-// In Torso state, the zombie has been blasted or cut in half, and the Torso will
-// drag itself along the ground with its arms. It will try to claw the player.
-//
-// In either state, a severely injured Zombie will release its headcrab, which
-// will immediately go after the player. The Zombie will then die (ragdoll).
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "npc_BaseZombie.h"
-#include "player.h"
-#include "game.h"
-#include "ai_network.h"
-#include "ai_navigator.h"
-#include "ai_motor.h"
-#include "ai_default.h"
-#include "ai_schedule.h"
-#include "ai_hull.h"
-#include "ai_node.h"
-#include "ai_memory.h"
-#include "ai_senses.h"
-#include "bitstring.h"
-#include "EntityFlame.h"
-#include "hl2_shareddefs.h"
-#include "npcevent.h"
-#include "activitylist.h"
-#include "entitylist.h"
-#include "gib.h"
-#include "soundenvelope.h"
-#include "ndebugoverlay.h"
-#include "rope.h"
-#include "rope_shared.h"
-#include "igamesystem.h"
-#include "vstdlib/random.h"
-#include "engine/IEngineSound.h"
-#include "props.h"
-#include "hl2_gamerules.h"
-#include "weapon_physcannon.h"
-#include "ammodef.h"
-#include "vehicle_base.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-extern ConVar sk_npc_head;
-
-#define ZOMBIE_BULLET_DAMAGE_SCALE 0.5f
-
-int g_interactionZombieMeleeWarning;
-
-envelopePoint_t envDefaultZombieMoanVolumeFast[] =
-{
- { 1.0f, 1.0f,
- 0.1f, 0.1f,
- },
- { 0.0f, 0.0f,
- 0.2f, 0.3f,
- },
-};
-
-envelopePoint_t envDefaultZombieMoanVolume[] =
-{
- { 1.0f, 0.1f,
- 0.1f, 0.1f,
- },
- { 1.0f, 1.0f,
- 0.2f, 0.2f,
- },
- { 0.0f, 0.0f,
- 0.3f, 0.4f,
- },
-};
-
-
-// if the zombie doesn't find anything closer than this, it doesn't swat.
-#define ZOMBIE_FARTHEST_PHYSICS_OBJECT 40.0*12.0
-#define ZOMBIE_PHYSICS_SEARCH_DEPTH 100
-
-// Don't swat objects unless player is closer than this.
-#define ZOMBIE_PLAYER_MAX_SWAT_DIST 1000
-
-//
-// How much health a Zombie torso gets when a whole zombie is broken
-// It's whole zombie's MAX Health * this value
-#define ZOMBIE_TORSO_HEALTH_FACTOR 0.5
-
-//
-// When the zombie has health < m_iMaxHealth * this value, it will
-// try to release its headcrab.
-#define ZOMBIE_RELEASE_HEALTH_FACTOR 0.5
-
-//
-// The heaviest physics object that a zombie should try to swat. (kg)
-#define ZOMBIE_MAX_PHYSOBJ_MASS 60
-
-//
-// Zombie tries to get this close to a physics object's origin to swat it
-#define ZOMBIE_PHYSOBJ_SWATDIST 80
-
-//
-// Because movement code sometimes doesn't get us QUITE where we
-// want to go, the zombie tries to get this close to a physics object
-// Zombie will end up somewhere between PHYSOBJ_MOVE_TO_DIST & PHYSOBJ_SWATDIST
-#define ZOMBIE_PHYSOBJ_MOVE_TO_DIST 48
-
-//
-// How long between physics swat attacks (in seconds).
-#define ZOMBIE_SWAT_DELAY 5
-
-
-//
-// After taking damage, ignore further damage for n seconds. This keeps the zombie
-// from being interrupted while.
-//
-#define ZOMBIE_FLINCH_DELAY 3
-
-
-#define ZOMBIE_BURN_TIME 10 // If ignited, burn for this many seconds
-#define ZOMBIE_BURN_TIME_NOISE 2 // Give or take this many seconds.
-
-
-//=========================================================
-// private activities
-//=========================================================
-int CNPC_BaseZombie::ACT_ZOM_SWATLEFTMID;
-int CNPC_BaseZombie::ACT_ZOM_SWATRIGHTMID;
-int CNPC_BaseZombie::ACT_ZOM_SWATLEFTLOW;
-int CNPC_BaseZombie::ACT_ZOM_SWATRIGHTLOW;
-int CNPC_BaseZombie::ACT_ZOM_RELEASECRAB;
-int CNPC_BaseZombie::ACT_ZOM_FALL;
-
-ConVar sk_zombie_dmg_one_slash( "sk_zombie_dmg_one_slash","0");
-ConVar sk_zombie_dmg_both_slash( "sk_zombie_dmg_both_slash","0");
-
-
-// When a zombie spawns, he will select a 'base' pitch value
-// that's somewhere between basepitchmin & basepitchmax
-ConVar zombie_basemin( "zombie_basemin", "100" );
-ConVar zombie_basemax( "zombie_basemax", "100" );
-
-ConVar zombie_changemin( "zombie_changemin", "0" );
-ConVar zombie_changemax( "zombie_changemax", "0" );
-
-// play a sound once in every zombie_stepfreq steps
-ConVar zombie_stepfreq( "zombie_stepfreq", "4" );
-ConVar zombie_moanfreq( "zombie_moanfreq", "1" );
-
-ConVar zombie_decaymin( "zombie_decaymin", "0.1" );
-ConVar zombie_decaymax( "zombie_decaymax", "0.4" );
-
-ConVar zombie_ambushdist( "zombie_ambushdist", "16000" );
-
-//=========================================================
-// For a couple of reasons, we keep a running count of how
-// many zombies in the world are angry at any given time.
-//=========================================================
-static int s_iAngryZombies = 0;
-
-//=========================================================
-//=========================================================
-class CAngryZombieCounter : public CAutoGameSystem
-{
-public:
- CAngryZombieCounter( char const *name ) : CAutoGameSystem( name )
- {
- }
- // Level init, shutdown
- virtual void LevelInitPreEntity()
- {
- s_iAngryZombies = 0;
- }
-};
-
-CAngryZombieCounter AngryZombieCounter( "CAngryZombieCounter" );
-
-
-int AE_ZOMBIE_ATTACK_RIGHT;
-int AE_ZOMBIE_ATTACK_LEFT;
-int AE_ZOMBIE_ATTACK_BOTH;
-int AE_ZOMBIE_SWATITEM;
-int AE_ZOMBIE_STARTSWAT;
-int AE_ZOMBIE_STEP_LEFT;
-int AE_ZOMBIE_STEP_RIGHT;
-int AE_ZOMBIE_SCUFF_LEFT;
-int AE_ZOMBIE_SCUFF_RIGHT;
-int AE_ZOMBIE_ATTACK_SCREAM;
-int AE_ZOMBIE_GET_UP;
-int AE_ZOMBIE_POUND;
-int AE_ZOMBIE_ALERTSOUND;
-int AE_ZOMBIE_POPHEADCRAB;
-
-
-//=========================================================
-//=========================================================
-BEGIN_DATADESC( CNPC_BaseZombie )
-
- DEFINE_SOUNDPATCH( m_pMoanSound ),
- DEFINE_FIELD( m_fIsTorso, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_fIsHeadless, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flNextFlinch, FIELD_TIME ),
- DEFINE_FIELD( m_bHeadShot, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flBurnDamage, FIELD_FLOAT ),
- DEFINE_FIELD( m_flBurnDamageResetTime, FIELD_TIME ),
- DEFINE_FIELD( m_hPhysicsEnt, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flNextMoanSound, FIELD_TIME ),
- DEFINE_FIELD( m_flNextSwat, FIELD_TIME ),
- DEFINE_FIELD( m_flNextSwatScan, FIELD_TIME ),
- DEFINE_FIELD( m_crabHealth, FIELD_FLOAT ),
- DEFINE_FIELD( m_flMoanPitch, FIELD_FLOAT ),
- DEFINE_FIELD( m_iMoanSound, FIELD_INTEGER ),
- DEFINE_FIELD( m_hObstructor, FIELD_EHANDLE ),
- DEFINE_FIELD( m_bIsSlumped, FIELD_BOOLEAN ),
-
-END_DATADESC()
-
-
-//LINK_ENTITY_TO_CLASS( base_zombie, CNPC_BaseZombie );
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CNPC_BaseZombie::g_numZombies = 0;
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-CNPC_BaseZombie::CNPC_BaseZombie()
-{
- // Gotta select which sound we're going to play, right here!
- // Because everyone's constructed before they spawn.
- //
- // Assign moan sounds in order, over and over.
- // This means if 3 or so zombies spawn near each
- // other, they will definitely not pick the same
- // moan loop.
- m_iMoanSound = g_numZombies;
-
- g_numZombies++;
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-CNPC_BaseZombie::~CNPC_BaseZombie()
-{
- g_numZombies--;
-}
-
-
-//---------------------------------------------------------
-// The closest physics object is chosen that is:
-// <= MaxMass in Mass
-// Between the zombie and the enemy
-// not too far from a direct line to the enemy.
-//---------------------------------------------------------
-bool CNPC_BaseZombie::FindNearestPhysicsObject( int iMaxMass )
-{
- CBaseEntity *pList[ ZOMBIE_PHYSICS_SEARCH_DEPTH ];
- CBaseEntity *pNearest = NULL;
- float flDist;
- IPhysicsObject *pPhysObj;
- int i;
- Vector vecDirToEnemy;
- Vector vecDirToObject;
-
- if ( !CanSwatPhysicsObjects() || !GetEnemy() )
- {
- // Can't swat, or no enemy, so no swat.
- m_hPhysicsEnt = NULL;
- return false;
- }
-
- vecDirToEnemy = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
- float dist = VectorNormalize(vecDirToEnemy);
- vecDirToEnemy.z = 0;
-
- if( dist > ZOMBIE_PLAYER_MAX_SWAT_DIST )
- {
- // Player is too far away. Don't bother
- // trying to swat anything at them until
- // they are closer.
- return false;
- }
-
- float flNearestDist = MIN( dist, ZOMBIE_FARTHEST_PHYSICS_OBJECT * 0.5 );
- Vector vecDelta( flNearestDist, flNearestDist, GetHullHeight() * 2.0 );
-
- class CZombieSwatEntitiesEnum : public CFlaggedEntitiesEnum
- {
- public:
- CZombieSwatEntitiesEnum( CBaseEntity **pList, int listMax, int iMaxMass )
- : CFlaggedEntitiesEnum( pList, listMax, 0 ),
- m_iMaxMass( iMaxMass )
- {
- }
-
- virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
- {
- CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
- if ( pEntity &&
- pEntity->VPhysicsGetObject() &&
- pEntity->VPhysicsGetObject()->GetMass() <= m_iMaxMass &&
- pEntity->VPhysicsGetObject()->IsAsleep() &&
- pEntity->VPhysicsGetObject()->IsMoveable() )
- {
- return CFlaggedEntitiesEnum::EnumElement( pHandleEntity );
- }
- return ITERATION_CONTINUE;
- }
-
- int m_iMaxMass;
- };
-
- CZombieSwatEntitiesEnum swatEnum( pList, ZOMBIE_PHYSICS_SEARCH_DEPTH, iMaxMass );
-
- int count = UTIL_EntitiesInBox( GetAbsOrigin() - vecDelta, GetAbsOrigin() + vecDelta, &swatEnum );
-
- // magically know where they are
- Vector vecZombieKnees;
- CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.25f ), &vecZombieKnees );
-
- for( i = 0 ; i < count ; i++ )
- {
- pPhysObj = pList[ i ]->VPhysicsGetObject();
-
- Assert( !( !pPhysObj || pPhysObj->GetMass() > iMaxMass || !pPhysObj->IsAsleep() ) );
-
- Vector center = pList[ i ]->WorldSpaceCenter();
- flDist = UTIL_DistApprox2D( GetAbsOrigin(), center );
-
- if( flDist >= flNearestDist )
- continue;
-
- // This object is closer... but is it between the player and the zombie?
- vecDirToObject = pList[ i ]->WorldSpaceCenter() - GetAbsOrigin();
- VectorNormalize(vecDirToObject);
- vecDirToObject.z = 0;
-
- if( DotProduct( vecDirToEnemy, vecDirToObject ) < 0.8 )
- continue;
-
- if( flDist >= UTIL_DistApprox2D( center, GetEnemy()->GetAbsOrigin() ) )
- continue;
-
- // don't swat things where the highest point is under my knees
- // NOTE: This is a rough test; a more exact test is going to occur below
- if ( (center.z + pList[i]->BoundingRadius()) < vecZombieKnees.z )
- continue;
-
- // don't swat things that are over my head.
- if( center.z > EyePosition().z )
- continue;
-
- vcollide_t *pCollide = modelinfo->GetVCollide( pList[i]->GetModelIndex() );
-
- Vector objMins, objMaxs;
- physcollision->CollideGetAABB( &objMins, &objMaxs, pCollide->solids[0], pList[i]->GetAbsOrigin(), pList[i]->GetAbsAngles() );
-
- if ( objMaxs.z < vecZombieKnees.z )
- continue;
-
- if ( !FVisible( pList[i] ) )
- continue;
-
- if ( hl2_episodic.GetBool() )
- {
- // Skip things that the enemy can't see. Do we want this as a general thing?
- // The case for this feature is that zombies who are pursuing the player will
- // stop along the way to swat objects at the player who is around the corner or
- // otherwise not in a place that the object has a hope of hitting. This diversion
- // makes the zombies very late (in a random fashion) getting where they are going. (sjb 1/2/06)
- if( !GetEnemy()->FVisible( pList[i] ) )
- continue;
- }
-
- // Make this the last check, since it makes a string.
- // Don't swat server ragdolls!
- if ( FClassnameIs( pList[ i ], "physics_prop_ragdoll" ) )
- continue;
-
- if ( FClassnameIs( pList[ i ], "prop_ragdoll" ) )
- continue;
-
- // The object must also be closer to the zombie than it is to the enemy
- pNearest = pList[ i ];
- flNearestDist = flDist;
- }
-
- m_hPhysicsEnt = pNearest;
-
- if( m_hPhysicsEnt == NULL )
- {
- return false;
- }
- else
- {
- return true;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns this monster's place in the relationship table.
-//-----------------------------------------------------------------------------
-Class_T CNPC_BaseZombie::Classify( void )
-{
- if ( IsSlumped() )
- return CLASS_NONE;
-
- return( CLASS_ZOMBIE );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Disposition_t CNPC_BaseZombie::IRelationType( CBaseEntity *pTarget )
-{
- // Slumping should not affect Zombie's opinion of others
- if ( IsSlumped() )
- {
- m_bIsSlumped = false;
- Disposition_t result = BaseClass::IRelationType( pTarget );
- m_bIsSlumped = true;
- return result;
- }
-
- return BaseClass::IRelationType( pTarget );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns the maximum yaw speed based on the monster's current activity.
-//-----------------------------------------------------------------------------
-float CNPC_BaseZombie::MaxYawSpeed( void )
-{
- if( m_fIsTorso )
- {
- return( 60 );
- }
- else if (IsMoving() && HasPoseParameter( GetSequence(), m_poseMove_Yaw ))
- {
- return( 15 );
- }
- else
- {
- switch( GetActivity() )
- {
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- return 100;
- break;
- case ACT_RUN:
- return 15;
- break;
- case ACT_WALK:
- case ACT_IDLE:
- return 25;
- break;
- case ACT_RANGE_ATTACK1:
- case ACT_RANGE_ATTACK2:
- case ACT_MELEE_ATTACK1:
- case ACT_MELEE_ATTACK2:
- return 120;
- default:
- return 90;
- break;
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: turn in the direction of movement
-// Output :
-//-----------------------------------------------------------------------------
-bool CNPC_BaseZombie::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
-{
- if (!HasPoseParameter( GetSequence(), m_poseMove_Yaw ))
- {
- return BaseClass::OverrideMoveFacing( move, flInterval );
- }
-
- // required movement direction
- float flMoveYaw = UTIL_VecToYaw( move.dir );
- float idealYaw = UTIL_AngleMod( flMoveYaw );
-
- if (GetEnemy())
- {
- float flEDist = UTIL_DistApprox2D( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter() );
-
- if (flEDist < 256.0)
- {
- float flEYaw = UTIL_VecToYaw( GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter() );
-
- if (flEDist < 128.0)
- {
- idealYaw = flEYaw;
- }
- else
- {
- idealYaw = flMoveYaw + UTIL_AngleDiff( flEYaw, flMoveYaw ) * (2 - flEDist / 128.0);
- }
-
- //DevMsg("was %.0f now %.0f\n", flMoveYaw, idealYaw );
- }
- }
-
- GetMotor()->SetIdealYawAndUpdate( idealYaw );
-
- // find movement direction to compensate for not being turned far enough
- float fSequenceMoveYaw = GetSequenceMoveYaw( GetSequence() );
- float flDiff = UTIL_AngleDiff( flMoveYaw, GetLocalAngles().y + fSequenceMoveYaw );
- SetPoseParameter( m_poseMove_Yaw, GetPoseParameter( m_poseMove_Yaw ) + flDiff );
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: For innate melee attack
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-int CNPC_BaseZombie::MeleeAttack1Conditions ( float flDot, float flDist )
-{
- float range = GetClawAttackRange();
-
- if (flDist > range )
- {
- // Translate a hit vehicle into its passenger if found
- if ( GetEnemy() != NULL )
- {
-#if defined(HL2_DLL) && !defined(HL2MP)
- // If the player is holding an object, knock it down.
- if( GetEnemy()->IsPlayer() )
- {
- CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
-
- Assert( pPlayer != NULL );
-
- // Is the player carrying something?
- CBaseEntity *pObject = GetPlayerHeldEntity(pPlayer);
-
- if( !pObject )
- {
- pObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
- }
-
- if( pObject )
- {
- float flDist = pObject->WorldSpaceCenter().DistTo( WorldSpaceCenter() );
-
- if( flDist <= GetClawAttackRange() )
- return COND_CAN_MELEE_ATTACK1;
- }
- }
-#endif
- }
- return COND_TOO_FAR_TO_ATTACK;
- }
-
- if (flDot < 0.7)
- {
- return COND_NOT_FACING_ATTACK;
- }
-
- // Build a cube-shaped hull, the same hull that ClawAttack() is going to use.
- Vector vecMins = GetHullMins();
- Vector vecMaxs = GetHullMaxs();
- vecMins.z = vecMins.x;
- vecMaxs.z = vecMaxs.x;
-
- Vector forward;
- GetVectors( &forward, NULL, NULL );
-
- trace_t tr;
- CTraceFilterNav traceFilter( this, false, this, COLLISION_GROUP_NONE );
- AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * GetClawAttackRange(), vecMins, vecMaxs, MASK_NPCSOLID, &traceFilter, &tr );
-
- if( tr.fraction == 1.0 || !tr.m_pEnt )
- {
-
-#ifdef HL2_EPISODIC
-
- // If our trace was unobstructed but we were shooting
- if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
- return COND_CAN_MELEE_ATTACK1;
-
-#endif // HL2_EPISODIC
-
- // This attack would miss completely. Trick the zombie into moving around some more.
- return COND_TOO_FAR_TO_ATTACK;
- }
-
- if( tr.m_pEnt == GetEnemy() ||
- tr.m_pEnt->IsNPC() ||
- ( tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast<CBreakableProp*>(tr.m_pEnt) ) ) )
- {
- // -Let the zombie swipe at his enemy if he's going to hit them.
- // -Also let him swipe at NPC's that happen to be between the zombie and the enemy.
- // This makes mobs of zombies seem more rowdy since it doesn't leave guys in the back row standing around.
- // -Also let him swipe at things that takedamage, under the assumptions that they can be broken.
- return COND_CAN_MELEE_ATTACK1;
- }
-
- Vector vecTrace = tr.endpos - tr.startpos;
- float lenTraceSq = vecTrace.Length2DSqr();
-
- if ( GetEnemy() && GetEnemy()->MyCombatCharacterPointer() && tr.m_pEnt == static_cast<CBaseCombatCharacter *>(GetEnemy())->GetVehicleEntity() )
- {
- if ( lenTraceSq < Square( GetClawAttackRange() * 0.75f ) )
- {
- return COND_CAN_MELEE_ATTACK1;
- }
- }
-
- if( tr.m_pEnt->IsBSPModel() )
- {
- // The trace hit something solid, but it's not the enemy. If this item is closer to the zombie than
- // the enemy is, treat this as an obstruction.
- Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
-
- if( lenTraceSq < vecToEnemy.Length2DSqr() )
- {
- return COND_ZOMBIE_LOCAL_MELEE_OBSTRUCTION;
- }
- }
-
-#ifdef HL2_EPISODIC
-
- if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt )
- {
- //Try to swat whatever the player is standing on instead of acting like a dill.
- return COND_CAN_MELEE_ATTACK1;
- }
-
- // Bullseyes are given some grace on if they can be hit
- if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
- return COND_CAN_MELEE_ATTACK1;
-
-#endif // HL2_EPISODIC
-
- // Move around some more
- return COND_TOO_FAR_TO_ATTACK;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-#define ZOMBIE_BUCKSHOT_TRIPLE_DAMAGE_DIST 96.0f // Triple damage from buckshot at 8 feet (headshot only)
-float CNPC_BaseZombie::GetHitgroupDamageMultiplier( int iHitGroup, const CTakeDamageInfo &info )
-{
- switch( iHitGroup )
- {
- case HITGROUP_HEAD:
- {
- if( info.GetDamageType() & DMG_BUCKSHOT )
- {
- float flDist = FLT_MAX;
-
- if( info.GetAttacker() )
- {
- flDist = ( GetAbsOrigin() - info.GetAttacker()->GetAbsOrigin() ).Length();
- }
-
- if( flDist <= ZOMBIE_BUCKSHOT_TRIPLE_DAMAGE_DIST )
- {
- return 3.0f;
- }
- }
- else
- {
- return 2.0f;
- }
- }
- }
-
- return BaseClass::GetHitgroupDamageMultiplier( iHitGroup, info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- CTakeDamageInfo infoCopy = info;
-
- // Keep track of headshots so we can determine whether to pop off our headcrab.
- if (ptr->hitgroup == HITGROUP_HEAD)
- {
- m_bHeadShot = true;
- }
-
- if( infoCopy.GetDamageType() & DMG_BUCKSHOT )
- {
- // Zombie gets across-the-board damage reduction for buckshot. This compensates for the recent changes which
- // make the shotgun much more powerful, and returns the zombies to a level that has been playtested extensively.(sjb)
- // This normalizes the buckshot damage to what it used to be on normal (5 dmg per pellet. Now it's 8 dmg per pellet).
- infoCopy.ScaleDamage( 0.625 );
- }
-
- BaseClass::TraceAttack( infoCopy, vecDir, ptr, pAccumulator );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: A zombie has taken damage. Determine whether he should split in half
-// Input :
-// Output : bool, true if yes.
-//-----------------------------------------------------------------------------
-bool CNPC_BaseZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold )
-{
- if ( info.GetDamageType() & DMG_REMOVENORAGDOLL )
- return false;
-
- if ( m_fIsTorso )
- {
- // Already split.
- return false;
- }
-
- // Not if we're in a dss
- if ( IsRunningDynamicInteraction() )
- return false;
-
- // Break in half IF:
- //
- // Take half or more of max health in DMG_BLAST
- if( (info.GetDamageType() & DMG_BLAST) && flDamageThreshold >= 0.5 )
- {
- return true;
- }
-
- if ( hl2_episodic.GetBool() )
- {
- // Always split after a cannon hit
- if ( info.GetAmmoType() == GetAmmoDef()->Index("CombineHeavyCannon") )
- return true;
- }
-
-#if 0
- if( info.GetDamageType() & DMG_BUCKSHOT )
- {
- if( m_iHealth <= 0 || flDamageThreshold >= 0.5 )
- {
- return true;
- }
- }
-#endif
-
- return false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: A zombie has taken damage. Determine whether he release his headcrab.
-// Output : YES, IMMEDIATE, or SCHEDULED (see HeadcrabRelease_t)
-//-----------------------------------------------------------------------------
-HeadcrabRelease_t CNPC_BaseZombie::ShouldReleaseHeadcrab( const CTakeDamageInfo &info, float flDamageThreshold )
-{
- if ( m_iHealth <= 0 )
- {
- if ( info.GetDamageType() & DMG_REMOVENORAGDOLL )
- return RELEASE_NO;
-
- if ( info.GetDamageType() & DMG_SNIPER )
- return RELEASE_RAGDOLL;
-
- // If I was killed by a bullet...
- if ( info.GetDamageType() & DMG_BULLET )
- {
- if( m_bHeadShot )
- {
- if( flDamageThreshold > 0.25 )
- {
- // Enough force to kill the crab.
- return RELEASE_RAGDOLL;
- }
- }
- else
- {
- // Killed by a shot to body or something. Crab is ok!
- return RELEASE_IMMEDIATE;
- }
- }
-
- // If I was killed by an explosion, release the crab.
- if ( info.GetDamageType() & DMG_BLAST )
- {
- return RELEASE_RAGDOLL;
- }
-
- if ( m_fIsTorso && IsChopped( info ) )
- {
- return RELEASE_RAGDOLL_SLICED_OFF;
- }
- }
-
- return RELEASE_NO;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : pInflictor -
-// pAttacker -
-// flDamage -
-// bitsDamageType -
-// Output : int
-//-----------------------------------------------------------------------------
-#define ZOMBIE_SCORCH_RATE 8
-#define ZOMBIE_MIN_RENDERCOLOR 50
-int CNPC_BaseZombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
-{
- CTakeDamageInfo info = inputInfo;
-
- if( inputInfo.GetDamageType() & DMG_BURN )
- {
- // If a zombie is on fire it only takes damage from the fire that's attached to it. (DMG_DIRECT)
- // This is to stop zombies from burning to death 10x faster when they're standing around
- // 10 fire entities.
- if( IsOnFire() && !(inputInfo.GetDamageType() & DMG_DIRECT) )
- {
- return 0;
- }
-
- Scorch( ZOMBIE_SCORCH_RATE, ZOMBIE_MIN_RENDERCOLOR );
- }
-
- // Take some percentage of damage from bullets (unless hit in the crab). Always take full buckshot & sniper damage
- if ( !m_bHeadShot && (info.GetDamageType() & DMG_BULLET) && !(info.GetDamageType() & (DMG_BUCKSHOT|DMG_SNIPER)) )
- {
- info.ScaleDamage( ZOMBIE_BULLET_DAMAGE_SCALE );
- }
-
- if ( ShouldIgnite( info ) )
- {
- Ignite( 100.0f );
- }
-
- int tookDamage = BaseClass::OnTakeDamage_Alive( info );
-
- // flDamageThreshold is what percentage of the creature's max health
- // this amount of damage represents. (clips at 1.0)
- float flDamageThreshold = MIN( 1, info.GetDamage() / m_iMaxHealth );
-
- // Being chopped up by a sharp physics object is a pretty special case
- // so we handle it with some special code. Mainly for
- // Ravenholm's helicopter traps right now (sjb).
- bool bChopped = IsChopped(info);
- bool bSquashed = IsSquashed(info);
- bool bKilledByVehicle = ( ( info.GetDamageType() & DMG_VEHICLE ) != 0 );
-
- if( !m_fIsTorso && (bChopped || bSquashed) && !bKilledByVehicle && !(info.GetDamageType() & DMG_REMOVENORAGDOLL) )
- {
- if( bChopped )
- {
- EmitSound( "E3_Phystown.Slicer" );
- }
-
- DieChopped( info );
- }
- else
- {
- HeadcrabRelease_t release = ShouldReleaseHeadcrab( info, flDamageThreshold );
-
- switch( release )
- {
- case RELEASE_IMMEDIATE:
- ReleaseHeadcrab( EyePosition(), vec3_origin, true, true );
- break;
-
- case RELEASE_RAGDOLL:
- // Go a little easy on headcrab ragdoll force. They're light!
- ReleaseHeadcrab( EyePosition(), inputInfo.GetDamageForce() * 0.25, true, false, true );
- break;
-
- case RELEASE_RAGDOLL_SLICED_OFF:
- {
- EmitSound( "E3_Phystown.Slicer" );
- Vector vecForce = inputInfo.GetDamageForce() * 0.1;
- vecForce += Vector( 0, 0, 2000.0 );
- ReleaseHeadcrab( EyePosition(), vecForce, true, false, true );
- }
- break;
-
- case RELEASE_VAPORIZE:
- RemoveHead();
- break;
-
- case RELEASE_SCHEDULED:
- SetCondition( COND_ZOMBIE_RELEASECRAB );
- break;
- }
-
- if( ShouldBecomeTorso( info, flDamageThreshold ) )
- {
- bool bHitByCombineCannon = (inputInfo.GetAmmoType() == GetAmmoDef()->Index("CombineHeavyCannon"));
-
- if ( CanBecomeLiveTorso() )
- {
- BecomeTorso( vec3_origin, inputInfo.GetDamageForce() * 0.50 );
-
- if ( ( info.GetDamageType() & DMG_BLAST) && random->RandomInt( 0, 1 ) == 0 )
- {
- Ignite( 5.0 + random->RandomFloat( 0.0, 5.0 ) );
- }
-
- // For Combine cannon impacts
- if ( hl2_episodic.GetBool() )
- {
- if ( bHitByCombineCannon )
- {
- // Catch on fire.
- Ignite( 5.0f + random->RandomFloat( 0.0f, 5.0f ) );
- }
- }
-
- if (flDamageThreshold >= 1.0)
- {
- m_iHealth = 0;
- BecomeRagdollOnClient( info.GetDamageForce() );
- }
- }
- else if ( random->RandomInt(1, 3) == 1 )
- DieChopped( info );
- }
- }
-
- if( tookDamage > 0 && (info.GetDamageType() & (DMG_BURN|DMG_DIRECT)) && m_ActBusyBehavior.IsActive() )
- {
- //!!!HACKHACK- Stuff a light_damage condition if an actbusying zombie takes direct burn damage. This will cause an
- // ignited zombie to 'wake up' and rise out of its actbusy slump. (sjb)
- SetCondition( COND_LIGHT_DAMAGE );
- }
-
- // IMPORTANT: always clear the headshot flag after applying damage. No early outs!
- m_bHeadShot = false;
-
- return tookDamage;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: make a sound Alyx can hear when in darkness mode
-// Input : volume (radius) of the sound.
-// Output :
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::MakeAISpookySound( float volume, float duration )
-{
-#ifdef HL2_EPISODIC
- if ( HL2GameRules()->IsAlyxInDarknessMode() )
- {
- CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), volume, duration, this, SOUNDENT_CHANNEL_SPOOKY_NOISE );
- }
-#endif // HL2_EPISODIC
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_BaseZombie::CanPlayMoanSound()
-{
- if( HasSpawnFlags( SF_NPC_GAG ) )
- return false;
-
- // Burning zombies play their moan loop at full volume for as long as they're
- // burning. Don't let a moan envelope play cause it will turn the volume down when done.
- if( IsOnFire() )
- return false;
-
- // Members of a small group of zombies can vocalize whenever they want
- if( s_iAngryZombies <= 4 )
- return true;
-
- // This serves to limit the number of zombies that can moan at one time when there are a lot.
- if( random->RandomInt( 1, zombie_moanfreq.GetInt() * (s_iAngryZombies/2) ) == 1 )
- {
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Open a window and let a little bit of the looping moan sound
-// come through.
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize )
-{
- if( HasSpawnFlags( SF_NPC_GAG ) )
- {
- // Not yet!
- return;
- }
-
- if( !m_pMoanSound )
- {
- // Don't set this up until the code calls for it.
- const char *pszSound = GetMoanSound( m_iMoanSound );
- m_flMoanPitch = random->RandomInt( zombie_basemin.GetInt(), zombie_basemax.GetInt() );
-
- //m_pMoanSound = ENVELOPE_CONTROLLER.SoundCreate( entindex(), CHAN_STATIC, pszSound, ATTN_NORM );
- CPASAttenuationFilter filter( this );
- m_pMoanSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_STATIC, pszSound, ATTN_NORM );
-
- ENVELOPE_CONTROLLER.Play( m_pMoanSound, 1.0, m_flMoanPitch );
- }
-
- //HACKHACK get these from chia chin's console vars.
- envDefaultZombieMoanVolumeFast[ 1 ].durationMin = zombie_decaymin.GetFloat();
- envDefaultZombieMoanVolumeFast[ 1 ].durationMax = zombie_decaymax.GetFloat();
-
- if( random->RandomInt( 1, 2 ) == 1 )
- {
- IdleSound();
- }
-
- float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pMoanSound, SOUNDCTRL_CHANGE_VOLUME, pEnvelope, iEnvelopeSize );
-
- float flPitch = random->RandomInt( m_flMoanPitch + zombie_changemin.GetInt(), m_flMoanPitch + zombie_changemax.GetInt() );
- ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, flPitch, 0.3 );
-
- m_flNextMoanSound = gpGlobals->curtime + duration + 9999;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determine whether the zombie is chopped up by some physics item
-//-----------------------------------------------------------------------------
-bool CNPC_BaseZombie::IsChopped( const CTakeDamageInfo &info )
-{
- float flDamageThreshold = MIN( 1, info.GetDamage() / m_iMaxHealth );
-
- if ( m_iHealth > 0 || flDamageThreshold <= 0.5 )
- return false;
-
- if ( !( info.GetDamageType() & DMG_SLASH) )
- return false;
-
- if ( !( info.GetDamageType() & DMG_CRUSH) )
- return false;
-
- if ( info.GetDamageType() & DMG_REMOVENORAGDOLL )
- return false;
-
- // If you take crush and slash damage, you're hit by a sharp physics item.
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Return true if this gibbing zombie should ignite its gibs
-//-----------------------------------------------------------------------------
-bool CNPC_BaseZombie::ShouldIgniteZombieGib( void )
-{
-#ifdef HL2_EPISODIC
- // If we're in darkness mode, don't ignite giblets, because we don't want to
- // pay the perf cost of multiple dynamic lights per giblet.
- return ( IsOnFire() && !HL2GameRules()->IsAlyxInDarknessMode() );
-#else
- return IsOnFire();
-#endif
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle the special case of a zombie killed by a physics chopper.
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::DieChopped( const CTakeDamageInfo &info )
-{
- bool bSquashed = IsSquashed(info);
-
- Vector forceVector( vec3_origin );
-
- forceVector += CalcDamageForceVector( info );
-
- if( !m_fIsHeadless && !bSquashed )
- {
- if( random->RandomInt( 0, 1 ) == 0 )
- {
- // Drop a live crab half of the time.
- ReleaseHeadcrab( EyePosition(), forceVector * 0.005, true, false, false );
- }
- }
-
- float flFadeTime = 0.0;
-
- if( HasSpawnFlags( SF_NPC_FADE_CORPSE ) )
- {
- flFadeTime = 5.0;
- }
-
- SetSolid( SOLID_NONE );
- AddEffects( EF_NODRAW );
-
- Vector vecLegsForce;
- vecLegsForce.x = random->RandomFloat( -400, 400 );
- vecLegsForce.y = random->RandomFloat( -400, 400 );
- vecLegsForce.z = random->RandomFloat( 0, 250 );
-
- if( bSquashed && vecLegsForce.z > 0 )
- {
- // Force the broken legs down. (Give some additional force, too)
- vecLegsForce.z *= -10;
- }
-
- CBaseEntity *pLegGib = CreateRagGib( GetLegsModel(), GetAbsOrigin(), GetAbsAngles(), vecLegsForce, flFadeTime, ShouldIgniteZombieGib() );
- if ( pLegGib )
- {
- CopyRenderColorTo( pLegGib );
- }
-
- forceVector *= random->RandomFloat( 0.04, 0.06 );
- forceVector.z = ( 100 * 12 * 5 ) * random->RandomFloat( 0.8, 1.2 );
-
- if( bSquashed && forceVector.z > 0 )
- {
- // Force the broken torso down.
- forceVector.z *= -1.0;
- }
-
- // Why do I have to fix this up?! (sjb)
- QAngle TorsoAngles;
- TorsoAngles = GetAbsAngles();
- TorsoAngles.x -= 90.0f;
- CBaseEntity *pTorsoGib = CreateRagGib( GetTorsoModel(), GetAbsOrigin() + Vector( 0, 0, 64 ), TorsoAngles, forceVector, flFadeTime, ShouldIgniteZombieGib() );
- if ( pTorsoGib )
- {
- CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating*>(pTorsoGib);
- if( pAnimating )
- {
- pAnimating->SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless );
- }
-
- pTorsoGib->SetOwnerEntity( this );
- CopyRenderColorTo( pTorsoGib );
-
- }
-
- if ( UTIL_ShouldShowBlood( BLOOD_COLOR_YELLOW ) )
- {
- int i;
- Vector vecSpot;
- Vector vecDir;
-
- for ( i = 0 ; i < 4; i++ )
- {
- vecSpot = WorldSpaceCenter();
-
- vecSpot.x += random->RandomFloat( -12, 12 );
- vecSpot.y += random->RandomFloat( -12, 12 );
- vecSpot.z += random->RandomFloat( -4, 16 );
-
- UTIL_BloodDrips( vecSpot, vec3_origin, BLOOD_COLOR_YELLOW, 50 );
- }
-
- for ( int i = 0 ; i < 4 ; i++ )
- {
- Vector vecSpot = WorldSpaceCenter();
-
- vecSpot.x += random->RandomFloat( -12, 12 );
- vecSpot.y += random->RandomFloat( -12, 12 );
- vecSpot.z += random->RandomFloat( -4, 16 );
-
- vecDir.x = random->RandomFloat(-1, 1);
- vecDir.y = random->RandomFloat(-1, 1);
- vecDir.z = 0;
- VectorNormalize( vecDir );
-
- UTIL_BloodImpact( vecSpot, vecDir, BloodColor(), 1 );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: damage has been done. Should the zombie ignite?
-//-----------------------------------------------------------------------------
-bool CNPC_BaseZombie::ShouldIgnite( const CTakeDamageInfo &info )
-{
- if ( IsOnFire() )
- {
- // Already burning!
- return false;
- }
-
- if ( info.GetDamageType() & DMG_BURN )
- {
- //
- // If we take more than ten percent of our health in burn damage within a five
- // second interval, we should catch on fire.
- //
- m_flBurnDamage += info.GetDamage();
- m_flBurnDamageResetTime = gpGlobals->curtime + 5;
-
- if ( m_flBurnDamage >= m_iMaxHealth * 0.1 )
- {
- return true;
- }
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sufficient fire damage has been done. Zombie ignites!
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
-{
- BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
-
-#ifdef HL2_EPISODIC
- if ( HL2GameRules()->IsAlyxInDarknessMode() == true && GetEffectEntity() != NULL )
- {
- GetEffectEntity()->AddEffects( EF_DIMLIGHT );
- }
-#endif // HL2_EPISODIC
-
- // Set the zombie up to burn to death in about ten seconds.
- SetHealth( MIN( m_iHealth, FLAME_DIRECT_DAMAGE_PER_SEC * (ZOMBIE_BURN_TIME + random->RandomFloat( -ZOMBIE_BURN_TIME_NOISE, ZOMBIE_BURN_TIME_NOISE)) ) );
-
- // FIXME: use overlays when they come online
- //AddOverlay( ACT_ZOM_WALK_ON_FIRE, false );
- if( !m_ActBusyBehavior.IsActive() )
- {
- Activity activity = GetActivity();
- Activity burningActivity = activity;
-
- if ( activity == ACT_WALK )
- {
- burningActivity = ACT_WALK_ON_FIRE;
- }
- else if ( activity == ACT_RUN )
- {
- burningActivity = ACT_RUN_ON_FIRE;
- }
- else if ( activity == ACT_IDLE )
- {
- burningActivity = ACT_IDLE_ON_FIRE;
- }
-
- if( HaveSequenceForActivity(burningActivity) )
- {
- // Make sure we have a sequence for this activity (torsos don't have any, for instance)
- // to prevent the baseNPC & baseAnimating code from throwing red level errors.
- SetActivity( burningActivity );
- }
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::CopyRenderColorTo( CBaseEntity *pOther )
-{
- color32 color = GetRenderColor();
- pOther->SetRenderColor( color.r, color.g, color.b, color.a );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Look in front and see if the claw hit anything.
-//
-// Input : flDist distance to trace
-// iDamage damage to do if attack hits
-// vecViewPunch camera punch (if attack hits player)
-// vecVelocityPunch velocity punch (if attack hits player)
-//
-// Output : The entity hit by claws. NULL if nothing.
-//-----------------------------------------------------------------------------
-CBaseEntity *CNPC_BaseZombie::ClawAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin )
-{
- // Added test because claw attack anim sometimes used when for cases other than melee
- int iDriverInitialHealth = -1;
- CBaseEntity *pDriver = NULL;
- if ( GetEnemy() )
- {
- trace_t tr;
- AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction < 1.0f )
- return NULL;
-
- // CheckTraceHullAttack() can damage player in vehicle as side effect of melee attack damaging physics objects, which the car forwards to the player
- // need to detect this to get correct damage effects
- CBaseCombatCharacter *pCCEnemy = ( GetEnemy() != NULL ) ? GetEnemy()->MyCombatCharacterPointer() : NULL;
- CBaseEntity *pVehicleEntity;
- if ( pCCEnemy != NULL && ( pVehicleEntity = pCCEnemy->GetVehicleEntity() ) != NULL )
- {
- if ( pVehicleEntity->GetServerVehicle() && dynamic_cast<CPropVehicleDriveable *>(pVehicleEntity) )
- {
- pDriver = static_cast<CPropVehicleDriveable *>(pVehicleEntity)->GetDriver();
- if ( pDriver && pDriver->IsPlayer() )
- {
- iDriverInitialHealth = pDriver->GetHealth();
- }
- else
- {
- pDriver = NULL;
- }
- }
- }
- }
-
- //
- // Trace out a cubic section of our hull and see what we hit.
- //
- Vector vecMins = GetHullMins();
- Vector vecMaxs = GetHullMaxs();
- vecMins.z = vecMins.x;
- vecMaxs.z = vecMaxs.x;
-
- CBaseEntity *pHurt = NULL;
- if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )
- {
- // We always hit bullseyes we're targeting
- pHurt = GetEnemy();
- CTakeDamageInfo info( this, this, vec3_origin, GetAbsOrigin(), iDamage, DMG_SLASH );
- pHurt->TakeDamage( info );
- }
- else
- {
- // Try to hit them with a trace
- pHurt = CheckTraceHullAttack( flDist, vecMins, vecMaxs, iDamage, DMG_SLASH );
- }
-
- if ( pDriver && iDriverInitialHealth != pDriver->GetHealth() )
- {
- pHurt = pDriver;
- }
-
- if ( !pHurt && m_hPhysicsEnt != NULL && IsCurSchedule(SCHED_ZOMBIE_ATTACKITEM) )
- {
- pHurt = m_hPhysicsEnt;
-
- Vector vForce = pHurt->WorldSpaceCenter() - WorldSpaceCenter();
- VectorNormalize( vForce );
-
- vForce *= 5 * 24;
-
- CTakeDamageInfo info( this, this, vForce, GetAbsOrigin(), iDamage, DMG_SLASH );
- pHurt->TakeDamage( info );
-
- pHurt = m_hPhysicsEnt;
- }
-
- if ( pHurt )
- {
- AttackHitSound();
-
- CBasePlayer *pPlayer = ToBasePlayer( pHurt );
-
- if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) )
- {
- pPlayer->ViewPunch( qaViewPunch );
-
- pPlayer->VelocityPunch( vecVelocityPunch );
- }
- else if( !pPlayer && UTIL_ShouldShowBlood(pHurt->BloodColor()) )
- {
- // Hit an NPC. Bleed them!
- Vector vecBloodPos;
-
- switch( BloodOrigin )
- {
- case ZOMBIE_BLOOD_LEFT_HAND:
- if( GetAttachment( "blood_left", vecBloodPos ) )
- SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
- break;
-
- case ZOMBIE_BLOOD_RIGHT_HAND:
- if( GetAttachment( "blood_right", vecBloodPos ) )
- SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
- break;
-
- case ZOMBIE_BLOOD_BOTH_HANDS:
- if( GetAttachment( "blood_left", vecBloodPos ) )
- SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
-
- if( GetAttachment( "blood_right", vecBloodPos ) )
- SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) );
- break;
-
- case ZOMBIE_BLOOD_BITE:
- // No blood for these.
- break;
- }
- }
- }
- else
- {
- AttackMissSound();
- }
-
- if ( pHurt == m_hPhysicsEnt && IsCurSchedule(SCHED_ZOMBIE_ATTACKITEM) )
- {
- m_hPhysicsEnt = NULL;
- m_flNextSwat = gpGlobals->curtime + random->RandomFloat( 2, 4 );
- }
-
- return pHurt;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: The zombie is frustrated and pounding walls/doors. Make an appropriate noise
-// Input :
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::PoundSound()
-{
- trace_t tr;
- Vector forward;
-
- GetVectors( &forward, NULL, NULL );
-
- AI_TraceLine( EyePosition(), EyePosition() + forward * 128, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
-
- if( tr.fraction == 1.0 )
- {
- // Didn't hit anything!
- return;
- }
-
- if( tr.fraction < 1.0 && tr.m_pEnt )
- {
- const surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps );
- if( psurf )
- {
- EmitSound( physprops->GetString(psurf->sounds.impactHard) );
- return;
- }
- }
-
- // Otherwise fall through to the default sound.
- CPASAttenuationFilter filter( this,"NPC_BaseZombie.PoundDoor" );
- EmitSound( filter, entindex(),"NPC_BaseZombie.PoundDoor" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Catches the monster-specific events that occur when tagged animation
-// frames are played.
-// Input : pEvent -
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent )
-{
- if ( pEvent->event == AE_NPC_ATTACK_BROADCAST )
- {
- if( GetEnemy() && GetEnemy()->IsNPC() )
- {
- if( HasCondition(COND_CAN_MELEE_ATTACK1) )
- {
- // This animation is sometimes played by code that doesn't intend to attack the enemy
- // (For instance, code that makes a zombie take a frustrated swipe at an obstacle).
- // Try not to trigger a reaction from our enemy unless we're really attacking.
- GetEnemy()->MyNPCPointer()->DispatchInteraction( g_interactionZombieMeleeWarning, NULL, this );
- }
- }
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_POUND )
- {
- PoundSound();
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_ALERTSOUND )
- {
- AlertSound();
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_STEP_LEFT )
- {
- MakeAIFootstepSound( 180.0f );
- FootstepSound( false );
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_STEP_RIGHT )
- {
- MakeAIFootstepSound( 180.0f );
- FootstepSound( true );
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_GET_UP )
- {
- MakeAIFootstepSound( 180.0f, 3.0f );
- if( !IsOnFire() )
- {
- // If you let this code run while a zombie is burning, it will stop wailing.
- m_flNextMoanSound = gpGlobals->curtime;
- MoanSound( envDefaultZombieMoanVolumeFast, ARRAYSIZE( envDefaultZombieMoanVolumeFast ) );
- }
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_SCUFF_LEFT )
- {
- MakeAIFootstepSound( 180.0f );
- FootscuffSound( false );
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_SCUFF_RIGHT )
- {
- MakeAIFootstepSound( 180.0f );
- FootscuffSound( true );
- return;
- }
-
- // all swat animations are handled as a single case.
- if ( pEvent->event == AE_ZOMBIE_STARTSWAT )
- {
- MakeAIFootstepSound( 180.0f );
- AttackSound();
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_ATTACK_SCREAM )
- {
- AttackSound();
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_SWATITEM )
- {
- CBaseEntity *pEnemy = GetEnemy();
- if ( pEnemy )
- {
- Vector v;
- CBaseEntity *pPhysicsEntity = m_hPhysicsEnt;
- if( !pPhysicsEntity )
- {
- DevMsg( "**Zombie: Missing my physics ent!!" );
- return;
- }
-
- IPhysicsObject *pPhysObj = pPhysicsEntity->VPhysicsGetObject();
-
- if( !pPhysObj )
- {
- DevMsg( "**Zombie: No Physics Object for physics Ent!" );
- return;
- }
-
- EmitSound( "NPC_BaseZombie.Swat" );
- PhysicsImpactSound( pEnemy, pPhysObj, CHAN_BODY, pPhysObj->GetMaterialIndex(), physprops->GetSurfaceIndex("flesh"), 0.5, 800 );
-
- Vector physicsCenter = pPhysicsEntity->WorldSpaceCenter();
- v = pEnemy->WorldSpaceCenter() - physicsCenter;
- VectorNormalize(v);
-
- // Send the object at 800 in/sec toward the enemy. Add 200 in/sec up velocity to keep it
- // in the air for a second or so.
- v = v * 800;
- v.z += 200;
-
- // add some spin so the object doesn't appear to just fly in a straight line
- // Also this spin will move the object slightly as it will press on whatever the object
- // is resting on.
- AngularImpulse angVelocity( random->RandomFloat(-180, 180), 20, random->RandomFloat(-360, 360) );
-
- pPhysObj->AddVelocity( &v, &angVelocity );
-
- // If we don't put the object scan time well into the future, the zombie
- // will re-select the object he just hit as it is flying away from him.
- // It will likely always be the nearest object because the zombie moved
- // close enough to it to hit it.
- m_hPhysicsEnt = NULL;
-
- m_flNextSwatScan = gpGlobals->curtime + ZOMBIE_SWAT_DELAY;
-
- return;
- }
- }
-
- if ( pEvent->event == AE_ZOMBIE_ATTACK_RIGHT )
- {
- Vector right, forward;
- AngleVectors( GetLocalAngles(), &forward, &right, NULL );
-
- right = right * 100;
- forward = forward * 200;
-
- QAngle qa( -15, -20, -10 );
- Vector vec = right + forward;
- ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), qa, vec, ZOMBIE_BLOOD_RIGHT_HAND );
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_ATTACK_LEFT )
- {
- Vector right, forward;
- AngleVectors( GetLocalAngles(), &forward, &right, NULL );
-
- right = right * -100;
- forward = forward * 200;
-
- QAngle qa( -15, 20, -10 );
- Vector vec = right + forward;
- ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), qa, vec, ZOMBIE_BLOOD_LEFT_HAND );
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_ATTACK_BOTH )
- {
- Vector forward;
- QAngle qaPunch( 45, random->RandomInt(-5,5), random->RandomInt(-5,5) );
- AngleVectors( GetLocalAngles(), &forward );
- forward = forward * 200;
- ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS );
- return;
- }
-
- if ( pEvent->event == AE_ZOMBIE_POPHEADCRAB )
- {
- if ( GetInteractionPartner() == NULL )
- return;
-
- const char *pString = pEvent->options;
- char token[128];
- pString = nexttoken( token, pString, ' ' );
-
- int boneIndex = GetInteractionPartner()->LookupBone( token );
-
- if ( boneIndex == -1 )
- {
- Warning( "AE_ZOMBIE_POPHEADCRAB event using invalid bone name! Usage: event AE_ZOMBIE_POPHEADCRAB \"<BoneName> <Speed>\" \n" );
- return;
- }
-
- pString = nexttoken( token, pString, ' ' );
-
- if ( !token )
- {
- Warning( "AE_ZOMBIE_POPHEADCRAB event format missing velocity parameter! Usage: event AE_ZOMBIE_POPHEADCRAB \"<BoneName> <Speed>\" \n" );
- return;
- }
-
- Vector vecBonePosition;
- QAngle angles;
- Vector vecHeadCrabPosition;
-
- int iCrabAttachment = LookupAttachment( "headcrab" );
- int iSpeed = atoi( token );
-
- GetInteractionPartner()->GetBonePosition( boneIndex, vecBonePosition, angles );
- GetAttachment( iCrabAttachment, vecHeadCrabPosition );
-
- Vector vVelocity = vecHeadCrabPosition - vecBonePosition;
- VectorNormalize( vVelocity );
-
- CTakeDamageInfo dmgInfo( this, GetInteractionPartner(), m_iHealth, DMG_DIRECT );
-
- dmgInfo.SetDamagePosition( vecHeadCrabPosition );
-
- ReleaseHeadcrab( EyePosition(), vVelocity * iSpeed, true, false, true );
-
- GuessDamageForce( &dmgInfo, vVelocity, vecHeadCrabPosition, 0.5f );
- TakeDamage( dmgInfo );
- return;
- }
-
- BaseClass::HandleAnimEvent( pEvent );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Spawn function for the base zombie.
-//
-// !!!IMPORTANT!!! YOUR DERIVED CLASS'S SPAWN() RESPONSIBILITIES:
-//
-// Call Precache();
-// Set status for m_fIsTorso & m_fIsHeadless
-// Set blood color
-// Set health
-// Set field of view
-// Call CapabilitiesClear() & then set relevant capabilities
-// THEN Call BaseClass::Spawn()
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::Spawn( void )
-{
- SetSolid( SOLID_BBOX );
- SetMoveType( MOVETYPE_STEP );
-
-#ifdef _XBOX
- // Always fade the corpse
- AddSpawnFlags( SF_NPC_FADE_CORPSE );
-#endif // _XBOX
-
- m_NPCState = NPC_STATE_NONE;
-
- CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 );
- CapabilitiesAdd( bits_CAP_SQUAD );
-
- m_flNextSwat = gpGlobals->curtime;
- m_flNextSwatScan = gpGlobals->curtime;
- m_pMoanSound = NULL;
-
- m_flNextMoanSound = gpGlobals->curtime + 9999;
-
- SetZombieModel();
-
- NPCInit();
-
- m_bIsSlumped = false;
-
- // Zombies get to cheat for 6 seconds (sjb)
- GetEnemies()->SetFreeKnowledgeDuration( 6.0 );
-
- m_ActBusyBehavior.SetUseRenderBounds(true);
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Pecaches all resources this NPC needs.
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::Precache( void )
-{
- UTIL_PrecacheOther( GetHeadcrabClassname() );
-
- PrecacheScriptSound( "E3_Phystown.Slicer" );
- PrecacheScriptSound( "NPC_BaseZombie.PoundDoor" );
- PrecacheScriptSound( "NPC_BaseZombie.Swat" );
-
- PrecacheModel( GetLegsModel() );
- PrecacheModel( GetTorsoModel() );
-
- PrecacheParticleSystem( "blood_impact_zombie_01" );
-
- BaseClass::Precache();
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::StartTouch( CBaseEntity *pOther )
-{
- BaseClass::StartTouch( pOther );
-
- if( IsSlumped() && hl2_episodic.GetBool() )
- {
- if( FClassnameIs( pOther, "prop_physics" ) )
- {
- // Get up!
- m_ActBusyBehavior.StopBusying();
- }
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-bool CNPC_BaseZombie::CreateBehaviors()
-{
- AddBehavior( &m_ActBusyBehavior );
-
- return BaseClass::CreateBehaviors();
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CNPC_BaseZombie::TranslateSchedule( int scheduleType )
-{
- switch( scheduleType )
- {
- case SCHED_CHASE_ENEMY:
- if ( HasCondition( COND_ZOMBIE_LOCAL_MELEE_OBSTRUCTION ) && !HasCondition(COND_TASK_FAILED) && IsCurSchedule( SCHED_ZOMBIE_CHASE_ENEMY, false ) )
- {
- return SCHED_COMBAT_PATROL;
- }
- return SCHED_ZOMBIE_CHASE_ENEMY;
- break;
-
- case SCHED_ZOMBIE_SWATITEM:
- // If the object is far away, move and swat it. If it's close, just swat it.
- if( DistToPhysicsEnt() > ZOMBIE_PHYSOBJ_SWATDIST )
- {
- return SCHED_ZOMBIE_MOVE_SWATITEM;
- }
- else
- {
- return SCHED_ZOMBIE_SWATITEM;
- }
- break;
-
- case SCHED_STANDOFF:
- return SCHED_ZOMBIE_WANDER_STANDOFF;
-
- case SCHED_MELEE_ATTACK1:
- return SCHED_ZOMBIE_MELEE_ATTACK1;
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Allows for modification of the interrupt mask for the current schedule.
-// In the most cases the base implementation should be called first.
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::BuildScheduleTestBits( void )
-{
- // Ignore damage if we were recently damaged or we're attacking.
- if ( GetActivity() == ACT_MELEE_ATTACK1 )
- {
- ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
- ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
- }
-#ifndef HL2_EPISODIC
- else if ( m_flNextFlinch >= gpGlobals->curtime )
- {
- ClearCustomInterruptCondition( COND_LIGHT_DAMAGE );
- ClearCustomInterruptCondition( COND_HEAVY_DAMAGE );
- }
-#endif // !HL2_EPISODIC
-
- // Everything should be interrupted if we get killed.
- SetCustomInterruptCondition( COND_ZOMBIE_RELEASECRAB );
-
- BaseClass::BuildScheduleTestBits();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Called when we change schedules.
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::OnScheduleChange( void )
-{
- //
- // If we took damage and changed schedules, ignore further damage for a few seconds.
- //
- if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ))
- {
- m_flNextFlinch = gpGlobals->curtime + ZOMBIE_FLINCH_DELAY;
- }
-
- BaseClass::OnScheduleChange();
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CNPC_BaseZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
-{
- if( failedSchedule == SCHED_ZOMBIE_WANDER_MEDIUM )
- {
- return SCHED_ZOMBIE_WANDER_FAIL;
- }
-
- // If we can swat physics objects, see if we can swat our obstructor
- if ( CanSwatPhysicsObjects() )
- {
- if ( !m_fIsTorso && IsPathTaskFailure( taskFailCode ) &&
- m_hObstructor != NULL && m_hObstructor->VPhysicsGetObject() &&
- m_hObstructor->VPhysicsGetObject()->GetMass() < 100 )
- {
- m_hPhysicsEnt = m_hObstructor;
- m_hObstructor = NULL;
- return SCHED_ZOMBIE_ATTACKITEM;
- }
- }
-
- m_hObstructor = NULL;
-
- return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CNPC_BaseZombie::SelectSchedule ( void )
-{
- if ( HasCondition( COND_ZOMBIE_RELEASECRAB ) )
- {
- // Death waits for no man. Or zombie. Or something.
- return SCHED_ZOMBIE_RELEASECRAB;
- }
-
- if ( BehaviorSelectSchedule() )
- {
- return BaseClass::SelectSchedule();
- }
-
- switch ( m_NPCState )
- {
- case NPC_STATE_COMBAT:
- if ( HasCondition( COND_NEW_ENEMY ) && GetEnemy() )
- {
- float flDist;
-
- flDist = ( GetLocalOrigin() - GetEnemy()->GetLocalOrigin() ).Length();
-
- // If this is a new enemy that's far away, ambush!!
- if (flDist >= zombie_ambushdist.GetFloat() && MustCloseToAttack() )
- {
- return SCHED_ZOMBIE_MOVE_TO_AMBUSH;
- }
- }
-
- if ( HasCondition( COND_LOST_ENEMY ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) )
- {
- return SCHED_ZOMBIE_WANDER_MEDIUM;
- }
-
- if( HasCondition( COND_ZOMBIE_CAN_SWAT_ATTACK ) )
- {
- return SCHED_ZOMBIE_SWATITEM;
- }
- break;
-
- case NPC_STATE_ALERT:
- if ( HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) )
- {
- ClearCondition( COND_LOST_ENEMY );
- ClearCondition( COND_ENEMY_UNREACHABLE );
-
-#ifdef DEBUG_ZOMBIES
- DevMsg("Wandering\n");
-#endif
-
- // Just lost track of our enemy.
- // Wander around a bit so we don't look like a dingus.
- return SCHED_ZOMBIE_WANDER_MEDIUM;
- }
- break;
- }
-
- return BaseClass::SelectSchedule();
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-bool CNPC_BaseZombie::IsSlumped( void )
-{
- if( hl2_episodic.GetBool() )
- {
- if( m_ActBusyBehavior.IsInsideActBusy() && !m_ActBusyBehavior.IsStopBusying() )
- {
- return true;
- }
- }
- else
- {
- int sequence = GetSequence();
- if ( sequence != -1 )
- {
- return ( strncmp( GetSequenceName( sequence ), "slump", 5 ) == 0 );
- }
- }
-
- return false;
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-bool CNPC_BaseZombie::IsGettingUp( void )
-{
- if( m_ActBusyBehavior.IsActive() && m_ActBusyBehavior.IsStopBusying() )
- {
- return true;
- }
- return false;
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CNPC_BaseZombie::GetSwatActivity( void )
-{
- // Hafta figure out whether to swat with left or right arm.
- // Also hafta figure out whether to swat high or low. (later)
- float flDot;
- Vector vecRight, vecDirToObj;
-
- AngleVectors( GetLocalAngles(), NULL, &vecRight, NULL );
-
- vecDirToObj = m_hPhysicsEnt->GetLocalOrigin() - GetLocalOrigin();
- VectorNormalize(vecDirToObj);
-
- // compare in 2D.
- vecRight.z = 0.0;
- vecDirToObj.z = 0.0;
-
- flDot = DotProduct( vecRight, vecDirToObj );
-
- Vector vecMyCenter;
- Vector vecObjCenter;
-
- vecMyCenter = WorldSpaceCenter();
- vecObjCenter = m_hPhysicsEnt->WorldSpaceCenter();
- float flZDiff;
-
- flZDiff = vecMyCenter.z - vecObjCenter.z;
-
- if( flDot >= 0 )
- {
- // Right
- if( flZDiff < 0 )
- {
- return ACT_ZOM_SWATRIGHTMID;
- }
-
- return ACT_ZOM_SWATRIGHTLOW;
- }
- else
- {
- // Left
- if( flZDiff < 0 )
- {
- return ACT_ZOM_SWATLEFTMID;
- }
-
- return ACT_ZOM_SWATLEFTLOW;
- }
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::GatherConditions( void )
-{
- ClearCondition( COND_ZOMBIE_LOCAL_MELEE_OBSTRUCTION );
-
- BaseClass::GatherConditions();
-
- if( m_NPCState == NPC_STATE_COMBAT && !m_fIsTorso )
- {
- // This check for !m_pPhysicsEnt prevents a crashing bug, but also
- // eliminates the zombie picking a better physics object if one happens to fall
- // between him and the object he's heading for already.
- if( gpGlobals->curtime >= m_flNextSwatScan && (m_hPhysicsEnt == NULL) )
- {
- FindNearestPhysicsObject( ZOMBIE_MAX_PHYSOBJ_MASS );
- m_flNextSwatScan = gpGlobals->curtime + 2.0;
- }
- }
-
- if( (m_hPhysicsEnt != NULL) && gpGlobals->curtime >= m_flNextSwat && HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_ZOMBIE_RELEASECRAB ) )
- {
- SetCondition( COND_ZOMBIE_CAN_SWAT_ATTACK );
- }
- else
- {
- ClearCondition( COND_ZOMBIE_CAN_SWAT_ATTACK );
- }
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::PrescheduleThink( void )
-{
- BaseClass::PrescheduleThink();
-
-#if 0
- DevMsg(" ** %d Angry Zombies **\n", s_iAngryZombies );
-#endif
-
-#if 0
- if( m_NPCState == NPC_STATE_COMBAT )
- {
- // Zombies should make idle sounds in combat
- if( random->RandomInt( 0, 30 ) == 0 )
- {
- IdleSound();
- }
- }
-#endif
-
- //
- // Cool off if we aren't burned for five seconds or so.
- //
- if ( ( m_flBurnDamageResetTime ) && ( gpGlobals->curtime >= m_flBurnDamageResetTime ) )
- {
- m_flBurnDamage = 0;
- }
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::StartTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_ZOMBIE_DIE:
- // Go to ragdoll
- KillMe();
- TaskComplete();
- break;
-
- case TASK_ZOMBIE_GET_PATH_TO_PHYSOBJ:
- {
- Vector vecGoalPos;
- Vector vecDir;
-
- vecDir = GetLocalOrigin() - m_hPhysicsEnt->GetLocalOrigin();
- VectorNormalize(vecDir);
- vecDir.z = 0;
-
- AI_NavGoal_t goal( m_hPhysicsEnt->WorldSpaceCenter() );
- goal.pTarget = m_hPhysicsEnt;
- GetNavigator()->SetGoal( goal );
-
- TaskComplete();
- }
- break;
-
- case TASK_ZOMBIE_SWAT_ITEM:
- {
- if( m_hPhysicsEnt == NULL )
- {
- // Physics Object is gone! Probably was an explosive
- // or something else broke it.
- TaskFail("Physics ent NULL");
- }
- else if ( DistToPhysicsEnt() > ZOMBIE_PHYSOBJ_SWATDIST )
- {
- // Physics ent is no longer in range! Probably another zombie swatted it or it moved
- // for some other reason.
- TaskFail( "Physics swat item has moved" );
- }
- else
- {
- SetIdealActivity( (Activity)GetSwatActivity() );
- }
- break;
- }
- break;
-
- case TASK_ZOMBIE_DELAY_SWAT:
- m_flNextSwat = gpGlobals->curtime + pTask->flTaskData;
- TaskComplete();
- break;
-
- case TASK_ZOMBIE_RELEASE_HEADCRAB:
- {
- // make the crab look like it's pushing off the body
- Vector vecForward;
- Vector vecVelocity;
-
- AngleVectors( GetAbsAngles(), &vecForward );
-
- vecVelocity = vecForward * 30;
- vecVelocity.z += 100;
-
- ReleaseHeadcrab( EyePosition(), vecVelocity, true, true );
- TaskComplete();
- }
- break;
-
- case TASK_ZOMBIE_WAIT_POST_MELEE:
- {
-#ifndef HL2_EPISODIC
- TaskComplete();
- return;
-#endif
-
- // Don't wait when attacking the player
- if ( GetEnemy() && GetEnemy()->IsPlayer() )
- {
- TaskComplete();
- return;
- }
-
- // Wait a single think
- SetWait( 0.1 );
- }
- break;
-
- default:
- BaseClass::StartTask( pTask );
- }
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::RunTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_ZOMBIE_SWAT_ITEM:
- if( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
-
- case TASK_ZOMBIE_WAIT_POST_MELEE:
- {
- if ( IsWaitFinished() )
- {
- TaskComplete();
- }
- }
- break;
- default:
- BaseClass::RunTask( pTask );
- break;
- }
-}
-
-
-//---------------------------------------------------------
-// Make the necessary changes to a zombie to make him a
-// torso!
-//---------------------------------------------------------
-void CNPC_BaseZombie::BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce )
-{
- if( m_fIsTorso )
- {
- DevMsg( "*** Zombie is already a torso!\n" );
- return;
- }
-
- if( IsOnFire() )
- {
- Extinguish();
- Ignite( 30 );
- }
-
- if ( !m_fIsHeadless )
- {
- m_iMaxHealth = ZOMBIE_TORSO_HEALTH_FACTOR * m_iMaxHealth;
- m_iHealth = m_iMaxHealth;
-
- // No more opening doors!
- CapabilitiesRemove( bits_CAP_DOORS_GROUP );
-
- ClearSchedule( "Becoming torso" );
- GetNavigator()->ClearGoal();
- m_hPhysicsEnt = NULL;
-
- // Put the zombie in a TOSS / fall schedule
- // Otherwise he fails and sits on the ground for a sec.
- SetSchedule( SCHED_FALL_TO_GROUND );
-
- m_fIsTorso = true;
-
- // Put the torso up where the torso was when the zombie
- // was whole.
- Vector origin = GetAbsOrigin();
- origin.z += 40;
- SetAbsOrigin( origin );
-
- SetGroundEntity( NULL );
- // assume zombie mass ~ 100 kg
- ApplyAbsVelocityImpulse( vecTorsoForce * (1.0 / 100.0) );
- }
-
- float flFadeTime = 0.0;
-
- if( HasSpawnFlags( SF_NPC_FADE_CORPSE ) )
- {
- flFadeTime = 5.0;
- }
-
- if ( m_fIsTorso == true )
- {
- // -40 on Z to make up for the +40 on Z that we did above. This stops legs spawning above the head.
- CBaseEntity *pGib = CreateRagGib( GetLegsModel(), GetAbsOrigin() - Vector(0, 0, 40), GetAbsAngles(), vecLegsForce, flFadeTime );
-
- // don't collide with this thing ever
- if ( pGib )
- {
- pGib->SetOwnerEntity( this );
- }
- }
-
-
- SetZombieModel();
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::Event_Killed( const CTakeDamageInfo &info )
-{
- if ( info.GetDamageType() & DMG_VEHICLE )
- {
- Vector vecDamageDir = info.GetDamageForce();
- VectorNormalize( vecDamageDir );
-
- // Big blood splat
- UTIL_BloodSpray( WorldSpaceCenter(), vecDamageDir, BLOOD_COLOR_YELLOW, 8, FX_BLOODSPRAY_CLOUD );
- }
-
- BaseClass::Event_Killed( info );
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-bool CNPC_BaseZombie::BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector )
-{
- bool bKilledByVehicle = ( ( info.GetDamageType() & DMG_VEHICLE ) != 0 );
- if( m_fIsTorso || (!IsChopped(info) && !IsSquashed(info)) || bKilledByVehicle )
- {
- return BaseClass::BecomeRagdoll( info, forceVector );
- }
-
- if( !(GetFlags()&FL_TRANSRAGDOLL) )
- {
- RemoveDeferred();
- }
-
- return true;
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::StopLoopingSounds()
-{
- ENVELOPE_CONTROLLER.SoundDestroy( m_pMoanSound );
- m_pMoanSound = NULL;
-
- BaseClass::StopLoopingSounds();
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_BaseZombie::RemoveHead( void )
-{
- m_fIsHeadless = true;
- SetZombieModel();
-}
-
-
-bool CNPC_BaseZombie::ShouldPlayFootstepMoan( void )
-{
- if( random->RandomInt( 1, zombie_stepfreq.GetInt() * s_iAngryZombies ) == 1 )
- {
- return true;
- }
-
- return false;
-}
-
-
-#define ZOMBIE_CRAB_INHERITED_SPAWNFLAGS (SF_NPC_GAG|SF_NPC_LONG_RANGE|SF_NPC_FADE_CORPSE|SF_NPC_ALWAYSTHINK)
-#define CRAB_HULL_EXPAND 1.1f
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_BaseZombie::HeadcrabFits( CBaseAnimating *pCrab )
-{
- Vector vecSpawnLoc = pCrab->GetAbsOrigin();
-
- CTraceFilterSimpleList traceFilter( COLLISION_GROUP_NONE );
- traceFilter.AddEntityToIgnore( pCrab );
- traceFilter.AddEntityToIgnore( this );
- if ( GetInteractionPartner() )
- {
- traceFilter.AddEntityToIgnore( GetInteractionPartner() );
- }
-
- trace_t tr;
- AI_TraceHull( vecSpawnLoc,
- vecSpawnLoc - Vector( 0, 0, 1 ),
- NAI_Hull::Mins(HULL_TINY) * CRAB_HULL_EXPAND,
- NAI_Hull::Maxs(HULL_TINY) * CRAB_HULL_EXPAND,
- MASK_NPCSOLID,
- &traceFilter,
- &tr );
-
- if( tr.fraction != 1.0 )
- {
- //NDebugOverlay::Box( vecSpawnLoc, NAI_Hull::Mins(HULL_TINY) * CRAB_HULL_EXPAND, NAI_Hull::Maxs(HULL_TINY) * CRAB_HULL_EXPAND, 255, 0, 0, 100, 10.0 );
- return false;
- }
-
- //NDebugOverlay::Box( vecSpawnLoc, NAI_Hull::Mins(HULL_TINY) * CRAB_HULL_EXPAND, NAI_Hull::Maxs(HULL_TINY) * CRAB_HULL_EXPAND, 0, 255, 0, 100, 10.0 );
- return true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &vecOrigin -
-// &vecVelocity -
-// fRemoveHead -
-// fRagdollBody -
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &vecVelocity, bool fRemoveHead, bool fRagdollBody, bool fRagdollCrab )
-{
- CAI_BaseNPC *pCrab;
- Vector vecSpot = vecOrigin;
-
- // Until the headcrab is a bodygroup, we have to approximate the
- // location of the head with magic numbers.
- if( !m_fIsTorso )
- {
- vecSpot.z -= 16;
- }
-
- if( fRagdollCrab )
- {
- //Vector vecForce = Vector( 0, 0, random->RandomFloat( 700, 1100 ) );
- CBaseEntity *pGib = CreateRagGib( GetHeadcrabModel(), vecOrigin, GetLocalAngles(), vecVelocity, 15, ShouldIgniteZombieGib() );
-
- if ( pGib )
- {
- CBaseAnimating *pAnimatingGib = dynamic_cast<CBaseAnimating*>(pGib);
-
- // don't collide with this thing ever
- int iCrabAttachment = LookupAttachment( "headcrab" );
- if (iCrabAttachment > 0 && pAnimatingGib )
- {
- SetHeadcrabSpawnLocation( iCrabAttachment, pAnimatingGib );
- }
-
- if( !HeadcrabFits(pAnimatingGib) )
- {
- UTIL_Remove(pGib);
- return;
- }
-
- pGib->SetOwnerEntity( this );
- CopyRenderColorTo( pGib );
-
-
- if( UTIL_ShouldShowBlood(BLOOD_COLOR_YELLOW) )
- {
- UTIL_BloodImpact( pGib->WorldSpaceCenter(), Vector(0,0,1), BLOOD_COLOR_YELLOW, 1 );
-
- for ( int i = 0 ; i < 3 ; i++ )
- {
- Vector vecSpot = pGib->WorldSpaceCenter();
-
- vecSpot.x += random->RandomFloat( -8, 8 );
- vecSpot.y += random->RandomFloat( -8, 8 );
- vecSpot.z += random->RandomFloat( -8, 8 );
-
- UTIL_BloodDrips( vecSpot, vec3_origin, BLOOD_COLOR_YELLOW, 50 );
- }
- }
- }
- }
- else
- {
- pCrab = (CAI_BaseNPC*)CreateEntityByName( GetHeadcrabClassname() );
-
- if ( !pCrab )
- {
- Warning( "**%s: Can't make %s!\n", GetClassname(), GetHeadcrabClassname() );
- return;
- }
-
- // Stick the crab in whatever squad the zombie was in.
- pCrab->SetSquadName( m_SquadName );
-
- // don't pop to floor, fall
- pCrab->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
-
- // add on the parent flags
- pCrab->AddSpawnFlags( m_spawnflags & ZOMBIE_CRAB_INHERITED_SPAWNFLAGS );
-
- // make me the crab's owner to avoid collision issues
- pCrab->SetOwnerEntity( this );
-
- pCrab->SetAbsOrigin( vecSpot );
- pCrab->SetAbsAngles( GetAbsAngles() );
- DispatchSpawn( pCrab );
-
- pCrab->GetMotor()->SetIdealYaw( GetAbsAngles().y );
-
- // FIXME: npc's with multiple headcrabs will need some way to query different attachments.
- // NOTE: this has till after spawn is called so that the model is set up
- int iCrabAttachment = LookupAttachment( "headcrab" );
- if (iCrabAttachment > 0)
- {
- SetHeadcrabSpawnLocation( iCrabAttachment, pCrab );
- pCrab->GetMotor()->SetIdealYaw( pCrab->GetAbsAngles().y );
-
- // Take out any pitch
- QAngle angles = pCrab->GetAbsAngles();
- angles.x = 0.0;
- pCrab->SetAbsAngles( angles );
- }
-
- if( !HeadcrabFits(pCrab) )
- {
- UTIL_Remove(pCrab);
- return;
- }
-
- pCrab->SetActivity( ACT_IDLE );
- pCrab->SetNextThink( gpGlobals->curtime );
- pCrab->PhysicsSimulate();
- pCrab->SetAbsVelocity( vecVelocity );
-
- // if I have an enemy, stuff that to the headcrab.
- CBaseEntity *pEnemy;
- pEnemy = GetEnemy();
-
- pCrab->m_flNextAttack = gpGlobals->curtime + 1.0f;
-
- if( pEnemy )
- {
- pCrab->SetEnemy( pEnemy );
- }
- if( ShouldIgniteZombieGib() )
- {
- pCrab->Ignite( 30 );
- }
-
- CopyRenderColorTo( pCrab );
-
- pCrab->Activate();
- }
-
- if( fRemoveHead )
- {
- RemoveHead();
- }
-
- if( fRagdollBody )
- {
- BecomeRagdollOnClient( vec3_origin );
- }
-}
-
-
-
-void CNPC_BaseZombie::SetHeadcrabSpawnLocation( int iCrabAttachment, CBaseAnimating *pCrab )
-{
- Assert( iCrabAttachment > 0 );
-
- // get world location of intended headcrab root bone
- matrix3x4_t attachmentToWorld;
- GetAttachment( iCrabAttachment, attachmentToWorld );
-
- // find offset of root bone from origin
- pCrab->SetAbsOrigin( Vector( 0, 0, 0 ) );
- pCrab->SetAbsAngles( QAngle( 0, 0, 0 ) );
- pCrab->InvalidateBoneCache();
- matrix3x4_t rootLocal;
- pCrab->GetBoneTransform( 0, rootLocal );
-
- // invert it
- matrix3x4_t rootInvLocal;
- MatrixInvert( rootLocal, rootInvLocal );
-
- // find spawn location needed for rootLocal transform to match attachmentToWorld
- matrix3x4_t spawnOrigin;
- ConcatTransforms( attachmentToWorld, rootInvLocal, spawnOrigin );
-
- // reset location of headcrab
- Vector vecOrigin;
- QAngle vecAngles;
- MatrixAngles( spawnOrigin, vecAngles, vecOrigin );
- pCrab->SetAbsOrigin( vecOrigin );
-
- // FIXME: head crabs don't like pitch or roll!
- vecAngles.z = 0;
-
- pCrab->SetAbsAngles( vecAngles );
- pCrab->InvalidateBoneCache();
-}
-
-
-
-//---------------------------------------------------------
-// Provides a standard way for the zombie to get the
-// distance to a physics ent. Since the code to find physics
-// objects uses a fast dis approx, we have to use that here
-// as well.
-//---------------------------------------------------------
-float CNPC_BaseZombie::DistToPhysicsEnt( void )
-{
- //return ( GetLocalOrigin() - m_hPhysicsEnt->GetLocalOrigin() ).Length();
- if ( m_hPhysicsEnt != NULL )
- return UTIL_DistApprox2D( GetAbsOrigin(), m_hPhysicsEnt->WorldSpaceCenter() );
- return ZOMBIE_PHYSOBJ_SWATDIST + 1;
-}
-
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
-{
- switch( NewState )
- {
- case NPC_STATE_COMBAT:
- {
- RemoveSpawnFlags( SF_NPC_GAG );
- s_iAngryZombies++;
- }
- break;
-
- default:
- if( OldState == NPC_STATE_COMBAT )
- {
- // Only decrement if coming OUT of combat state.
- s_iAngryZombies--;
- }
- break;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Refines a base activity into something more specific to our internal state.
-//-----------------------------------------------------------------------------
-Activity CNPC_BaseZombie::NPC_TranslateActivity( Activity baseAct )
-{
- if ( baseAct == ACT_WALK && IsCurSchedule( SCHED_COMBAT_PATROL, false) )
- baseAct = ACT_RUN;
-
- if ( IsOnFire() )
- {
- switch ( baseAct )
- {
- case ACT_RUN_ON_FIRE:
- {
- return ( Activity )ACT_WALK_ON_FIRE;
- }
-
- case ACT_WALK:
- {
- // I'm on fire. Put ME out.
- return ( Activity )ACT_WALK_ON_FIRE;
- }
-
- case ACT_IDLE:
- {
- // I'm on fire. Put ME out.
- return ( Activity )ACT_IDLE_ON_FIRE;
- }
- }
- }
-
- return BaseClass::NPC_TranslateActivity( baseAct );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_BaseZombie::BodyTarget( const Vector &posSrc, bool bNoisy )
-{
-
- if( IsCurSchedule(SCHED_BIG_FLINCH) || m_ActBusyBehavior.IsActive() )
- {
- // This zombie is assumed to be standing up.
- // Return a position that's centered over the absorigin,
- // halfway between the origin and the head.
- Vector vecTarget = GetAbsOrigin();
- Vector vecHead = HeadTarget( posSrc );
- vecTarget.z = ((vecTarget.z + vecHead.z) * 0.5f);
- return vecTarget;
- }
-
- return BaseClass::BodyTarget( posSrc, bNoisy );
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-Vector CNPC_BaseZombie::HeadTarget( const Vector &posSrc )
-{
- int iCrabAttachment = LookupAttachment( "headcrab" );
- Assert( iCrabAttachment > 0 );
-
- Vector vecPosition;
-
- GetAttachment( iCrabAttachment, vecPosition );
-
- return vecPosition;
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-float CNPC_BaseZombie::GetAutoAimRadius()
-{
- if( m_fIsTorso )
- {
- return 12.0f;
- }
-
- return BaseClass::GetAutoAimRadius();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-bool CNPC_BaseZombie::OnInsufficientStopDist( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult )
-{
- if ( pMoveGoal->directTrace.fStatus == AIMR_BLOCKED_ENTITY && gpGlobals->curtime >= m_flNextSwat )
- {
- m_hObstructor = pMoveGoal->directTrace.pObstruction;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : *pEnemy -
-// &chasePosition -
-//-----------------------------------------------------------------------------
-void CNPC_BaseZombie::TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition )
-{
- // If our enemy is in a vehicle, we need them to tell us where to navigate to them
- if ( pEnemy == NULL )
- return;
-
- CBaseCombatCharacter *pBCC = pEnemy->MyCombatCharacterPointer();
- if ( pBCC && pBCC->IsInAVehicle() )
- {
- Vector vecForward, vecRight;
- pBCC->GetVectors( &vecForward, &vecRight, NULL );
-
- chasePosition = pBCC->WorldSpaceCenter() + ( vecForward * 24.0f ) + ( vecRight * 48.0f );
- return;
- }
-
- BaseClass::TranslateNavGoal( pEnemy, chasePosition );
-}
-
-//-----------------------------------------------------------------------------
-//
-// Schedules
-//
-//-----------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC( base_zombie, CNPC_BaseZombie )
-
- DECLARE_TASK( TASK_ZOMBIE_DELAY_SWAT )
- DECLARE_TASK( TASK_ZOMBIE_SWAT_ITEM )
- DECLARE_TASK( TASK_ZOMBIE_GET_PATH_TO_PHYSOBJ )
- DECLARE_TASK( TASK_ZOMBIE_DIE )
- DECLARE_TASK( TASK_ZOMBIE_RELEASE_HEADCRAB )
- DECLARE_TASK( TASK_ZOMBIE_WAIT_POST_MELEE )
-
- DECLARE_ACTIVITY( ACT_ZOM_SWATLEFTMID )
- DECLARE_ACTIVITY( ACT_ZOM_SWATRIGHTMID )
- DECLARE_ACTIVITY( ACT_ZOM_SWATLEFTLOW )
- DECLARE_ACTIVITY( ACT_ZOM_SWATRIGHTLOW )
- DECLARE_ACTIVITY( ACT_ZOM_RELEASECRAB )
- DECLARE_ACTIVITY( ACT_ZOM_FALL )
-
- DECLARE_CONDITION( COND_ZOMBIE_CAN_SWAT_ATTACK )
- DECLARE_CONDITION( COND_ZOMBIE_RELEASECRAB )
- DECLARE_CONDITION( COND_ZOMBIE_LOCAL_MELEE_OBSTRUCTION )
-
- //Adrian: events go here
- DECLARE_ANIMEVENT( AE_ZOMBIE_ATTACK_RIGHT )
- DECLARE_ANIMEVENT( AE_ZOMBIE_ATTACK_LEFT )
- DECLARE_ANIMEVENT( AE_ZOMBIE_ATTACK_BOTH )
- DECLARE_ANIMEVENT( AE_ZOMBIE_SWATITEM )
- DECLARE_ANIMEVENT( AE_ZOMBIE_STARTSWAT )
- DECLARE_ANIMEVENT( AE_ZOMBIE_STEP_LEFT )
- DECLARE_ANIMEVENT( AE_ZOMBIE_STEP_RIGHT )
- DECLARE_ANIMEVENT( AE_ZOMBIE_SCUFF_LEFT )
- DECLARE_ANIMEVENT( AE_ZOMBIE_SCUFF_RIGHT )
- DECLARE_ANIMEVENT( AE_ZOMBIE_ATTACK_SCREAM )
- DECLARE_ANIMEVENT( AE_ZOMBIE_GET_UP )
- DECLARE_ANIMEVENT( AE_ZOMBIE_POUND )
- DECLARE_ANIMEVENT( AE_ZOMBIE_ALERTSOUND )
- DECLARE_ANIMEVENT( AE_ZOMBIE_POPHEADCRAB )
-
- DECLARE_INTERACTION( g_interactionZombieMeleeWarning )
-
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_MOVE_SWATITEM,
-
- " Tasks"
- " TASK_ZOMBIE_DELAY_SWAT 3"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
- " TASK_ZOMBIE_GET_PATH_TO_PHYSOBJ 0"
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- " TASK_ZOMBIE_SWAT_ITEM 0"
- " "
- " Interrupts"
- " COND_ZOMBIE_RELEASECRAB"
- " COND_ENEMY_DEAD"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // SwatItem
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_SWATITEM,
-
- " Tasks"
- " TASK_ZOMBIE_DELAY_SWAT 3"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
- " TASK_FACE_ENEMY 0"
- " TASK_ZOMBIE_SWAT_ITEM 0"
- " "
- " Interrupts"
- " COND_ZOMBIE_RELEASECRAB"
- " COND_ENEMY_DEAD"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_ATTACKITEM,
-
- " Tasks"
- " TASK_FACE_ENEMY 0"
- " TASK_MELEE_ATTACK1 0"
- " "
- " Interrupts"
- " COND_ZOMBIE_RELEASECRAB"
- " COND_ENEMY_DEAD"
- " COND_NEW_ENEMY"
- )
-
- //=========================================================
- // ChaseEnemy
- //=========================================================
-#ifdef HL2_EPISODIC
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_CHASE_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
- " TASK_SET_TOLERANCE_DISTANCE 24"
- " TASK_GET_CHASE_PATH_TO_ENEMY 600"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK2"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_TASK_FAILED"
- " COND_ZOMBIE_CAN_SWAT_ATTACK"
- " COND_ZOMBIE_RELEASECRAB"
- " COND_HEAVY_DAMAGE"
- )
-#else
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_CHASE_ENEMY,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
- " TASK_SET_TOLERANCE_DISTANCE 24"
- " TASK_GET_CHASE_PATH_TO_ENEMY 600"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_FACE_ENEMY 0"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_ENEMY_DEAD"
- " COND_ENEMY_UNREACHABLE"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK2"
- " COND_TOO_CLOSE_TO_ATTACK"
- " COND_TASK_FAILED"
- " COND_ZOMBIE_CAN_SWAT_ATTACK"
- " COND_ZOMBIE_RELEASECRAB"
- )
-#endif // HL2_EPISODIC
-
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_RELEASECRAB,
-
- " Tasks"
- " TASK_PLAY_PRIVATE_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ZOM_RELEASECRAB"
- " TASK_ZOMBIE_RELEASE_HEADCRAB 0"
- " TASK_ZOMBIE_DIE 0"
- " "
- " Interrupts"
- " COND_TASK_FAILED"
- )
-
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_MOVE_TO_AMBUSH,
-
- " Tasks"
- " TASK_WAIT 1.0" // don't react as soon as you see the player.
- " TASK_FIND_COVER_FROM_ENEMY 0"
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_TURN_LEFT 180"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_ZOMBIE_WAIT_AMBUSH"
- " "
- " Interrupts"
- " COND_TASK_FAILED"
- " COND_NEW_ENEMY"
- )
-
-
- //=========================================================
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_WAIT_AMBUSH,
-
- " Tasks"
- " TASK_WAIT_FACE_ENEMY 99999"
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_SEE_ENEMY"
- )
-
- //=========================================================
- // Wander around for a while so we don't look stupid.
- // this is done if we ever lose track of our enemy.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_WANDER_MEDIUM,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 480384" // 4 feet to 32 feet
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT_PVS 0" // if the player left my PVS, just wait.
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_ZOMBIE_WANDER_MEDIUM" // keep doing it
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_SEE_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_WANDER_STANDOFF,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WANDER 480384" // 4 feet to 32 feet
- " TASK_WALK_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT_PVS 0" // if the player left my PVS, just wait.
- " "
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_ENEMY_DEAD"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK2"
- " COND_ZOMBIE_RELEASECRAB"
- )
-
- //=========================================================
- // If you fail to wander, wait just a bit and try again.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_WANDER_FAIL,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_WAIT 1"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_ZOMBIE_WANDER_MEDIUM"
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_ENEMY_DEAD"
- " COND_CAN_RANGE_ATTACK1"
- " COND_CAN_MELEE_ATTACK1"
- " COND_CAN_RANGE_ATTACK2"
- " COND_CAN_MELEE_ATTACK2"
- " COND_ZOMBIE_RELEASECRAB"
- )
-
- //=========================================================
- // Like the base class, only don't stop in the middle of
- // swinging if the enemy is killed, hides, or new enemy.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_MELEE_ATTACK1,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
- " TASK_MELEE_ATTACK1 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_ZOMBIE_POST_MELEE_WAIT"
- ""
- " Interrupts"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- // Make the zombie wait a frame after a melee attack, to
- // allow itself & it's enemy to test for dynamic scripted sequences.
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ZOMBIE_POST_MELEE_WAIT,
-
- " Tasks"
- " TASK_ZOMBIE_WAIT_POST_MELEE 0"
- )
-
-AI_END_CUSTOM_NPC()
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Implements the zombie, a horrific once-human headcrab victim. +// +// The zombie has two main states: Full and Torso. +// +// In Full state, the zombie is whole and walks upright as he did in Half-Life. +// He will try to claw the player and swat physics items at him. +// +// In Torso state, the zombie has been blasted or cut in half, and the Torso will +// drag itself along the ground with its arms. It will try to claw the player. +// +// In either state, a severely injured Zombie will release its headcrab, which +// will immediately go after the player. The Zombie will then die (ragdoll). +// +//=============================================================================// + +#include "cbase.h" +#include "npc_BaseZombie.h" +#include "player.h" +#include "game.h" +#include "ai_network.h" +#include "ai_navigator.h" +#include "ai_motor.h" +#include "ai_default.h" +#include "ai_schedule.h" +#include "ai_hull.h" +#include "ai_node.h" +#include "ai_memory.h" +#include "ai_senses.h" +#include "bitstring.h" +#include "EntityFlame.h" +#include "hl2_shareddefs.h" +#include "npcevent.h" +#include "activitylist.h" +#include "entitylist.h" +#include "gib.h" +#include "soundenvelope.h" +#include "ndebugoverlay.h" +#include "rope.h" +#include "rope_shared.h" +#include "igamesystem.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "props.h" +#include "hl2_gamerules.h" +#include "weapon_physcannon.h" +#include "ammodef.h" +#include "vehicle_base.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar sk_npc_head; + +#define ZOMBIE_BULLET_DAMAGE_SCALE 0.5f + +int g_interactionZombieMeleeWarning; + +envelopePoint_t envDefaultZombieMoanVolumeFast[] = +{ + { 1.0f, 1.0f, + 0.1f, 0.1f, + }, + { 0.0f, 0.0f, + 0.2f, 0.3f, + }, +}; + +envelopePoint_t envDefaultZombieMoanVolume[] = +{ + { 1.0f, 0.1f, + 0.1f, 0.1f, + }, + { 1.0f, 1.0f, + 0.2f, 0.2f, + }, + { 0.0f, 0.0f, + 0.3f, 0.4f, + }, +}; + + +// if the zombie doesn't find anything closer than this, it doesn't swat. +#define ZOMBIE_FARTHEST_PHYSICS_OBJECT 40.0*12.0 +#define ZOMBIE_PHYSICS_SEARCH_DEPTH 100 + +// Don't swat objects unless player is closer than this. +#define ZOMBIE_PLAYER_MAX_SWAT_DIST 1000 + +// +// How much health a Zombie torso gets when a whole zombie is broken +// It's whole zombie's MAX Health * this value +#define ZOMBIE_TORSO_HEALTH_FACTOR 0.5 + +// +// When the zombie has health < m_iMaxHealth * this value, it will +// try to release its headcrab. +#define ZOMBIE_RELEASE_HEALTH_FACTOR 0.5 + +// +// The heaviest physics object that a zombie should try to swat. (kg) +#define ZOMBIE_MAX_PHYSOBJ_MASS 60 + +// +// Zombie tries to get this close to a physics object's origin to swat it +#define ZOMBIE_PHYSOBJ_SWATDIST 80 + +// +// Because movement code sometimes doesn't get us QUITE where we +// want to go, the zombie tries to get this close to a physics object +// Zombie will end up somewhere between PHYSOBJ_MOVE_TO_DIST & PHYSOBJ_SWATDIST +#define ZOMBIE_PHYSOBJ_MOVE_TO_DIST 48 + +// +// How long between physics swat attacks (in seconds). +#define ZOMBIE_SWAT_DELAY 5 + + +// +// After taking damage, ignore further damage for n seconds. This keeps the zombie +// from being interrupted while. +// +#define ZOMBIE_FLINCH_DELAY 3 + + +#define ZOMBIE_BURN_TIME 10 // If ignited, burn for this many seconds +#define ZOMBIE_BURN_TIME_NOISE 2 // Give or take this many seconds. + + +//========================================================= +// private activities +//========================================================= +int CNPC_BaseZombie::ACT_ZOM_SWATLEFTMID; +int CNPC_BaseZombie::ACT_ZOM_SWATRIGHTMID; +int CNPC_BaseZombie::ACT_ZOM_SWATLEFTLOW; +int CNPC_BaseZombie::ACT_ZOM_SWATRIGHTLOW; +int CNPC_BaseZombie::ACT_ZOM_RELEASECRAB; +int CNPC_BaseZombie::ACT_ZOM_FALL; + +ConVar sk_zombie_dmg_one_slash( "sk_zombie_dmg_one_slash","0"); +ConVar sk_zombie_dmg_both_slash( "sk_zombie_dmg_both_slash","0"); + + +// When a zombie spawns, he will select a 'base' pitch value +// that's somewhere between basepitchmin & basepitchmax +ConVar zombie_basemin( "zombie_basemin", "100" ); +ConVar zombie_basemax( "zombie_basemax", "100" ); + +ConVar zombie_changemin( "zombie_changemin", "0" ); +ConVar zombie_changemax( "zombie_changemax", "0" ); + +// play a sound once in every zombie_stepfreq steps +ConVar zombie_stepfreq( "zombie_stepfreq", "4" ); +ConVar zombie_moanfreq( "zombie_moanfreq", "1" ); + +ConVar zombie_decaymin( "zombie_decaymin", "0.1" ); +ConVar zombie_decaymax( "zombie_decaymax", "0.4" ); + +ConVar zombie_ambushdist( "zombie_ambushdist", "16000" ); + +//========================================================= +// For a couple of reasons, we keep a running count of how +// many zombies in the world are angry at any given time. +//========================================================= +static int s_iAngryZombies = 0; + +//========================================================= +//========================================================= +class CAngryZombieCounter : public CAutoGameSystem +{ +public: + CAngryZombieCounter( char const *name ) : CAutoGameSystem( name ) + { + } + // Level init, shutdown + virtual void LevelInitPreEntity() + { + s_iAngryZombies = 0; + } +}; + +CAngryZombieCounter AngryZombieCounter( "CAngryZombieCounter" ); + + +int AE_ZOMBIE_ATTACK_RIGHT; +int AE_ZOMBIE_ATTACK_LEFT; +int AE_ZOMBIE_ATTACK_BOTH; +int AE_ZOMBIE_SWATITEM; +int AE_ZOMBIE_STARTSWAT; +int AE_ZOMBIE_STEP_LEFT; +int AE_ZOMBIE_STEP_RIGHT; +int AE_ZOMBIE_SCUFF_LEFT; +int AE_ZOMBIE_SCUFF_RIGHT; +int AE_ZOMBIE_ATTACK_SCREAM; +int AE_ZOMBIE_GET_UP; +int AE_ZOMBIE_POUND; +int AE_ZOMBIE_ALERTSOUND; +int AE_ZOMBIE_POPHEADCRAB; + + +//========================================================= +//========================================================= +BEGIN_DATADESC( CNPC_BaseZombie ) + + DEFINE_SOUNDPATCH( m_pMoanSound ), + DEFINE_FIELD( m_fIsTorso, FIELD_BOOLEAN ), + DEFINE_FIELD( m_fIsHeadless, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flNextFlinch, FIELD_TIME ), + DEFINE_FIELD( m_bHeadShot, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flBurnDamage, FIELD_FLOAT ), + DEFINE_FIELD( m_flBurnDamageResetTime, FIELD_TIME ), + DEFINE_FIELD( m_hPhysicsEnt, FIELD_EHANDLE ), + DEFINE_FIELD( m_flNextMoanSound, FIELD_TIME ), + DEFINE_FIELD( m_flNextSwat, FIELD_TIME ), + DEFINE_FIELD( m_flNextSwatScan, FIELD_TIME ), + DEFINE_FIELD( m_crabHealth, FIELD_FLOAT ), + DEFINE_FIELD( m_flMoanPitch, FIELD_FLOAT ), + DEFINE_FIELD( m_iMoanSound, FIELD_INTEGER ), + DEFINE_FIELD( m_hObstructor, FIELD_EHANDLE ), + DEFINE_FIELD( m_bIsSlumped, FIELD_BOOLEAN ), + +END_DATADESC() + + +//LINK_ENTITY_TO_CLASS( base_zombie, CNPC_BaseZombie ); + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CNPC_BaseZombie::g_numZombies = 0; + + +//--------------------------------------------------------- +//--------------------------------------------------------- +CNPC_BaseZombie::CNPC_BaseZombie() +{ + // Gotta select which sound we're going to play, right here! + // Because everyone's constructed before they spawn. + // + // Assign moan sounds in order, over and over. + // This means if 3 or so zombies spawn near each + // other, they will definitely not pick the same + // moan loop. + m_iMoanSound = g_numZombies; + + g_numZombies++; +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +CNPC_BaseZombie::~CNPC_BaseZombie() +{ + g_numZombies--; +} + + +//--------------------------------------------------------- +// The closest physics object is chosen that is: +// <= MaxMass in Mass +// Between the zombie and the enemy +// not too far from a direct line to the enemy. +//--------------------------------------------------------- +bool CNPC_BaseZombie::FindNearestPhysicsObject( int iMaxMass ) +{ + CBaseEntity *pList[ ZOMBIE_PHYSICS_SEARCH_DEPTH ]; + CBaseEntity *pNearest = NULL; + float flDist; + IPhysicsObject *pPhysObj; + int i; + Vector vecDirToEnemy; + Vector vecDirToObject; + + if ( !CanSwatPhysicsObjects() || !GetEnemy() ) + { + // Can't swat, or no enemy, so no swat. + m_hPhysicsEnt = NULL; + return false; + } + + vecDirToEnemy = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); + float dist = VectorNormalize(vecDirToEnemy); + vecDirToEnemy.z = 0; + + if( dist > ZOMBIE_PLAYER_MAX_SWAT_DIST ) + { + // Player is too far away. Don't bother + // trying to swat anything at them until + // they are closer. + return false; + } + + float flNearestDist = MIN( dist, ZOMBIE_FARTHEST_PHYSICS_OBJECT * 0.5 ); + Vector vecDelta( flNearestDist, flNearestDist, GetHullHeight() * 2.0 ); + + class CZombieSwatEntitiesEnum : public CFlaggedEntitiesEnum + { + public: + CZombieSwatEntitiesEnum( CBaseEntity **pList, int listMax, int iMaxMass ) + : CFlaggedEntitiesEnum( pList, listMax, 0 ), + m_iMaxMass( iMaxMass ) + { + } + + virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) + { + CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); + if ( pEntity && + pEntity->VPhysicsGetObject() && + pEntity->VPhysicsGetObject()->GetMass() <= m_iMaxMass && + pEntity->VPhysicsGetObject()->IsAsleep() && + pEntity->VPhysicsGetObject()->IsMoveable() ) + { + return CFlaggedEntitiesEnum::EnumElement( pHandleEntity ); + } + return ITERATION_CONTINUE; + } + + int m_iMaxMass; + }; + + CZombieSwatEntitiesEnum swatEnum( pList, ZOMBIE_PHYSICS_SEARCH_DEPTH, iMaxMass ); + + int count = UTIL_EntitiesInBox( GetAbsOrigin() - vecDelta, GetAbsOrigin() + vecDelta, &swatEnum ); + + // magically know where they are + Vector vecZombieKnees; + CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.25f ), &vecZombieKnees ); + + for( i = 0 ; i < count ; i++ ) + { + pPhysObj = pList[ i ]->VPhysicsGetObject(); + + Assert( !( !pPhysObj || pPhysObj->GetMass() > iMaxMass || !pPhysObj->IsAsleep() ) ); + + Vector center = pList[ i ]->WorldSpaceCenter(); + flDist = UTIL_DistApprox2D( GetAbsOrigin(), center ); + + if( flDist >= flNearestDist ) + continue; + + // This object is closer... but is it between the player and the zombie? + vecDirToObject = pList[ i ]->WorldSpaceCenter() - GetAbsOrigin(); + VectorNormalize(vecDirToObject); + vecDirToObject.z = 0; + + if( DotProduct( vecDirToEnemy, vecDirToObject ) < 0.8 ) + continue; + + if( flDist >= UTIL_DistApprox2D( center, GetEnemy()->GetAbsOrigin() ) ) + continue; + + // don't swat things where the highest point is under my knees + // NOTE: This is a rough test; a more exact test is going to occur below + if ( (center.z + pList[i]->BoundingRadius()) < vecZombieKnees.z ) + continue; + + // don't swat things that are over my head. + if( center.z > EyePosition().z ) + continue; + + vcollide_t *pCollide = modelinfo->GetVCollide( pList[i]->GetModelIndex() ); + + Vector objMins, objMaxs; + physcollision->CollideGetAABB( &objMins, &objMaxs, pCollide->solids[0], pList[i]->GetAbsOrigin(), pList[i]->GetAbsAngles() ); + + if ( objMaxs.z < vecZombieKnees.z ) + continue; + + if ( !FVisible( pList[i] ) ) + continue; + + if ( hl2_episodic.GetBool() ) + { + // Skip things that the enemy can't see. Do we want this as a general thing? + // The case for this feature is that zombies who are pursuing the player will + // stop along the way to swat objects at the player who is around the corner or + // otherwise not in a place that the object has a hope of hitting. This diversion + // makes the zombies very late (in a random fashion) getting where they are going. (sjb 1/2/06) + if( !GetEnemy()->FVisible( pList[i] ) ) + continue; + } + + // Make this the last check, since it makes a string. + // Don't swat server ragdolls! + if ( FClassnameIs( pList[ i ], "physics_prop_ragdoll" ) ) + continue; + + if ( FClassnameIs( pList[ i ], "prop_ragdoll" ) ) + continue; + + // The object must also be closer to the zombie than it is to the enemy + pNearest = pList[ i ]; + flNearestDist = flDist; + } + + m_hPhysicsEnt = pNearest; + + if( m_hPhysicsEnt == NULL ) + { + return false; + } + else + { + return true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns this monster's place in the relationship table. +//----------------------------------------------------------------------------- +Class_T CNPC_BaseZombie::Classify( void ) +{ + if ( IsSlumped() ) + return CLASS_NONE; + + return( CLASS_ZOMBIE ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +Disposition_t CNPC_BaseZombie::IRelationType( CBaseEntity *pTarget ) +{ + // Slumping should not affect Zombie's opinion of others + if ( IsSlumped() ) + { + m_bIsSlumped = false; + Disposition_t result = BaseClass::IRelationType( pTarget ); + m_bIsSlumped = true; + return result; + } + + return BaseClass::IRelationType( pTarget ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the maximum yaw speed based on the monster's current activity. +//----------------------------------------------------------------------------- +float CNPC_BaseZombie::MaxYawSpeed( void ) +{ + if( m_fIsTorso ) + { + return( 60 ); + } + else if (IsMoving() && HasPoseParameter( GetSequence(), m_poseMove_Yaw )) + { + return( 15 ); + } + else + { + switch( GetActivity() ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + return 100; + break; + case ACT_RUN: + return 15; + break; + case ACT_WALK: + case ACT_IDLE: + return 25; + break; + case ACT_RANGE_ATTACK1: + case ACT_RANGE_ATTACK2: + case ACT_MELEE_ATTACK1: + case ACT_MELEE_ATTACK2: + return 120; + default: + return 90; + break; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: turn in the direction of movement +// Output : +//----------------------------------------------------------------------------- +bool CNPC_BaseZombie::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval ) +{ + if (!HasPoseParameter( GetSequence(), m_poseMove_Yaw )) + { + return BaseClass::OverrideMoveFacing( move, flInterval ); + } + + // required movement direction + float flMoveYaw = UTIL_VecToYaw( move.dir ); + float idealYaw = UTIL_AngleMod( flMoveYaw ); + + if (GetEnemy()) + { + float flEDist = UTIL_DistApprox2D( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter() ); + + if (flEDist < 256.0) + { + float flEYaw = UTIL_VecToYaw( GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter() ); + + if (flEDist < 128.0) + { + idealYaw = flEYaw; + } + else + { + idealYaw = flMoveYaw + UTIL_AngleDiff( flEYaw, flMoveYaw ) * (2 - flEDist / 128.0); + } + + //DevMsg("was %.0f now %.0f\n", flMoveYaw, idealYaw ); + } + } + + GetMotor()->SetIdealYawAndUpdate( idealYaw ); + + // find movement direction to compensate for not being turned far enough + float fSequenceMoveYaw = GetSequenceMoveYaw( GetSequence() ); + float flDiff = UTIL_AngleDiff( flMoveYaw, GetLocalAngles().y + fSequenceMoveYaw ); + SetPoseParameter( m_poseMove_Yaw, GetPoseParameter( m_poseMove_Yaw ) + flDiff ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: For innate melee attack +// Input : +// Output : +//----------------------------------------------------------------------------- +int CNPC_BaseZombie::MeleeAttack1Conditions ( float flDot, float flDist ) +{ + float range = GetClawAttackRange(); + + if (flDist > range ) + { + // Translate a hit vehicle into its passenger if found + if ( GetEnemy() != NULL ) + { +#if defined(HL2_DLL) && !defined(HL2MP) + // If the player is holding an object, knock it down. + if( GetEnemy()->IsPlayer() ) + { + CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() ); + + Assert( pPlayer != NULL ); + + // Is the player carrying something? + CBaseEntity *pObject = GetPlayerHeldEntity(pPlayer); + + if( !pObject ) + { + pObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() ); + } + + if( pObject ) + { + float flDist = pObject->WorldSpaceCenter().DistTo( WorldSpaceCenter() ); + + if( flDist <= GetClawAttackRange() ) + return COND_CAN_MELEE_ATTACK1; + } + } +#endif + } + return COND_TOO_FAR_TO_ATTACK; + } + + if (flDot < 0.7) + { + return COND_NOT_FACING_ATTACK; + } + + // Build a cube-shaped hull, the same hull that ClawAttack() is going to use. + Vector vecMins = GetHullMins(); + Vector vecMaxs = GetHullMaxs(); + vecMins.z = vecMins.x; + vecMaxs.z = vecMaxs.x; + + Vector forward; + GetVectors( &forward, NULL, NULL ); + + trace_t tr; + CTraceFilterNav traceFilter( this, false, this, COLLISION_GROUP_NONE ); + AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * GetClawAttackRange(), vecMins, vecMaxs, MASK_NPCSOLID, &traceFilter, &tr ); + + if( tr.fraction == 1.0 || !tr.m_pEnt ) + { + +#ifdef HL2_EPISODIC + + // If our trace was unobstructed but we were shooting + if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE ) + return COND_CAN_MELEE_ATTACK1; + +#endif // HL2_EPISODIC + + // This attack would miss completely. Trick the zombie into moving around some more. + return COND_TOO_FAR_TO_ATTACK; + } + + if( tr.m_pEnt == GetEnemy() || + tr.m_pEnt->IsNPC() || + ( tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast<CBreakableProp*>(tr.m_pEnt) ) ) ) + { + // -Let the zombie swipe at his enemy if he's going to hit them. + // -Also let him swipe at NPC's that happen to be between the zombie and the enemy. + // This makes mobs of zombies seem more rowdy since it doesn't leave guys in the back row standing around. + // -Also let him swipe at things that takedamage, under the assumptions that they can be broken. + return COND_CAN_MELEE_ATTACK1; + } + + Vector vecTrace = tr.endpos - tr.startpos; + float lenTraceSq = vecTrace.Length2DSqr(); + + if ( GetEnemy() && GetEnemy()->MyCombatCharacterPointer() && tr.m_pEnt == static_cast<CBaseCombatCharacter *>(GetEnemy())->GetVehicleEntity() ) + { + if ( lenTraceSq < Square( GetClawAttackRange() * 0.75f ) ) + { + return COND_CAN_MELEE_ATTACK1; + } + } + + if( tr.m_pEnt->IsBSPModel() ) + { + // The trace hit something solid, but it's not the enemy. If this item is closer to the zombie than + // the enemy is, treat this as an obstruction. + Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter(); + + if( lenTraceSq < vecToEnemy.Length2DSqr() ) + { + return COND_ZOMBIE_LOCAL_MELEE_OBSTRUCTION; + } + } + +#ifdef HL2_EPISODIC + + if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt ) + { + //Try to swat whatever the player is standing on instead of acting like a dill. + return COND_CAN_MELEE_ATTACK1; + } + + // Bullseyes are given some grace on if they can be hit + if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE ) + return COND_CAN_MELEE_ATTACK1; + +#endif // HL2_EPISODIC + + // Move around some more + return COND_TOO_FAR_TO_ATTACK; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#define ZOMBIE_BUCKSHOT_TRIPLE_DAMAGE_DIST 96.0f // Triple damage from buckshot at 8 feet (headshot only) +float CNPC_BaseZombie::GetHitgroupDamageMultiplier( int iHitGroup, const CTakeDamageInfo &info ) +{ + switch( iHitGroup ) + { + case HITGROUP_HEAD: + { + if( info.GetDamageType() & DMG_BUCKSHOT ) + { + float flDist = FLT_MAX; + + if( info.GetAttacker() ) + { + flDist = ( GetAbsOrigin() - info.GetAttacker()->GetAbsOrigin() ).Length(); + } + + if( flDist <= ZOMBIE_BUCKSHOT_TRIPLE_DAMAGE_DIST ) + { + return 3.0f; + } + } + else + { + return 2.0f; + } + } + } + + return BaseClass::GetHitgroupDamageMultiplier( iHitGroup, info ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + CTakeDamageInfo infoCopy = info; + + // Keep track of headshots so we can determine whether to pop off our headcrab. + if (ptr->hitgroup == HITGROUP_HEAD) + { + m_bHeadShot = true; + } + + if( infoCopy.GetDamageType() & DMG_BUCKSHOT ) + { + // Zombie gets across-the-board damage reduction for buckshot. This compensates for the recent changes which + // make the shotgun much more powerful, and returns the zombies to a level that has been playtested extensively.(sjb) + // This normalizes the buckshot damage to what it used to be on normal (5 dmg per pellet. Now it's 8 dmg per pellet). + infoCopy.ScaleDamage( 0.625 ); + } + + BaseClass::TraceAttack( infoCopy, vecDir, ptr, pAccumulator ); +} + + +//----------------------------------------------------------------------------- +// Purpose: A zombie has taken damage. Determine whether he should split in half +// Input : +// Output : bool, true if yes. +//----------------------------------------------------------------------------- +bool CNPC_BaseZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold ) +{ + if ( info.GetDamageType() & DMG_REMOVENORAGDOLL ) + return false; + + if ( m_fIsTorso ) + { + // Already split. + return false; + } + + // Not if we're in a dss + if ( IsRunningDynamicInteraction() ) + return false; + + // Break in half IF: + // + // Take half or more of max health in DMG_BLAST + if( (info.GetDamageType() & DMG_BLAST) && flDamageThreshold >= 0.5 ) + { + return true; + } + + if ( hl2_episodic.GetBool() ) + { + // Always split after a cannon hit + if ( info.GetAmmoType() == GetAmmoDef()->Index("CombineHeavyCannon") ) + return true; + } + +#if 0 + if( info.GetDamageType() & DMG_BUCKSHOT ) + { + if( m_iHealth <= 0 || flDamageThreshold >= 0.5 ) + { + return true; + } + } +#endif + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: A zombie has taken damage. Determine whether he release his headcrab. +// Output : YES, IMMEDIATE, or SCHEDULED (see HeadcrabRelease_t) +//----------------------------------------------------------------------------- +HeadcrabRelease_t CNPC_BaseZombie::ShouldReleaseHeadcrab( const CTakeDamageInfo &info, float flDamageThreshold ) +{ + if ( m_iHealth <= 0 ) + { + if ( info.GetDamageType() & DMG_REMOVENORAGDOLL ) + return RELEASE_NO; + + if ( info.GetDamageType() & DMG_SNIPER ) + return RELEASE_RAGDOLL; + + // If I was killed by a bullet... + if ( info.GetDamageType() & DMG_BULLET ) + { + if( m_bHeadShot ) + { + if( flDamageThreshold > 0.25 ) + { + // Enough force to kill the crab. + return RELEASE_RAGDOLL; + } + } + else + { + // Killed by a shot to body or something. Crab is ok! + return RELEASE_IMMEDIATE; + } + } + + // If I was killed by an explosion, release the crab. + if ( info.GetDamageType() & DMG_BLAST ) + { + return RELEASE_RAGDOLL; + } + + if ( m_fIsTorso && IsChopped( info ) ) + { + return RELEASE_RAGDOLL_SLICED_OFF; + } + } + + return RELEASE_NO; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pInflictor - +// pAttacker - +// flDamage - +// bitsDamageType - +// Output : int +//----------------------------------------------------------------------------- +#define ZOMBIE_SCORCH_RATE 8 +#define ZOMBIE_MIN_RENDERCOLOR 50 +int CNPC_BaseZombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) +{ + CTakeDamageInfo info = inputInfo; + + if( inputInfo.GetDamageType() & DMG_BURN ) + { + // If a zombie is on fire it only takes damage from the fire that's attached to it. (DMG_DIRECT) + // This is to stop zombies from burning to death 10x faster when they're standing around + // 10 fire entities. + if( IsOnFire() && !(inputInfo.GetDamageType() & DMG_DIRECT) ) + { + return 0; + } + + Scorch( ZOMBIE_SCORCH_RATE, ZOMBIE_MIN_RENDERCOLOR ); + } + + // Take some percentage of damage from bullets (unless hit in the crab). Always take full buckshot & sniper damage + if ( !m_bHeadShot && (info.GetDamageType() & DMG_BULLET) && !(info.GetDamageType() & (DMG_BUCKSHOT|DMG_SNIPER)) ) + { + info.ScaleDamage( ZOMBIE_BULLET_DAMAGE_SCALE ); + } + + if ( ShouldIgnite( info ) ) + { + Ignite( 100.0f ); + } + + int tookDamage = BaseClass::OnTakeDamage_Alive( info ); + + // flDamageThreshold is what percentage of the creature's max health + // this amount of damage represents. (clips at 1.0) + float flDamageThreshold = MIN( 1, info.GetDamage() / m_iMaxHealth ); + + // Being chopped up by a sharp physics object is a pretty special case + // so we handle it with some special code. Mainly for + // Ravenholm's helicopter traps right now (sjb). + bool bChopped = IsChopped(info); + bool bSquashed = IsSquashed(info); + bool bKilledByVehicle = ( ( info.GetDamageType() & DMG_VEHICLE ) != 0 ); + + if( !m_fIsTorso && (bChopped || bSquashed) && !bKilledByVehicle && !(info.GetDamageType() & DMG_REMOVENORAGDOLL) ) + { + if( bChopped ) + { + EmitSound( "E3_Phystown.Slicer" ); + } + + DieChopped( info ); + } + else + { + HeadcrabRelease_t release = ShouldReleaseHeadcrab( info, flDamageThreshold ); + + switch( release ) + { + case RELEASE_IMMEDIATE: + ReleaseHeadcrab( EyePosition(), vec3_origin, true, true ); + break; + + case RELEASE_RAGDOLL: + // Go a little easy on headcrab ragdoll force. They're light! + ReleaseHeadcrab( EyePosition(), inputInfo.GetDamageForce() * 0.25, true, false, true ); + break; + + case RELEASE_RAGDOLL_SLICED_OFF: + { + EmitSound( "E3_Phystown.Slicer" ); + Vector vecForce = inputInfo.GetDamageForce() * 0.1; + vecForce += Vector( 0, 0, 2000.0 ); + ReleaseHeadcrab( EyePosition(), vecForce, true, false, true ); + } + break; + + case RELEASE_VAPORIZE: + RemoveHead(); + break; + + case RELEASE_SCHEDULED: + SetCondition( COND_ZOMBIE_RELEASECRAB ); + break; + } + + if( ShouldBecomeTorso( info, flDamageThreshold ) ) + { + bool bHitByCombineCannon = (inputInfo.GetAmmoType() == GetAmmoDef()->Index("CombineHeavyCannon")); + + if ( CanBecomeLiveTorso() ) + { + BecomeTorso( vec3_origin, inputInfo.GetDamageForce() * 0.50 ); + + if ( ( info.GetDamageType() & DMG_BLAST) && random->RandomInt( 0, 1 ) == 0 ) + { + Ignite( 5.0 + random->RandomFloat( 0.0, 5.0 ) ); + } + + // For Combine cannon impacts + if ( hl2_episodic.GetBool() ) + { + if ( bHitByCombineCannon ) + { + // Catch on fire. + Ignite( 5.0f + random->RandomFloat( 0.0f, 5.0f ) ); + } + } + + if (flDamageThreshold >= 1.0) + { + m_iHealth = 0; + BecomeRagdollOnClient( info.GetDamageForce() ); + } + } + else if ( random->RandomInt(1, 3) == 1 ) + DieChopped( info ); + } + } + + if( tookDamage > 0 && (info.GetDamageType() & (DMG_BURN|DMG_DIRECT)) && m_ActBusyBehavior.IsActive() ) + { + //!!!HACKHACK- Stuff a light_damage condition if an actbusying zombie takes direct burn damage. This will cause an + // ignited zombie to 'wake up' and rise out of its actbusy slump. (sjb) + SetCondition( COND_LIGHT_DAMAGE ); + } + + // IMPORTANT: always clear the headshot flag after applying damage. No early outs! + m_bHeadShot = false; + + return tookDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: make a sound Alyx can hear when in darkness mode +// Input : volume (radius) of the sound. +// Output : +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::MakeAISpookySound( float volume, float duration ) +{ +#ifdef HL2_EPISODIC + if ( HL2GameRules()->IsAlyxInDarknessMode() ) + { + CSoundEnt::InsertSound( SOUND_COMBAT, EyePosition(), volume, duration, this, SOUNDENT_CHANNEL_SPOOKY_NOISE ); + } +#endif // HL2_EPISODIC +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_BaseZombie::CanPlayMoanSound() +{ + if( HasSpawnFlags( SF_NPC_GAG ) ) + return false; + + // Burning zombies play their moan loop at full volume for as long as they're + // burning. Don't let a moan envelope play cause it will turn the volume down when done. + if( IsOnFire() ) + return false; + + // Members of a small group of zombies can vocalize whenever they want + if( s_iAngryZombies <= 4 ) + return true; + + // This serves to limit the number of zombies that can moan at one time when there are a lot. + if( random->RandomInt( 1, zombie_moanfreq.GetInt() * (s_iAngryZombies/2) ) == 1 ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Open a window and let a little bit of the looping moan sound +// come through. +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize ) +{ + if( HasSpawnFlags( SF_NPC_GAG ) ) + { + // Not yet! + return; + } + + if( !m_pMoanSound ) + { + // Don't set this up until the code calls for it. + const char *pszSound = GetMoanSound( m_iMoanSound ); + m_flMoanPitch = random->RandomInt( zombie_basemin.GetInt(), zombie_basemax.GetInt() ); + + //m_pMoanSound = ENVELOPE_CONTROLLER.SoundCreate( entindex(), CHAN_STATIC, pszSound, ATTN_NORM ); + CPASAttenuationFilter filter( this ); + m_pMoanSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_STATIC, pszSound, ATTN_NORM ); + + ENVELOPE_CONTROLLER.Play( m_pMoanSound, 1.0, m_flMoanPitch ); + } + + //HACKHACK get these from chia chin's console vars. + envDefaultZombieMoanVolumeFast[ 1 ].durationMin = zombie_decaymin.GetFloat(); + envDefaultZombieMoanVolumeFast[ 1 ].durationMax = zombie_decaymax.GetFloat(); + + if( random->RandomInt( 1, 2 ) == 1 ) + { + IdleSound(); + } + + float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pMoanSound, SOUNDCTRL_CHANGE_VOLUME, pEnvelope, iEnvelopeSize ); + + float flPitch = random->RandomInt( m_flMoanPitch + zombie_changemin.GetInt(), m_flMoanPitch + zombie_changemax.GetInt() ); + ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, flPitch, 0.3 ); + + m_flNextMoanSound = gpGlobals->curtime + duration + 9999; +} + +//----------------------------------------------------------------------------- +// Purpose: Determine whether the zombie is chopped up by some physics item +//----------------------------------------------------------------------------- +bool CNPC_BaseZombie::IsChopped( const CTakeDamageInfo &info ) +{ + float flDamageThreshold = MIN( 1, info.GetDamage() / m_iMaxHealth ); + + if ( m_iHealth > 0 || flDamageThreshold <= 0.5 ) + return false; + + if ( !( info.GetDamageType() & DMG_SLASH) ) + return false; + + if ( !( info.GetDamageType() & DMG_CRUSH) ) + return false; + + if ( info.GetDamageType() & DMG_REMOVENORAGDOLL ) + return false; + + // If you take crush and slash damage, you're hit by a sharp physics item. + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Return true if this gibbing zombie should ignite its gibs +//----------------------------------------------------------------------------- +bool CNPC_BaseZombie::ShouldIgniteZombieGib( void ) +{ +#ifdef HL2_EPISODIC + // If we're in darkness mode, don't ignite giblets, because we don't want to + // pay the perf cost of multiple dynamic lights per giblet. + return ( IsOnFire() && !HL2GameRules()->IsAlyxInDarknessMode() ); +#else + return IsOnFire(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Handle the special case of a zombie killed by a physics chopper. +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::DieChopped( const CTakeDamageInfo &info ) +{ + bool bSquashed = IsSquashed(info); + + Vector forceVector( vec3_origin ); + + forceVector += CalcDamageForceVector( info ); + + if( !m_fIsHeadless && !bSquashed ) + { + if( random->RandomInt( 0, 1 ) == 0 ) + { + // Drop a live crab half of the time. + ReleaseHeadcrab( EyePosition(), forceVector * 0.005, true, false, false ); + } + } + + float flFadeTime = 0.0; + + if( HasSpawnFlags( SF_NPC_FADE_CORPSE ) ) + { + flFadeTime = 5.0; + } + + SetSolid( SOLID_NONE ); + AddEffects( EF_NODRAW ); + + Vector vecLegsForce; + vecLegsForce.x = random->RandomFloat( -400, 400 ); + vecLegsForce.y = random->RandomFloat( -400, 400 ); + vecLegsForce.z = random->RandomFloat( 0, 250 ); + + if( bSquashed && vecLegsForce.z > 0 ) + { + // Force the broken legs down. (Give some additional force, too) + vecLegsForce.z *= -10; + } + + CBaseEntity *pLegGib = CreateRagGib( GetLegsModel(), GetAbsOrigin(), GetAbsAngles(), vecLegsForce, flFadeTime, ShouldIgniteZombieGib() ); + if ( pLegGib ) + { + CopyRenderColorTo( pLegGib ); + } + + forceVector *= random->RandomFloat( 0.04, 0.06 ); + forceVector.z = ( 100 * 12 * 5 ) * random->RandomFloat( 0.8, 1.2 ); + + if( bSquashed && forceVector.z > 0 ) + { + // Force the broken torso down. + forceVector.z *= -1.0; + } + + // Why do I have to fix this up?! (sjb) + QAngle TorsoAngles; + TorsoAngles = GetAbsAngles(); + TorsoAngles.x -= 90.0f; + CBaseEntity *pTorsoGib = CreateRagGib( GetTorsoModel(), GetAbsOrigin() + Vector( 0, 0, 64 ), TorsoAngles, forceVector, flFadeTime, ShouldIgniteZombieGib() ); + if ( pTorsoGib ) + { + CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating*>(pTorsoGib); + if( pAnimating ) + { + pAnimating->SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless ); + } + + pTorsoGib->SetOwnerEntity( this ); + CopyRenderColorTo( pTorsoGib ); + + } + + if ( UTIL_ShouldShowBlood( BLOOD_COLOR_YELLOW ) ) + { + int i; + Vector vecSpot; + Vector vecDir; + + for ( i = 0 ; i < 4; i++ ) + { + vecSpot = WorldSpaceCenter(); + + vecSpot.x += random->RandomFloat( -12, 12 ); + vecSpot.y += random->RandomFloat( -12, 12 ); + vecSpot.z += random->RandomFloat( -4, 16 ); + + UTIL_BloodDrips( vecSpot, vec3_origin, BLOOD_COLOR_YELLOW, 50 ); + } + + for ( int i = 0 ; i < 4 ; i++ ) + { + Vector vecSpot = WorldSpaceCenter(); + + vecSpot.x += random->RandomFloat( -12, 12 ); + vecSpot.y += random->RandomFloat( -12, 12 ); + vecSpot.z += random->RandomFloat( -4, 16 ); + + vecDir.x = random->RandomFloat(-1, 1); + vecDir.y = random->RandomFloat(-1, 1); + vecDir.z = 0; + VectorNormalize( vecDir ); + + UTIL_BloodImpact( vecSpot, vecDir, BloodColor(), 1 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: damage has been done. Should the zombie ignite? +//----------------------------------------------------------------------------- +bool CNPC_BaseZombie::ShouldIgnite( const CTakeDamageInfo &info ) +{ + if ( IsOnFire() ) + { + // Already burning! + return false; + } + + if ( info.GetDamageType() & DMG_BURN ) + { + // + // If we take more than ten percent of our health in burn damage within a five + // second interval, we should catch on fire. + // + m_flBurnDamage += info.GetDamage(); + m_flBurnDamageResetTime = gpGlobals->curtime + 5; + + if ( m_flBurnDamage >= m_iMaxHealth * 0.1 ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Sufficient fire damage has been done. Zombie ignites! +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner ) +{ + BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner ); + +#ifdef HL2_EPISODIC + if ( HL2GameRules()->IsAlyxInDarknessMode() == true && GetEffectEntity() != NULL ) + { + GetEffectEntity()->AddEffects( EF_DIMLIGHT ); + } +#endif // HL2_EPISODIC + + // Set the zombie up to burn to death in about ten seconds. + SetHealth( MIN( m_iHealth, FLAME_DIRECT_DAMAGE_PER_SEC * (ZOMBIE_BURN_TIME + random->RandomFloat( -ZOMBIE_BURN_TIME_NOISE, ZOMBIE_BURN_TIME_NOISE)) ) ); + + // FIXME: use overlays when they come online + //AddOverlay( ACT_ZOM_WALK_ON_FIRE, false ); + if( !m_ActBusyBehavior.IsActive() ) + { + Activity activity = GetActivity(); + Activity burningActivity = activity; + + if ( activity == ACT_WALK ) + { + burningActivity = ACT_WALK_ON_FIRE; + } + else if ( activity == ACT_RUN ) + { + burningActivity = ACT_RUN_ON_FIRE; + } + else if ( activity == ACT_IDLE ) + { + burningActivity = ACT_IDLE_ON_FIRE; + } + + if( HaveSequenceForActivity(burningActivity) ) + { + // Make sure we have a sequence for this activity (torsos don't have any, for instance) + // to prevent the baseNPC & baseAnimating code from throwing red level errors. + SetActivity( burningActivity ); + } + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::CopyRenderColorTo( CBaseEntity *pOther ) +{ + color32 color = GetRenderColor(); + pOther->SetRenderColor( color.r, color.g, color.b, color.a ); +} + +//----------------------------------------------------------------------------- +// Purpose: Look in front and see if the claw hit anything. +// +// Input : flDist distance to trace +// iDamage damage to do if attack hits +// vecViewPunch camera punch (if attack hits player) +// vecVelocityPunch velocity punch (if attack hits player) +// +// Output : The entity hit by claws. NULL if nothing. +//----------------------------------------------------------------------------- +CBaseEntity *CNPC_BaseZombie::ClawAttack( float flDist, int iDamage, QAngle &qaViewPunch, Vector &vecVelocityPunch, int BloodOrigin ) +{ + // Added test because claw attack anim sometimes used when for cases other than melee + int iDriverInitialHealth = -1; + CBaseEntity *pDriver = NULL; + if ( GetEnemy() ) + { + trace_t tr; + AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 1.0f ) + return NULL; + + // CheckTraceHullAttack() can damage player in vehicle as side effect of melee attack damaging physics objects, which the car forwards to the player + // need to detect this to get correct damage effects + CBaseCombatCharacter *pCCEnemy = ( GetEnemy() != NULL ) ? GetEnemy()->MyCombatCharacterPointer() : NULL; + CBaseEntity *pVehicleEntity; + if ( pCCEnemy != NULL && ( pVehicleEntity = pCCEnemy->GetVehicleEntity() ) != NULL ) + { + if ( pVehicleEntity->GetServerVehicle() && dynamic_cast<CPropVehicleDriveable *>(pVehicleEntity) ) + { + pDriver = static_cast<CPropVehicleDriveable *>(pVehicleEntity)->GetDriver(); + if ( pDriver && pDriver->IsPlayer() ) + { + iDriverInitialHealth = pDriver->GetHealth(); + } + else + { + pDriver = NULL; + } + } + } + } + + // + // Trace out a cubic section of our hull and see what we hit. + // + Vector vecMins = GetHullMins(); + Vector vecMaxs = GetHullMaxs(); + vecMins.z = vecMins.x; + vecMaxs.z = vecMaxs.x; + + CBaseEntity *pHurt = NULL; + if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE ) + { + // We always hit bullseyes we're targeting + pHurt = GetEnemy(); + CTakeDamageInfo info( this, this, vec3_origin, GetAbsOrigin(), iDamage, DMG_SLASH ); + pHurt->TakeDamage( info ); + } + else + { + // Try to hit them with a trace + pHurt = CheckTraceHullAttack( flDist, vecMins, vecMaxs, iDamage, DMG_SLASH ); + } + + if ( pDriver && iDriverInitialHealth != pDriver->GetHealth() ) + { + pHurt = pDriver; + } + + if ( !pHurt && m_hPhysicsEnt != NULL && IsCurSchedule(SCHED_ZOMBIE_ATTACKITEM) ) + { + pHurt = m_hPhysicsEnt; + + Vector vForce = pHurt->WorldSpaceCenter() - WorldSpaceCenter(); + VectorNormalize( vForce ); + + vForce *= 5 * 24; + + CTakeDamageInfo info( this, this, vForce, GetAbsOrigin(), iDamage, DMG_SLASH ); + pHurt->TakeDamage( info ); + + pHurt = m_hPhysicsEnt; + } + + if ( pHurt ) + { + AttackHitSound(); + + CBasePlayer *pPlayer = ToBasePlayer( pHurt ); + + if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) ) + { + pPlayer->ViewPunch( qaViewPunch ); + + pPlayer->VelocityPunch( vecVelocityPunch ); + } + else if( !pPlayer && UTIL_ShouldShowBlood(pHurt->BloodColor()) ) + { + // Hit an NPC. Bleed them! + Vector vecBloodPos; + + switch( BloodOrigin ) + { + case ZOMBIE_BLOOD_LEFT_HAND: + if( GetAttachment( "blood_left", vecBloodPos ) ) + SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) ); + break; + + case ZOMBIE_BLOOD_RIGHT_HAND: + if( GetAttachment( "blood_right", vecBloodPos ) ) + SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) ); + break; + + case ZOMBIE_BLOOD_BOTH_HANDS: + if( GetAttachment( "blood_left", vecBloodPos ) ) + SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) ); + + if( GetAttachment( "blood_right", vecBloodPos ) ) + SpawnBlood( vecBloodPos, g_vecAttackDir, pHurt->BloodColor(), MIN( iDamage, 30 ) ); + break; + + case ZOMBIE_BLOOD_BITE: + // No blood for these. + break; + } + } + } + else + { + AttackMissSound(); + } + + if ( pHurt == m_hPhysicsEnt && IsCurSchedule(SCHED_ZOMBIE_ATTACKITEM) ) + { + m_hPhysicsEnt = NULL; + m_flNextSwat = gpGlobals->curtime + random->RandomFloat( 2, 4 ); + } + + return pHurt; +} + +//----------------------------------------------------------------------------- +// Purpose: The zombie is frustrated and pounding walls/doors. Make an appropriate noise +// Input : +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::PoundSound() +{ + trace_t tr; + Vector forward; + + GetVectors( &forward, NULL, NULL ); + + AI_TraceLine( EyePosition(), EyePosition() + forward * 128, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + + if( tr.fraction == 1.0 ) + { + // Didn't hit anything! + return; + } + + if( tr.fraction < 1.0 && tr.m_pEnt ) + { + const surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps ); + if( psurf ) + { + EmitSound( physprops->GetString(psurf->sounds.impactHard) ); + return; + } + } + + // Otherwise fall through to the default sound. + CPASAttenuationFilter filter( this,"NPC_BaseZombie.PoundDoor" ); + EmitSound( filter, entindex(),"NPC_BaseZombie.PoundDoor" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Catches the monster-specific events that occur when tagged animation +// frames are played. +// Input : pEvent - +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent ) +{ + if ( pEvent->event == AE_NPC_ATTACK_BROADCAST ) + { + if( GetEnemy() && GetEnemy()->IsNPC() ) + { + if( HasCondition(COND_CAN_MELEE_ATTACK1) ) + { + // This animation is sometimes played by code that doesn't intend to attack the enemy + // (For instance, code that makes a zombie take a frustrated swipe at an obstacle). + // Try not to trigger a reaction from our enemy unless we're really attacking. + GetEnemy()->MyNPCPointer()->DispatchInteraction( g_interactionZombieMeleeWarning, NULL, this ); + } + } + return; + } + + if ( pEvent->event == AE_ZOMBIE_POUND ) + { + PoundSound(); + return; + } + + if ( pEvent->event == AE_ZOMBIE_ALERTSOUND ) + { + AlertSound(); + return; + } + + if ( pEvent->event == AE_ZOMBIE_STEP_LEFT ) + { + MakeAIFootstepSound( 180.0f ); + FootstepSound( false ); + return; + } + + if ( pEvent->event == AE_ZOMBIE_STEP_RIGHT ) + { + MakeAIFootstepSound( 180.0f ); + FootstepSound( true ); + return; + } + + if ( pEvent->event == AE_ZOMBIE_GET_UP ) + { + MakeAIFootstepSound( 180.0f, 3.0f ); + if( !IsOnFire() ) + { + // If you let this code run while a zombie is burning, it will stop wailing. + m_flNextMoanSound = gpGlobals->curtime; + MoanSound( envDefaultZombieMoanVolumeFast, ARRAYSIZE( envDefaultZombieMoanVolumeFast ) ); + } + return; + } + + if ( pEvent->event == AE_ZOMBIE_SCUFF_LEFT ) + { + MakeAIFootstepSound( 180.0f ); + FootscuffSound( false ); + return; + } + + if ( pEvent->event == AE_ZOMBIE_SCUFF_RIGHT ) + { + MakeAIFootstepSound( 180.0f ); + FootscuffSound( true ); + return; + } + + // all swat animations are handled as a single case. + if ( pEvent->event == AE_ZOMBIE_STARTSWAT ) + { + MakeAIFootstepSound( 180.0f ); + AttackSound(); + return; + } + + if ( pEvent->event == AE_ZOMBIE_ATTACK_SCREAM ) + { + AttackSound(); + return; + } + + if ( pEvent->event == AE_ZOMBIE_SWATITEM ) + { + CBaseEntity *pEnemy = GetEnemy(); + if ( pEnemy ) + { + Vector v; + CBaseEntity *pPhysicsEntity = m_hPhysicsEnt; + if( !pPhysicsEntity ) + { + DevMsg( "**Zombie: Missing my physics ent!!" ); + return; + } + + IPhysicsObject *pPhysObj = pPhysicsEntity->VPhysicsGetObject(); + + if( !pPhysObj ) + { + DevMsg( "**Zombie: No Physics Object for physics Ent!" ); + return; + } + + EmitSound( "NPC_BaseZombie.Swat" ); + PhysicsImpactSound( pEnemy, pPhysObj, CHAN_BODY, pPhysObj->GetMaterialIndex(), physprops->GetSurfaceIndex("flesh"), 0.5, 800 ); + + Vector physicsCenter = pPhysicsEntity->WorldSpaceCenter(); + v = pEnemy->WorldSpaceCenter() - physicsCenter; + VectorNormalize(v); + + // Send the object at 800 in/sec toward the enemy. Add 200 in/sec up velocity to keep it + // in the air for a second or so. + v = v * 800; + v.z += 200; + + // add some spin so the object doesn't appear to just fly in a straight line + // Also this spin will move the object slightly as it will press on whatever the object + // is resting on. + AngularImpulse angVelocity( random->RandomFloat(-180, 180), 20, random->RandomFloat(-360, 360) ); + + pPhysObj->AddVelocity( &v, &angVelocity ); + + // If we don't put the object scan time well into the future, the zombie + // will re-select the object he just hit as it is flying away from him. + // It will likely always be the nearest object because the zombie moved + // close enough to it to hit it. + m_hPhysicsEnt = NULL; + + m_flNextSwatScan = gpGlobals->curtime + ZOMBIE_SWAT_DELAY; + + return; + } + } + + if ( pEvent->event == AE_ZOMBIE_ATTACK_RIGHT ) + { + Vector right, forward; + AngleVectors( GetLocalAngles(), &forward, &right, NULL ); + + right = right * 100; + forward = forward * 200; + + QAngle qa( -15, -20, -10 ); + Vector vec = right + forward; + ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), qa, vec, ZOMBIE_BLOOD_RIGHT_HAND ); + return; + } + + if ( pEvent->event == AE_ZOMBIE_ATTACK_LEFT ) + { + Vector right, forward; + AngleVectors( GetLocalAngles(), &forward, &right, NULL ); + + right = right * -100; + forward = forward * 200; + + QAngle qa( -15, 20, -10 ); + Vector vec = right + forward; + ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), qa, vec, ZOMBIE_BLOOD_LEFT_HAND ); + return; + } + + if ( pEvent->event == AE_ZOMBIE_ATTACK_BOTH ) + { + Vector forward; + QAngle qaPunch( 45, random->RandomInt(-5,5), random->RandomInt(-5,5) ); + AngleVectors( GetLocalAngles(), &forward ); + forward = forward * 200; + ClawAttack( GetClawAttackRange(), sk_zombie_dmg_one_slash.GetFloat(), qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS ); + return; + } + + if ( pEvent->event == AE_ZOMBIE_POPHEADCRAB ) + { + if ( GetInteractionPartner() == NULL ) + return; + + const char *pString = pEvent->options; + char token[128]; + pString = nexttoken( token, pString, ' ' ); + + int boneIndex = GetInteractionPartner()->LookupBone( token ); + + if ( boneIndex == -1 ) + { + Warning( "AE_ZOMBIE_POPHEADCRAB event using invalid bone name! Usage: event AE_ZOMBIE_POPHEADCRAB \"<BoneName> <Speed>\" \n" ); + return; + } + + pString = nexttoken( token, pString, ' ' ); + + if ( !token ) + { + Warning( "AE_ZOMBIE_POPHEADCRAB event format missing velocity parameter! Usage: event AE_ZOMBIE_POPHEADCRAB \"<BoneName> <Speed>\" \n" ); + return; + } + + Vector vecBonePosition; + QAngle angles; + Vector vecHeadCrabPosition; + + int iCrabAttachment = LookupAttachment( "headcrab" ); + int iSpeed = atoi( token ); + + GetInteractionPartner()->GetBonePosition( boneIndex, vecBonePosition, angles ); + GetAttachment( iCrabAttachment, vecHeadCrabPosition ); + + Vector vVelocity = vecHeadCrabPosition - vecBonePosition; + VectorNormalize( vVelocity ); + + CTakeDamageInfo dmgInfo( this, GetInteractionPartner(), m_iHealth, DMG_DIRECT ); + + dmgInfo.SetDamagePosition( vecHeadCrabPosition ); + + ReleaseHeadcrab( EyePosition(), vVelocity * iSpeed, true, false, true ); + + GuessDamageForce( &dmgInfo, vVelocity, vecHeadCrabPosition, 0.5f ); + TakeDamage( dmgInfo ); + return; + } + + BaseClass::HandleAnimEvent( pEvent ); +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn function for the base zombie. +// +// !!!IMPORTANT!!! YOUR DERIVED CLASS'S SPAWN() RESPONSIBILITIES: +// +// Call Precache(); +// Set status for m_fIsTorso & m_fIsHeadless +// Set blood color +// Set health +// Set field of view +// Call CapabilitiesClear() & then set relevant capabilities +// THEN Call BaseClass::Spawn() +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::Spawn( void ) +{ + SetSolid( SOLID_BBOX ); + SetMoveType( MOVETYPE_STEP ); + +#ifdef _XBOX + // Always fade the corpse + AddSpawnFlags( SF_NPC_FADE_CORPSE ); +#endif // _XBOX + + m_NPCState = NPC_STATE_NONE; + + CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 ); + CapabilitiesAdd( bits_CAP_SQUAD ); + + m_flNextSwat = gpGlobals->curtime; + m_flNextSwatScan = gpGlobals->curtime; + m_pMoanSound = NULL; + + m_flNextMoanSound = gpGlobals->curtime + 9999; + + SetZombieModel(); + + NPCInit(); + + m_bIsSlumped = false; + + // Zombies get to cheat for 6 seconds (sjb) + GetEnemies()->SetFreeKnowledgeDuration( 6.0 ); + + m_ActBusyBehavior.SetUseRenderBounds(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: Pecaches all resources this NPC needs. +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::Precache( void ) +{ + UTIL_PrecacheOther( GetHeadcrabClassname() ); + + PrecacheScriptSound( "E3_Phystown.Slicer" ); + PrecacheScriptSound( "NPC_BaseZombie.PoundDoor" ); + PrecacheScriptSound( "NPC_BaseZombie.Swat" ); + + PrecacheModel( GetLegsModel() ); + PrecacheModel( GetTorsoModel() ); + + PrecacheParticleSystem( "blood_impact_zombie_01" ); + + BaseClass::Precache(); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::StartTouch( CBaseEntity *pOther ) +{ + BaseClass::StartTouch( pOther ); + + if( IsSlumped() && hl2_episodic.GetBool() ) + { + if( FClassnameIs( pOther, "prop_physics" ) ) + { + // Get up! + m_ActBusyBehavior.StopBusying(); + } + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CNPC_BaseZombie::CreateBehaviors() +{ + AddBehavior( &m_ActBusyBehavior ); + + return BaseClass::CreateBehaviors(); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CNPC_BaseZombie::TranslateSchedule( int scheduleType ) +{ + switch( scheduleType ) + { + case SCHED_CHASE_ENEMY: + if ( HasCondition( COND_ZOMBIE_LOCAL_MELEE_OBSTRUCTION ) && !HasCondition(COND_TASK_FAILED) && IsCurSchedule( SCHED_ZOMBIE_CHASE_ENEMY, false ) ) + { + return SCHED_COMBAT_PATROL; + } + return SCHED_ZOMBIE_CHASE_ENEMY; + break; + + case SCHED_ZOMBIE_SWATITEM: + // If the object is far away, move and swat it. If it's close, just swat it. + if( DistToPhysicsEnt() > ZOMBIE_PHYSOBJ_SWATDIST ) + { + return SCHED_ZOMBIE_MOVE_SWATITEM; + } + else + { + return SCHED_ZOMBIE_SWATITEM; + } + break; + + case SCHED_STANDOFF: + return SCHED_ZOMBIE_WANDER_STANDOFF; + + case SCHED_MELEE_ATTACK1: + return SCHED_ZOMBIE_MELEE_ATTACK1; + } + + return BaseClass::TranslateSchedule( scheduleType ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Allows for modification of the interrupt mask for the current schedule. +// In the most cases the base implementation should be called first. +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::BuildScheduleTestBits( void ) +{ + // Ignore damage if we were recently damaged or we're attacking. + if ( GetActivity() == ACT_MELEE_ATTACK1 ) + { + ClearCustomInterruptCondition( COND_LIGHT_DAMAGE ); + ClearCustomInterruptCondition( COND_HEAVY_DAMAGE ); + } +#ifndef HL2_EPISODIC + else if ( m_flNextFlinch >= gpGlobals->curtime ) + { + ClearCustomInterruptCondition( COND_LIGHT_DAMAGE ); + ClearCustomInterruptCondition( COND_HEAVY_DAMAGE ); + } +#endif // !HL2_EPISODIC + + // Everything should be interrupted if we get killed. + SetCustomInterruptCondition( COND_ZOMBIE_RELEASECRAB ); + + BaseClass::BuildScheduleTestBits(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when we change schedules. +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::OnScheduleChange( void ) +{ + // + // If we took damage and changed schedules, ignore further damage for a few seconds. + // + if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE )) + { + m_flNextFlinch = gpGlobals->curtime + ZOMBIE_FLINCH_DELAY; + } + + BaseClass::OnScheduleChange(); +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CNPC_BaseZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) +{ + if( failedSchedule == SCHED_ZOMBIE_WANDER_MEDIUM ) + { + return SCHED_ZOMBIE_WANDER_FAIL; + } + + // If we can swat physics objects, see if we can swat our obstructor + if ( CanSwatPhysicsObjects() ) + { + if ( !m_fIsTorso && IsPathTaskFailure( taskFailCode ) && + m_hObstructor != NULL && m_hObstructor->VPhysicsGetObject() && + m_hObstructor->VPhysicsGetObject()->GetMass() < 100 ) + { + m_hPhysicsEnt = m_hObstructor; + m_hObstructor = NULL; + return SCHED_ZOMBIE_ATTACKITEM; + } + } + + m_hObstructor = NULL; + + return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode ); +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CNPC_BaseZombie::SelectSchedule ( void ) +{ + if ( HasCondition( COND_ZOMBIE_RELEASECRAB ) ) + { + // Death waits for no man. Or zombie. Or something. + return SCHED_ZOMBIE_RELEASECRAB; + } + + if ( BehaviorSelectSchedule() ) + { + return BaseClass::SelectSchedule(); + } + + switch ( m_NPCState ) + { + case NPC_STATE_COMBAT: + if ( HasCondition( COND_NEW_ENEMY ) && GetEnemy() ) + { + float flDist; + + flDist = ( GetLocalOrigin() - GetEnemy()->GetLocalOrigin() ).Length(); + + // If this is a new enemy that's far away, ambush!! + if (flDist >= zombie_ambushdist.GetFloat() && MustCloseToAttack() ) + { + return SCHED_ZOMBIE_MOVE_TO_AMBUSH; + } + } + + if ( HasCondition( COND_LOST_ENEMY ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) ) + { + return SCHED_ZOMBIE_WANDER_MEDIUM; + } + + if( HasCondition( COND_ZOMBIE_CAN_SWAT_ATTACK ) ) + { + return SCHED_ZOMBIE_SWATITEM; + } + break; + + case NPC_STATE_ALERT: + if ( HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) ) + { + ClearCondition( COND_LOST_ENEMY ); + ClearCondition( COND_ENEMY_UNREACHABLE ); + +#ifdef DEBUG_ZOMBIES + DevMsg("Wandering\n"); +#endif + + // Just lost track of our enemy. + // Wander around a bit so we don't look like a dingus. + return SCHED_ZOMBIE_WANDER_MEDIUM; + } + break; + } + + return BaseClass::SelectSchedule(); +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CNPC_BaseZombie::IsSlumped( void ) +{ + if( hl2_episodic.GetBool() ) + { + if( m_ActBusyBehavior.IsInsideActBusy() && !m_ActBusyBehavior.IsStopBusying() ) + { + return true; + } + } + else + { + int sequence = GetSequence(); + if ( sequence != -1 ) + { + return ( strncmp( GetSequenceName( sequence ), "slump", 5 ) == 0 ); + } + } + + return false; +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CNPC_BaseZombie::IsGettingUp( void ) +{ + if( m_ActBusyBehavior.IsActive() && m_ActBusyBehavior.IsStopBusying() ) + { + return true; + } + return false; +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CNPC_BaseZombie::GetSwatActivity( void ) +{ + // Hafta figure out whether to swat with left or right arm. + // Also hafta figure out whether to swat high or low. (later) + float flDot; + Vector vecRight, vecDirToObj; + + AngleVectors( GetLocalAngles(), NULL, &vecRight, NULL ); + + vecDirToObj = m_hPhysicsEnt->GetLocalOrigin() - GetLocalOrigin(); + VectorNormalize(vecDirToObj); + + // compare in 2D. + vecRight.z = 0.0; + vecDirToObj.z = 0.0; + + flDot = DotProduct( vecRight, vecDirToObj ); + + Vector vecMyCenter; + Vector vecObjCenter; + + vecMyCenter = WorldSpaceCenter(); + vecObjCenter = m_hPhysicsEnt->WorldSpaceCenter(); + float flZDiff; + + flZDiff = vecMyCenter.z - vecObjCenter.z; + + if( flDot >= 0 ) + { + // Right + if( flZDiff < 0 ) + { + return ACT_ZOM_SWATRIGHTMID; + } + + return ACT_ZOM_SWATRIGHTLOW; + } + else + { + // Left + if( flZDiff < 0 ) + { + return ACT_ZOM_SWATLEFTMID; + } + + return ACT_ZOM_SWATLEFTLOW; + } +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::GatherConditions( void ) +{ + ClearCondition( COND_ZOMBIE_LOCAL_MELEE_OBSTRUCTION ); + + BaseClass::GatherConditions(); + + if( m_NPCState == NPC_STATE_COMBAT && !m_fIsTorso ) + { + // This check for !m_pPhysicsEnt prevents a crashing bug, but also + // eliminates the zombie picking a better physics object if one happens to fall + // between him and the object he's heading for already. + if( gpGlobals->curtime >= m_flNextSwatScan && (m_hPhysicsEnt == NULL) ) + { + FindNearestPhysicsObject( ZOMBIE_MAX_PHYSOBJ_MASS ); + m_flNextSwatScan = gpGlobals->curtime + 2.0; + } + } + + if( (m_hPhysicsEnt != NULL) && gpGlobals->curtime >= m_flNextSwat && HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_ZOMBIE_RELEASECRAB ) ) + { + SetCondition( COND_ZOMBIE_CAN_SWAT_ATTACK ); + } + else + { + ClearCondition( COND_ZOMBIE_CAN_SWAT_ATTACK ); + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::PrescheduleThink( void ) +{ + BaseClass::PrescheduleThink(); + +#if 0 + DevMsg(" ** %d Angry Zombies **\n", s_iAngryZombies ); +#endif + +#if 0 + if( m_NPCState == NPC_STATE_COMBAT ) + { + // Zombies should make idle sounds in combat + if( random->RandomInt( 0, 30 ) == 0 ) + { + IdleSound(); + } + } +#endif + + // + // Cool off if we aren't burned for five seconds or so. + // + if ( ( m_flBurnDamageResetTime ) && ( gpGlobals->curtime >= m_flBurnDamageResetTime ) ) + { + m_flBurnDamage = 0; + } +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::StartTask( const Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_ZOMBIE_DIE: + // Go to ragdoll + KillMe(); + TaskComplete(); + break; + + case TASK_ZOMBIE_GET_PATH_TO_PHYSOBJ: + { + Vector vecGoalPos; + Vector vecDir; + + vecDir = GetLocalOrigin() - m_hPhysicsEnt->GetLocalOrigin(); + VectorNormalize(vecDir); + vecDir.z = 0; + + AI_NavGoal_t goal( m_hPhysicsEnt->WorldSpaceCenter() ); + goal.pTarget = m_hPhysicsEnt; + GetNavigator()->SetGoal( goal ); + + TaskComplete(); + } + break; + + case TASK_ZOMBIE_SWAT_ITEM: + { + if( m_hPhysicsEnt == NULL ) + { + // Physics Object is gone! Probably was an explosive + // or something else broke it. + TaskFail("Physics ent NULL"); + } + else if ( DistToPhysicsEnt() > ZOMBIE_PHYSOBJ_SWATDIST ) + { + // Physics ent is no longer in range! Probably another zombie swatted it or it moved + // for some other reason. + TaskFail( "Physics swat item has moved" ); + } + else + { + SetIdealActivity( (Activity)GetSwatActivity() ); + } + break; + } + break; + + case TASK_ZOMBIE_DELAY_SWAT: + m_flNextSwat = gpGlobals->curtime + pTask->flTaskData; + TaskComplete(); + break; + + case TASK_ZOMBIE_RELEASE_HEADCRAB: + { + // make the crab look like it's pushing off the body + Vector vecForward; + Vector vecVelocity; + + AngleVectors( GetAbsAngles(), &vecForward ); + + vecVelocity = vecForward * 30; + vecVelocity.z += 100; + + ReleaseHeadcrab( EyePosition(), vecVelocity, true, true ); + TaskComplete(); + } + break; + + case TASK_ZOMBIE_WAIT_POST_MELEE: + { +#ifndef HL2_EPISODIC + TaskComplete(); + return; +#endif + + // Don't wait when attacking the player + if ( GetEnemy() && GetEnemy()->IsPlayer() ) + { + TaskComplete(); + return; + } + + // Wait a single think + SetWait( 0.1 ); + } + break; + + default: + BaseClass::StartTask( pTask ); + } +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::RunTask( const Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_ZOMBIE_SWAT_ITEM: + if( IsActivityFinished() ) + { + TaskComplete(); + } + break; + + case TASK_ZOMBIE_WAIT_POST_MELEE: + { + if ( IsWaitFinished() ) + { + TaskComplete(); + } + } + break; + default: + BaseClass::RunTask( pTask ); + break; + } +} + + +//--------------------------------------------------------- +// Make the necessary changes to a zombie to make him a +// torso! +//--------------------------------------------------------- +void CNPC_BaseZombie::BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce ) +{ + if( m_fIsTorso ) + { + DevMsg( "*** Zombie is already a torso!\n" ); + return; + } + + if( IsOnFire() ) + { + Extinguish(); + Ignite( 30 ); + } + + if ( !m_fIsHeadless ) + { + m_iMaxHealth = ZOMBIE_TORSO_HEALTH_FACTOR * m_iMaxHealth; + m_iHealth = m_iMaxHealth; + + // No more opening doors! + CapabilitiesRemove( bits_CAP_DOORS_GROUP ); + + ClearSchedule( "Becoming torso" ); + GetNavigator()->ClearGoal(); + m_hPhysicsEnt = NULL; + + // Put the zombie in a TOSS / fall schedule + // Otherwise he fails and sits on the ground for a sec. + SetSchedule( SCHED_FALL_TO_GROUND ); + + m_fIsTorso = true; + + // Put the torso up where the torso was when the zombie + // was whole. + Vector origin = GetAbsOrigin(); + origin.z += 40; + SetAbsOrigin( origin ); + + SetGroundEntity( NULL ); + // assume zombie mass ~ 100 kg + ApplyAbsVelocityImpulse( vecTorsoForce * (1.0 / 100.0) ); + } + + float flFadeTime = 0.0; + + if( HasSpawnFlags( SF_NPC_FADE_CORPSE ) ) + { + flFadeTime = 5.0; + } + + if ( m_fIsTorso == true ) + { + // -40 on Z to make up for the +40 on Z that we did above. This stops legs spawning above the head. + CBaseEntity *pGib = CreateRagGib( GetLegsModel(), GetAbsOrigin() - Vector(0, 0, 40), GetAbsAngles(), vecLegsForce, flFadeTime ); + + // don't collide with this thing ever + if ( pGib ) + { + pGib->SetOwnerEntity( this ); + } + } + + + SetZombieModel(); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::Event_Killed( const CTakeDamageInfo &info ) +{ + if ( info.GetDamageType() & DMG_VEHICLE ) + { + Vector vecDamageDir = info.GetDamageForce(); + VectorNormalize( vecDamageDir ); + + // Big blood splat + UTIL_BloodSpray( WorldSpaceCenter(), vecDamageDir, BLOOD_COLOR_YELLOW, 8, FX_BLOODSPRAY_CLOUD ); + } + + BaseClass::Event_Killed( info ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CNPC_BaseZombie::BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector ) +{ + bool bKilledByVehicle = ( ( info.GetDamageType() & DMG_VEHICLE ) != 0 ); + if( m_fIsTorso || (!IsChopped(info) && !IsSquashed(info)) || bKilledByVehicle ) + { + return BaseClass::BecomeRagdoll( info, forceVector ); + } + + if( !(GetFlags()&FL_TRANSRAGDOLL) ) + { + RemoveDeferred(); + } + + return true; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::StopLoopingSounds() +{ + ENVELOPE_CONTROLLER.SoundDestroy( m_pMoanSound ); + m_pMoanSound = NULL; + + BaseClass::StopLoopingSounds(); +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::RemoveHead( void ) +{ + m_fIsHeadless = true; + SetZombieModel(); +} + + +bool CNPC_BaseZombie::ShouldPlayFootstepMoan( void ) +{ + if( random->RandomInt( 1, zombie_stepfreq.GetInt() * s_iAngryZombies ) == 1 ) + { + return true; + } + + return false; +} + + +#define ZOMBIE_CRAB_INHERITED_SPAWNFLAGS (SF_NPC_GAG|SF_NPC_LONG_RANGE|SF_NPC_FADE_CORPSE|SF_NPC_ALWAYSTHINK) +#define CRAB_HULL_EXPAND 1.1f +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_BaseZombie::HeadcrabFits( CBaseAnimating *pCrab ) +{ + Vector vecSpawnLoc = pCrab->GetAbsOrigin(); + + CTraceFilterSimpleList traceFilter( COLLISION_GROUP_NONE ); + traceFilter.AddEntityToIgnore( pCrab ); + traceFilter.AddEntityToIgnore( this ); + if ( GetInteractionPartner() ) + { + traceFilter.AddEntityToIgnore( GetInteractionPartner() ); + } + + trace_t tr; + AI_TraceHull( vecSpawnLoc, + vecSpawnLoc - Vector( 0, 0, 1 ), + NAI_Hull::Mins(HULL_TINY) * CRAB_HULL_EXPAND, + NAI_Hull::Maxs(HULL_TINY) * CRAB_HULL_EXPAND, + MASK_NPCSOLID, + &traceFilter, + &tr ); + + if( tr.fraction != 1.0 ) + { + //NDebugOverlay::Box( vecSpawnLoc, NAI_Hull::Mins(HULL_TINY) * CRAB_HULL_EXPAND, NAI_Hull::Maxs(HULL_TINY) * CRAB_HULL_EXPAND, 255, 0, 0, 100, 10.0 ); + return false; + } + + //NDebugOverlay::Box( vecSpawnLoc, NAI_Hull::Mins(HULL_TINY) * CRAB_HULL_EXPAND, NAI_Hull::Maxs(HULL_TINY) * CRAB_HULL_EXPAND, 0, 255, 0, 100, 10.0 ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecOrigin - +// &vecVelocity - +// fRemoveHead - +// fRagdollBody - +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &vecVelocity, bool fRemoveHead, bool fRagdollBody, bool fRagdollCrab ) +{ + CAI_BaseNPC *pCrab; + Vector vecSpot = vecOrigin; + + // Until the headcrab is a bodygroup, we have to approximate the + // location of the head with magic numbers. + if( !m_fIsTorso ) + { + vecSpot.z -= 16; + } + + if( fRagdollCrab ) + { + //Vector vecForce = Vector( 0, 0, random->RandomFloat( 700, 1100 ) ); + CBaseEntity *pGib = CreateRagGib( GetHeadcrabModel(), vecOrigin, GetLocalAngles(), vecVelocity, 15, ShouldIgniteZombieGib() ); + + if ( pGib ) + { + CBaseAnimating *pAnimatingGib = dynamic_cast<CBaseAnimating*>(pGib); + + // don't collide with this thing ever + int iCrabAttachment = LookupAttachment( "headcrab" ); + if (iCrabAttachment > 0 && pAnimatingGib ) + { + SetHeadcrabSpawnLocation( iCrabAttachment, pAnimatingGib ); + } + + if( !HeadcrabFits(pAnimatingGib) ) + { + UTIL_Remove(pGib); + return; + } + + pGib->SetOwnerEntity( this ); + CopyRenderColorTo( pGib ); + + + if( UTIL_ShouldShowBlood(BLOOD_COLOR_YELLOW) ) + { + UTIL_BloodImpact( pGib->WorldSpaceCenter(), Vector(0,0,1), BLOOD_COLOR_YELLOW, 1 ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + Vector vecSpot = pGib->WorldSpaceCenter(); + + vecSpot.x += random->RandomFloat( -8, 8 ); + vecSpot.y += random->RandomFloat( -8, 8 ); + vecSpot.z += random->RandomFloat( -8, 8 ); + + UTIL_BloodDrips( vecSpot, vec3_origin, BLOOD_COLOR_YELLOW, 50 ); + } + } + } + } + else + { + pCrab = (CAI_BaseNPC*)CreateEntityByName( GetHeadcrabClassname() ); + + if ( !pCrab ) + { + Warning( "**%s: Can't make %s!\n", GetClassname(), GetHeadcrabClassname() ); + return; + } + + // Stick the crab in whatever squad the zombie was in. + pCrab->SetSquadName( m_SquadName ); + + // don't pop to floor, fall + pCrab->AddSpawnFlags( SF_NPC_FALL_TO_GROUND ); + + // add on the parent flags + pCrab->AddSpawnFlags( m_spawnflags & ZOMBIE_CRAB_INHERITED_SPAWNFLAGS ); + + // make me the crab's owner to avoid collision issues + pCrab->SetOwnerEntity( this ); + + pCrab->SetAbsOrigin( vecSpot ); + pCrab->SetAbsAngles( GetAbsAngles() ); + DispatchSpawn( pCrab ); + + pCrab->GetMotor()->SetIdealYaw( GetAbsAngles().y ); + + // FIXME: npc's with multiple headcrabs will need some way to query different attachments. + // NOTE: this has till after spawn is called so that the model is set up + int iCrabAttachment = LookupAttachment( "headcrab" ); + if (iCrabAttachment > 0) + { + SetHeadcrabSpawnLocation( iCrabAttachment, pCrab ); + pCrab->GetMotor()->SetIdealYaw( pCrab->GetAbsAngles().y ); + + // Take out any pitch + QAngle angles = pCrab->GetAbsAngles(); + angles.x = 0.0; + pCrab->SetAbsAngles( angles ); + } + + if( !HeadcrabFits(pCrab) ) + { + UTIL_Remove(pCrab); + return; + } + + pCrab->SetActivity( ACT_IDLE ); + pCrab->SetNextThink( gpGlobals->curtime ); + pCrab->PhysicsSimulate(); + pCrab->SetAbsVelocity( vecVelocity ); + + // if I have an enemy, stuff that to the headcrab. + CBaseEntity *pEnemy; + pEnemy = GetEnemy(); + + pCrab->m_flNextAttack = gpGlobals->curtime + 1.0f; + + if( pEnemy ) + { + pCrab->SetEnemy( pEnemy ); + } + if( ShouldIgniteZombieGib() ) + { + pCrab->Ignite( 30 ); + } + + CopyRenderColorTo( pCrab ); + + pCrab->Activate(); + } + + if( fRemoveHead ) + { + RemoveHead(); + } + + if( fRagdollBody ) + { + BecomeRagdollOnClient( vec3_origin ); + } +} + + + +void CNPC_BaseZombie::SetHeadcrabSpawnLocation( int iCrabAttachment, CBaseAnimating *pCrab ) +{ + Assert( iCrabAttachment > 0 ); + + // get world location of intended headcrab root bone + matrix3x4_t attachmentToWorld; + GetAttachment( iCrabAttachment, attachmentToWorld ); + + // find offset of root bone from origin + pCrab->SetAbsOrigin( Vector( 0, 0, 0 ) ); + pCrab->SetAbsAngles( QAngle( 0, 0, 0 ) ); + pCrab->InvalidateBoneCache(); + matrix3x4_t rootLocal; + pCrab->GetBoneTransform( 0, rootLocal ); + + // invert it + matrix3x4_t rootInvLocal; + MatrixInvert( rootLocal, rootInvLocal ); + + // find spawn location needed for rootLocal transform to match attachmentToWorld + matrix3x4_t spawnOrigin; + ConcatTransforms( attachmentToWorld, rootInvLocal, spawnOrigin ); + + // reset location of headcrab + Vector vecOrigin; + QAngle vecAngles; + MatrixAngles( spawnOrigin, vecAngles, vecOrigin ); + pCrab->SetAbsOrigin( vecOrigin ); + + // FIXME: head crabs don't like pitch or roll! + vecAngles.z = 0; + + pCrab->SetAbsAngles( vecAngles ); + pCrab->InvalidateBoneCache(); +} + + + +//--------------------------------------------------------- +// Provides a standard way for the zombie to get the +// distance to a physics ent. Since the code to find physics +// objects uses a fast dis approx, we have to use that here +// as well. +//--------------------------------------------------------- +float CNPC_BaseZombie::DistToPhysicsEnt( void ) +{ + //return ( GetLocalOrigin() - m_hPhysicsEnt->GetLocalOrigin() ).Length(); + if ( m_hPhysicsEnt != NULL ) + return UTIL_DistApprox2D( GetAbsOrigin(), m_hPhysicsEnt->WorldSpaceCenter() ); + return ZOMBIE_PHYSOBJ_SWATDIST + 1; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::OnStateChange( NPC_STATE OldState, NPC_STATE NewState ) +{ + switch( NewState ) + { + case NPC_STATE_COMBAT: + { + RemoveSpawnFlags( SF_NPC_GAG ); + s_iAngryZombies++; + } + break; + + default: + if( OldState == NPC_STATE_COMBAT ) + { + // Only decrement if coming OUT of combat state. + s_iAngryZombies--; + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Refines a base activity into something more specific to our internal state. +//----------------------------------------------------------------------------- +Activity CNPC_BaseZombie::NPC_TranslateActivity( Activity baseAct ) +{ + if ( baseAct == ACT_WALK && IsCurSchedule( SCHED_COMBAT_PATROL, false) ) + baseAct = ACT_RUN; + + if ( IsOnFire() ) + { + switch ( baseAct ) + { + case ACT_RUN_ON_FIRE: + { + return ( Activity )ACT_WALK_ON_FIRE; + } + + case ACT_WALK: + { + // I'm on fire. Put ME out. + return ( Activity )ACT_WALK_ON_FIRE; + } + + case ACT_IDLE: + { + // I'm on fire. Put ME out. + return ( Activity )ACT_IDLE_ON_FIRE; + } + } + } + + return BaseClass::NPC_TranslateActivity( baseAct ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +Vector CNPC_BaseZombie::BodyTarget( const Vector &posSrc, bool bNoisy ) +{ + + if( IsCurSchedule(SCHED_BIG_FLINCH) || m_ActBusyBehavior.IsActive() ) + { + // This zombie is assumed to be standing up. + // Return a position that's centered over the absorigin, + // halfway between the origin and the head. + Vector vecTarget = GetAbsOrigin(); + Vector vecHead = HeadTarget( posSrc ); + vecTarget.z = ((vecTarget.z + vecHead.z) * 0.5f); + return vecTarget; + } + + return BaseClass::BodyTarget( posSrc, bNoisy ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +Vector CNPC_BaseZombie::HeadTarget( const Vector &posSrc ) +{ + int iCrabAttachment = LookupAttachment( "headcrab" ); + Assert( iCrabAttachment > 0 ); + + Vector vecPosition; + + GetAttachment( iCrabAttachment, vecPosition ); + + return vecPosition; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +float CNPC_BaseZombie::GetAutoAimRadius() +{ + if( m_fIsTorso ) + { + return 12.0f; + } + + return BaseClass::GetAutoAimRadius(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_BaseZombie::OnInsufficientStopDist( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) +{ + if ( pMoveGoal->directTrace.fStatus == AIMR_BLOCKED_ENTITY && gpGlobals->curtime >= m_flNextSwat ) + { + m_hObstructor = pMoveGoal->directTrace.pObstruction; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEnemy - +// &chasePosition - +//----------------------------------------------------------------------------- +void CNPC_BaseZombie::TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition ) +{ + // If our enemy is in a vehicle, we need them to tell us where to navigate to them + if ( pEnemy == NULL ) + return; + + CBaseCombatCharacter *pBCC = pEnemy->MyCombatCharacterPointer(); + if ( pBCC && pBCC->IsInAVehicle() ) + { + Vector vecForward, vecRight; + pBCC->GetVectors( &vecForward, &vecRight, NULL ); + + chasePosition = pBCC->WorldSpaceCenter() + ( vecForward * 24.0f ) + ( vecRight * 48.0f ); + return; + } + + BaseClass::TranslateNavGoal( pEnemy, chasePosition ); +} + +//----------------------------------------------------------------------------- +// +// Schedules +// +//----------------------------------------------------------------------------- + +AI_BEGIN_CUSTOM_NPC( base_zombie, CNPC_BaseZombie ) + + DECLARE_TASK( TASK_ZOMBIE_DELAY_SWAT ) + DECLARE_TASK( TASK_ZOMBIE_SWAT_ITEM ) + DECLARE_TASK( TASK_ZOMBIE_GET_PATH_TO_PHYSOBJ ) + DECLARE_TASK( TASK_ZOMBIE_DIE ) + DECLARE_TASK( TASK_ZOMBIE_RELEASE_HEADCRAB ) + DECLARE_TASK( TASK_ZOMBIE_WAIT_POST_MELEE ) + + DECLARE_ACTIVITY( ACT_ZOM_SWATLEFTMID ) + DECLARE_ACTIVITY( ACT_ZOM_SWATRIGHTMID ) + DECLARE_ACTIVITY( ACT_ZOM_SWATLEFTLOW ) + DECLARE_ACTIVITY( ACT_ZOM_SWATRIGHTLOW ) + DECLARE_ACTIVITY( ACT_ZOM_RELEASECRAB ) + DECLARE_ACTIVITY( ACT_ZOM_FALL ) + + DECLARE_CONDITION( COND_ZOMBIE_CAN_SWAT_ATTACK ) + DECLARE_CONDITION( COND_ZOMBIE_RELEASECRAB ) + DECLARE_CONDITION( COND_ZOMBIE_LOCAL_MELEE_OBSTRUCTION ) + + //Adrian: events go here + DECLARE_ANIMEVENT( AE_ZOMBIE_ATTACK_RIGHT ) + DECLARE_ANIMEVENT( AE_ZOMBIE_ATTACK_LEFT ) + DECLARE_ANIMEVENT( AE_ZOMBIE_ATTACK_BOTH ) + DECLARE_ANIMEVENT( AE_ZOMBIE_SWATITEM ) + DECLARE_ANIMEVENT( AE_ZOMBIE_STARTSWAT ) + DECLARE_ANIMEVENT( AE_ZOMBIE_STEP_LEFT ) + DECLARE_ANIMEVENT( AE_ZOMBIE_STEP_RIGHT ) + DECLARE_ANIMEVENT( AE_ZOMBIE_SCUFF_LEFT ) + DECLARE_ANIMEVENT( AE_ZOMBIE_SCUFF_RIGHT ) + DECLARE_ANIMEVENT( AE_ZOMBIE_ATTACK_SCREAM ) + DECLARE_ANIMEVENT( AE_ZOMBIE_GET_UP ) + DECLARE_ANIMEVENT( AE_ZOMBIE_POUND ) + DECLARE_ANIMEVENT( AE_ZOMBIE_ALERTSOUND ) + DECLARE_ANIMEVENT( AE_ZOMBIE_POPHEADCRAB ) + + DECLARE_INTERACTION( g_interactionZombieMeleeWarning ) + + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_MOVE_SWATITEM, + + " Tasks" + " TASK_ZOMBIE_DELAY_SWAT 3" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" + " TASK_ZOMBIE_GET_PATH_TO_PHYSOBJ 0" + " TASK_WALK_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_FACE_ENEMY 0" + " TASK_ZOMBIE_SWAT_ITEM 0" + " " + " Interrupts" + " COND_ZOMBIE_RELEASECRAB" + " COND_ENEMY_DEAD" + " COND_NEW_ENEMY" + ) + + //========================================================= + // SwatItem + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_SWATITEM, + + " Tasks" + " TASK_ZOMBIE_DELAY_SWAT 3" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" + " TASK_FACE_ENEMY 0" + " TASK_ZOMBIE_SWAT_ITEM 0" + " " + " Interrupts" + " COND_ZOMBIE_RELEASECRAB" + " COND_ENEMY_DEAD" + " COND_NEW_ENEMY" + ) + + //========================================================= + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_ATTACKITEM, + + " Tasks" + " TASK_FACE_ENEMY 0" + " TASK_MELEE_ATTACK1 0" + " " + " Interrupts" + " COND_ZOMBIE_RELEASECRAB" + " COND_ENEMY_DEAD" + " COND_NEW_ENEMY" + ) + + //========================================================= + // ChaseEnemy + //========================================================= +#ifdef HL2_EPISODIC + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_CHASE_ENEMY, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" + " TASK_SET_TOLERANCE_DISTANCE 24" + " TASK_GET_CHASE_PATH_TO_ENEMY 600" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_FACE_ENEMY 0" + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_ENEMY_UNREACHABLE" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK2" + " COND_TOO_CLOSE_TO_ATTACK" + " COND_TASK_FAILED" + " COND_ZOMBIE_CAN_SWAT_ATTACK" + " COND_ZOMBIE_RELEASECRAB" + " COND_HEAVY_DAMAGE" + ) +#else + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_CHASE_ENEMY, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" + " TASK_SET_TOLERANCE_DISTANCE 24" + " TASK_GET_CHASE_PATH_TO_ENEMY 600" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_FACE_ENEMY 0" + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_ENEMY_UNREACHABLE" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK2" + " COND_TOO_CLOSE_TO_ATTACK" + " COND_TASK_FAILED" + " COND_ZOMBIE_CAN_SWAT_ATTACK" + " COND_ZOMBIE_RELEASECRAB" + ) +#endif // HL2_EPISODIC + + + //========================================================= + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_RELEASECRAB, + + " Tasks" + " TASK_PLAY_PRIVATE_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ZOM_RELEASECRAB" + " TASK_ZOMBIE_RELEASE_HEADCRAB 0" + " TASK_ZOMBIE_DIE 0" + " " + " Interrupts" + " COND_TASK_FAILED" + ) + + + //========================================================= + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_MOVE_TO_AMBUSH, + + " Tasks" + " TASK_WAIT 1.0" // don't react as soon as you see the player. + " TASK_FIND_COVER_FROM_ENEMY 0" + " TASK_WALK_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_STOP_MOVING 0" + " TASK_TURN_LEFT 180" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_ZOMBIE_WAIT_AMBUSH" + " " + " Interrupts" + " COND_TASK_FAILED" + " COND_NEW_ENEMY" + ) + + + //========================================================= + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_WAIT_AMBUSH, + + " Tasks" + " TASK_WAIT_FACE_ENEMY 99999" + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_SEE_ENEMY" + ) + + //========================================================= + // Wander around for a while so we don't look stupid. + // this is done if we ever lose track of our enemy. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_WANDER_MEDIUM, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_WANDER 480384" // 4 feet to 32 feet + " TASK_WALK_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_STOP_MOVING 0" + " TASK_WAIT_PVS 0" // if the player left my PVS, just wait. + " TASK_SET_SCHEDULE SCHEDULE:SCHED_ZOMBIE_WANDER_MEDIUM" // keep doing it + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_SEE_ENEMY" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + ) + + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_WANDER_STANDOFF, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_WANDER 480384" // 4 feet to 32 feet + " TASK_WALK_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_STOP_MOVING 0" + " TASK_WAIT_PVS 0" // if the player left my PVS, just wait. + " " + " Interrupts" + " COND_NEW_ENEMY" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_ENEMY_DEAD" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK2" + " COND_ZOMBIE_RELEASECRAB" + ) + + //========================================================= + // If you fail to wander, wait just a bit and try again. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_WANDER_FAIL, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_WAIT 1" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_ZOMBIE_WANDER_MEDIUM" + " Interrupts" + " COND_NEW_ENEMY" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_ENEMY_DEAD" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK2" + " COND_ZOMBIE_RELEASECRAB" + ) + + //========================================================= + // Like the base class, only don't stop in the middle of + // swinging if the enemy is killed, hides, or new enemy. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_MELEE_ATTACK1, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_FACE_ENEMY 0" + " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack + " TASK_MELEE_ATTACK1 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_ZOMBIE_POST_MELEE_WAIT" + "" + " Interrupts" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + ) + + //========================================================= + // Make the zombie wait a frame after a melee attack, to + // allow itself & it's enemy to test for dynamic scripted sequences. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_ZOMBIE_POST_MELEE_WAIT, + + " Tasks" + " TASK_ZOMBIE_WAIT_POST_MELEE 0" + ) + +AI_END_CUSTOM_NPC() |