aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_BaseZombie.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/npc_BaseZombie.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/npc_BaseZombie.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_BaseZombie.cpp3023
1 files changed, 3023 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_BaseZombie.cpp b/mp/src/game/server/hl2/npc_BaseZombie.cpp
new file mode 100644
index 00000000..39646480
--- /dev/null
+++ b/mp/src/game/server/hl2/npc_BaseZombie.cpp
@@ -0,0 +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()