You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2156 lines
57 KiB
2156 lines
57 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "ai_basenpc.h" |
|
#include "ai_default.h" |
|
#include "ai_schedule.h" |
|
#include "ai_hull.h" |
|
#include "ai_motor.h" |
|
#include "ai_memory.h" |
|
#include "ai_route.h" |
|
#include "soundent.h" |
|
#include "game.h" |
|
#include "npcevent.h" |
|
#include "entitylist.h" |
|
#include "ai_task.h" |
|
#include "activitylist.h" |
|
#include "engine/IEngineSound.h" |
|
#include "npc_BaseZombie.h" |
|
#include "movevars_shared.h" |
|
#include "IEffects.h" |
|
#include "props.h" |
|
#include "physics_npc_solver.h" |
|
#include "physics_prop_ragdoll.h" |
|
|
|
#ifdef HL2_EPISODIC |
|
#include "episodic/ai_behavior_passenger_zombie.h" |
|
#endif // HL2_EPISODIC |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define FASTZOMBIE_IDLE_PITCH 35 |
|
#define FASTZOMBIE_MIN_PITCH 70 |
|
#define FASTZOMBIE_MAX_PITCH 130 |
|
#define FASTZOMBIE_SOUND_UPDATE_FREQ 0.5 |
|
|
|
#define FASTZOMBIE_MAXLEAP_Z 128 |
|
|
|
#define FASTZOMBIE_EXCITE_DIST 480.0 |
|
|
|
#define FASTZOMBIE_BASE_FREQ 1.5 |
|
|
|
// If flying at an enemy, and this close or closer, start playing the maul animation!! |
|
#define FASTZOMBIE_MAUL_RANGE 300 |
|
|
|
#ifdef HL2_EPISODIC |
|
|
|
int AE_PASSENGER_PHYSICS_PUSH; |
|
int AE_FASTZOMBIE_VEHICLE_LEAP; |
|
int AE_FASTZOMBIE_VEHICLE_SS_DIE; // Killed while doing scripted sequence on vehicle |
|
|
|
#endif // HL2_EPISODIC |
|
|
|
enum |
|
{ |
|
COND_FASTZOMBIE_CLIMB_TOUCH = LAST_BASE_ZOMBIE_CONDITION, |
|
}; |
|
|
|
envelopePoint_t envFastZombieVolumeJump[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
1.0f, 1.2f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envFastZombieVolumePain[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
1.0f, 1.0f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envFastZombieInverseVolumePain[] = |
|
{ |
|
{ 0.0f, 0.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 1.0f, 1.0f, |
|
1.0f, 1.0f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envFastZombieVolumeJumpPostApex[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
1.0f, 1.2f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envFastZombieVolumeClimb[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
0.2f, 0.2f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envFastZombieMoanVolumeFast[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
0.2f, 0.3f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envFastZombieMoanVolume[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 1.0f, 1.0f, |
|
0.2f, 0.2f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
1.0f, 0.4f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envFastZombieFootstepVolume[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 0.7f, 0.7f, |
|
0.2f, 0.2f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envFastZombieVolumeFrenzy[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.1f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
2.0f, 2.0f, |
|
}, |
|
}; |
|
|
|
|
|
//========================================================= |
|
// animation events |
|
//========================================================= |
|
int AE_FASTZOMBIE_LEAP; |
|
int AE_FASTZOMBIE_GALLOP_LEFT; |
|
int AE_FASTZOMBIE_GALLOP_RIGHT; |
|
int AE_FASTZOMBIE_CLIMB_LEFT; |
|
int AE_FASTZOMBIE_CLIMB_RIGHT; |
|
|
|
//========================================================= |
|
// tasks |
|
//========================================================= |
|
enum |
|
{ |
|
TASK_FASTZOMBIE_DO_ATTACK = LAST_SHARED_TASK + 100, // again, my !!!HACKHACK |
|
TASK_FASTZOMBIE_LAND_RECOVER, |
|
TASK_FASTZOMBIE_UNSTICK_JUMP, |
|
TASK_FASTZOMBIE_JUMP_BACK, |
|
TASK_FASTZOMBIE_VERIFY_ATTACK, |
|
}; |
|
|
|
//========================================================= |
|
// activities |
|
//========================================================= |
|
int ACT_FASTZOMBIE_LEAP_SOAR; |
|
int ACT_FASTZOMBIE_LEAP_STRIKE; |
|
int ACT_FASTZOMBIE_LAND_RIGHT; |
|
int ACT_FASTZOMBIE_LAND_LEFT; |
|
int ACT_FASTZOMBIE_FRENZY; |
|
int ACT_FASTZOMBIE_BIG_SLASH; |
|
|
|
//========================================================= |
|
// schedules |
|
//========================================================= |
|
enum |
|
{ |
|
SCHED_FASTZOMBIE_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE + 100, // hack to get past the base zombie's schedules |
|
SCHED_FASTZOMBIE_UNSTICK_JUMP, |
|
SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP, |
|
SCHED_FASTZOMBIE_MELEE_ATTACK1, |
|
SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1, |
|
}; |
|
|
|
|
|
|
|
//========================================================= |
|
//========================================================= |
|
class CFastZombie : public CNPC_BaseZombie |
|
{ |
|
DECLARE_CLASS( CFastZombie, CNPC_BaseZombie ); |
|
|
|
public: |
|
void Spawn( void ); |
|
void Precache( void ); |
|
|
|
void SetZombieModel( void ); |
|
bool CanSwatPhysicsObjects( void ) { return false; } |
|
|
|
int TranslateSchedule( int scheduleType ); |
|
|
|
Activity NPC_TranslateActivity( Activity baseAct ); |
|
|
|
void LeapAttackTouch( CBaseEntity *pOther ); |
|
void ClimbTouch( CBaseEntity *pOther ); |
|
|
|
void StartTask( const Task_t *pTask ); |
|
void RunTask( const Task_t *pTask ); |
|
int SelectSchedule( void ); |
|
void OnScheduleChange( void ); |
|
|
|
void PrescheduleThink( void ); |
|
|
|
float InnateRange1MaxRange( void ); |
|
int RangeAttack1Conditions( float flDot, float flDist ); |
|
int MeleeAttack1Conditions( float flDot, float flDist ); |
|
|
|
virtual float GetClawAttackRange() const { return 50; } |
|
|
|
bool ShouldPlayFootstepMoan( void ) { return false; } |
|
|
|
void HandleAnimEvent( animevent_t *pEvent ); |
|
|
|
void PostNPCInit( void ); |
|
|
|
void LeapAttack( void ); |
|
void LeapAttackSound( void ); |
|
|
|
void BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce ); |
|
|
|
bool IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const; |
|
bool MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost ); |
|
bool ShouldFailNav( bool bMovementFailed ); |
|
|
|
int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ); |
|
|
|
const char *GetMoanSound( int nSound ); |
|
|
|
void OnChangeActivity( Activity NewActivity ); |
|
void OnStateChange( NPC_STATE OldState, NPC_STATE NewState ); |
|
void Event_Killed( const CTakeDamageInfo &info ); |
|
bool ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold ); |
|
|
|
virtual Vector GetAutoAimCenter() { return WorldSpaceCenter() - Vector( 0, 0, 12.0f ); } |
|
|
|
void PainSound( const CTakeDamageInfo &info ); |
|
void DeathSound( const CTakeDamageInfo &info ); |
|
void AlertSound( void ); |
|
void IdleSound( void ); |
|
void AttackSound( void ); |
|
void AttackHitSound( void ); |
|
void AttackMissSound( void ); |
|
void FootstepSound( bool fRightFoot ); |
|
void FootscuffSound( bool fRightFoot ) {}; // fast guy doesn't scuff |
|
void StopLoopingSounds( void ); |
|
|
|
void SoundInit( void ); |
|
void SetIdleSoundState( void ); |
|
void SetAngrySoundState( void ); |
|
|
|
void BuildScheduleTestBits( void ); |
|
|
|
void BeginNavJump( void ); |
|
void EndNavJump( void ); |
|
|
|
bool IsNavJumping( void ) { return m_fIsNavJumping; } |
|
void OnNavJumpHitApex( void ); |
|
|
|
void BeginAttackJump( void ); |
|
void EndAttackJump( void ); |
|
|
|
float MaxYawSpeed( void ); |
|
|
|
virtual const char *GetHeadcrabClassname( void ); |
|
virtual const char *GetHeadcrabModel( void ); |
|
virtual const char *GetLegsModel( void ); |
|
virtual const char *GetTorsoModel( void ); |
|
|
|
//============================================================================= |
|
#ifdef HL2_EPISODIC |
|
|
|
public: |
|
virtual bool CreateBehaviors( void ); |
|
virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ); |
|
virtual void UpdateEfficiency( bool bInPVS ); |
|
virtual bool IsInAVehicle( void ); |
|
void InputAttachToVehicle( inputdata_t &inputdata ); |
|
void VehicleLeapAttackTouch( CBaseEntity *pOther ); |
|
|
|
private: |
|
void VehicleLeapAttack( void ); |
|
bool CanEnterVehicle( CPropJeepEpisodic *pVehicle ); |
|
|
|
CAI_PassengerBehaviorZombie m_PassengerBehavior; |
|
|
|
#endif // HL2_EPISODIC |
|
//============================================================================= |
|
|
|
protected: |
|
|
|
static const char *pMoanSounds[]; |
|
|
|
// Sound stuff |
|
float m_flDistFactor; |
|
unsigned char m_iClimbCount; // counts rungs climbed (for sound) |
|
bool m_fIsNavJumping; |
|
bool m_fIsAttackJumping; |
|
bool m_fHitApex; |
|
mutable float m_flJumpDist; |
|
|
|
bool m_fHasScreamed; |
|
|
|
private: |
|
float m_flNextMeleeAttack; |
|
bool m_fJustJumped; |
|
float m_flJumpStartAltitude; |
|
float m_flTimeUpdateSound; |
|
|
|
CSoundPatch *m_pLayer2; // used for climbing ladders, and when jumping (pre apex) |
|
|
|
public: |
|
DEFINE_CUSTOM_AI; |
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( npc_fastzombie, CFastZombie ); |
|
LINK_ENTITY_TO_CLASS( npc_fastzombie_torso, CFastZombie ); |
|
|
|
|
|
BEGIN_DATADESC( CFastZombie ) |
|
|
|
DEFINE_FIELD( m_flDistFactor, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_iClimbCount, FIELD_CHARACTER ), |
|
DEFINE_FIELD( m_fIsNavJumping, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_fIsAttackJumping, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_fHitApex, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flJumpDist, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_fHasScreamed, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flNextMeleeAttack, FIELD_TIME ), |
|
DEFINE_FIELD( m_fJustJumped, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flJumpStartAltitude, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flTimeUpdateSound, FIELD_TIME ), |
|
|
|
// Function Pointers |
|
DEFINE_ENTITYFUNC( LeapAttackTouch ), |
|
DEFINE_ENTITYFUNC( ClimbTouch ), |
|
DEFINE_SOUNDPATCH( m_pLayer2 ), |
|
|
|
#ifdef HL2_EPISODIC |
|
DEFINE_ENTITYFUNC( VehicleLeapAttackTouch ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "AttachToVehicle", InputAttachToVehicle ), |
|
#endif // HL2_EPISODIC |
|
|
|
END_DATADESC() |
|
|
|
|
|
const char *CFastZombie::pMoanSounds[] = |
|
{ |
|
"NPC_FastZombie.Moan1", |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// The model we use for our legs when we get blowed up. |
|
//----------------------------------------------------------------------------- |
|
static const char *s_pLegsModel = "models/gibs/fast_zombie_legs.mdl"; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::Precache( void ) |
|
{ |
|
PrecacheModel("models/zombie/fast.mdl"); |
|
#ifdef HL2_EPISODIC |
|
PrecacheModel("models/zombie/Fast_torso.mdl"); |
|
PrecacheScriptSound( "NPC_FastZombie.CarEnter1" ); |
|
PrecacheScriptSound( "NPC_FastZombie.CarEnter2" ); |
|
PrecacheScriptSound( "NPC_FastZombie.CarEnter3" ); |
|
PrecacheScriptSound( "NPC_FastZombie.CarEnter4" ); |
|
PrecacheScriptSound( "NPC_FastZombie.CarScream" ); |
|
#endif |
|
PrecacheModel( "models/gibs/fast_zombie_torso.mdl" ); |
|
PrecacheModel( "models/gibs/fast_zombie_legs.mdl" ); |
|
|
|
PrecacheScriptSound( "NPC_FastZombie.LeapAttack" ); |
|
PrecacheScriptSound( "NPC_FastZombie.FootstepRight" ); |
|
PrecacheScriptSound( "NPC_FastZombie.FootstepLeft" ); |
|
PrecacheScriptSound( "NPC_FastZombie.AttackHit" ); |
|
PrecacheScriptSound( "NPC_FastZombie.AttackMiss" ); |
|
PrecacheScriptSound( "NPC_FastZombie.LeapAttack" ); |
|
PrecacheScriptSound( "NPC_FastZombie.Attack" ); |
|
PrecacheScriptSound( "NPC_FastZombie.Idle" ); |
|
PrecacheScriptSound( "NPC_FastZombie.AlertFar" ); |
|
PrecacheScriptSound( "NPC_FastZombie.AlertNear" ); |
|
PrecacheScriptSound( "NPC_FastZombie.GallopLeft" ); |
|
PrecacheScriptSound( "NPC_FastZombie.GallopRight" ); |
|
PrecacheScriptSound( "NPC_FastZombie.Scream" ); |
|
PrecacheScriptSound( "NPC_FastZombie.RangeAttack" ); |
|
PrecacheScriptSound( "NPC_FastZombie.Frenzy" ); |
|
PrecacheScriptSound( "NPC_FastZombie.NoSound" ); |
|
PrecacheScriptSound( "NPC_FastZombie.Die" ); |
|
|
|
PrecacheScriptSound( "NPC_FastZombie.Gurgle" ); |
|
|
|
PrecacheScriptSound( "NPC_FastZombie.Moan1" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFastZombie::OnScheduleChange( void ) |
|
{ |
|
if ( m_flNextMeleeAttack > gpGlobals->curtime + 1 ) |
|
{ |
|
// Allow melee attacks again. |
|
m_flNextMeleeAttack = gpGlobals->curtime + 0.5; |
|
} |
|
|
|
BaseClass::OnScheduleChange(); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
int CFastZombie::SelectSchedule ( void ) |
|
{ |
|
|
|
// ======================================================== |
|
#ifdef HL2_EPISODIC |
|
|
|
// Defer all decisions to the behavior if it's running |
|
if ( m_PassengerBehavior.CanSelectSchedule() ) |
|
{ |
|
DeferSchedulingToBehavior( &m_PassengerBehavior ); |
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
#endif //HL2_EPISODIC |
|
// ======================================================== |
|
|
|
if ( HasCondition( COND_ZOMBIE_RELEASECRAB ) ) |
|
{ |
|
// Death waits for no man. Or zombie. Or something. |
|
return SCHED_ZOMBIE_RELEASECRAB; |
|
} |
|
|
|
if ( HasCondition( COND_FASTZOMBIE_CLIMB_TOUCH ) ) |
|
{ |
|
return SCHED_FASTZOMBIE_UNSTICK_JUMP; |
|
} |
|
|
|
switch ( m_NPCState ) |
|
{ |
|
case NPC_STATE_COMBAT: |
|
if ( HasCondition( COND_LOST_ENEMY ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) ) |
|
{ |
|
// Set state to alert and recurse! |
|
SetState( NPC_STATE_ALERT ); |
|
return SelectSchedule(); |
|
} |
|
break; |
|
|
|
case NPC_STATE_ALERT: |
|
if ( HasCondition( COND_LOST_ENEMY ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) ) |
|
{ |
|
ClearCondition( COND_LOST_ENEMY ); |
|
ClearCondition( COND_ENEMY_UNREACHABLE ); |
|
SetEnemy( NULL ); |
|
|
|
#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(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::PrescheduleThink( void ) |
|
{ |
|
BaseClass::PrescheduleThink(); |
|
|
|
if( GetGroundEntity() && GetGroundEntity()->Classify() == CLASS_HEADCRAB ) |
|
{ |
|
// Kill! |
|
CTakeDamageInfo info; |
|
info.SetDamage( GetGroundEntity()->GetHealth() ); |
|
info.SetAttacker( this ); |
|
info.SetInflictor( this ); |
|
info.SetDamageType( DMG_GENERIC ); |
|
GetGroundEntity()->TakeDamage( info ); |
|
} |
|
|
|
if( m_pMoanSound && gpGlobals->curtime > m_flTimeUpdateSound ) |
|
{ |
|
// Manage the snorting sound, pitch up for closer. |
|
float flDistNoBBox; |
|
|
|
if( GetEnemy() && m_NPCState == NPC_STATE_COMBAT ) |
|
{ |
|
flDistNoBBox = ( GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter() ).Length(); |
|
flDistNoBBox -= WorldAlignSize().x; |
|
} |
|
else |
|
{ |
|
// Calm down! |
|
flDistNoBBox = FASTZOMBIE_EXCITE_DIST; |
|
m_flTimeUpdateSound += 1.0; |
|
} |
|
|
|
if( flDistNoBBox >= FASTZOMBIE_EXCITE_DIST && m_flDistFactor != 1.0 ) |
|
{ |
|
// Go back to normal pitch. |
|
m_flDistFactor = 1.0; |
|
|
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_IDLE_PITCH, FASTZOMBIE_SOUND_UPDATE_FREQ ); |
|
} |
|
else if( flDistNoBBox < FASTZOMBIE_EXCITE_DIST ) |
|
{ |
|
// Zombie is close! Recalculate pitch. |
|
int iPitch; |
|
|
|
m_flDistFactor = MIN( 1.0, 1 - flDistNoBBox / FASTZOMBIE_EXCITE_DIST ); |
|
iPitch = FASTZOMBIE_MIN_PITCH + ( ( FASTZOMBIE_MAX_PITCH - FASTZOMBIE_MIN_PITCH ) * m_flDistFactor); |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, iPitch, FASTZOMBIE_SOUND_UPDATE_FREQ ); |
|
} |
|
|
|
m_flTimeUpdateSound = gpGlobals->curtime + FASTZOMBIE_SOUND_UPDATE_FREQ; |
|
} |
|
|
|
// Crudely detect the apex of our jump |
|
if( IsNavJumping() && !m_fHitApex && GetAbsVelocity().z <= 0.0 ) |
|
{ |
|
OnNavJumpHitApex(); |
|
} |
|
|
|
if( IsCurSchedule(SCHED_FASTZOMBIE_RANGE_ATTACK1, false) ) |
|
{ |
|
// Think more frequently when flying quickly through the |
|
// air, to update the server's location more often. |
|
SetNextThink(gpGlobals->curtime); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Startup all of the sound patches that the fast zombie uses. |
|
// |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::SoundInit( void ) |
|
{ |
|
if( !m_pMoanSound ) |
|
{ |
|
// !!!HACKHACK - kickstart the moan sound. (sjb) |
|
MoanSound( envFastZombieMoanVolume, ARRAYSIZE( envFastZombieMoanVolume ) ); |
|
|
|
// Clear the commands that the base class gave the moaning sound channel. |
|
ENVELOPE_CONTROLLER.CommandClear( m_pMoanSound ); |
|
} |
|
|
|
CPASAttenuationFilter filter( this ); |
|
|
|
if( !m_pLayer2 ) |
|
{ |
|
// Set up layer2 |
|
m_pLayer2 = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_VOICE, "NPC_FastZombie.Gurgle", ATTN_NORM ); |
|
|
|
// Start silent. |
|
ENVELOPE_CONTROLLER.Play( m_pLayer2, 0.0, 100 ); |
|
} |
|
|
|
SetIdleSoundState(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Make the zombie sound calm. |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::SetIdleSoundState( void ) |
|
{ |
|
// Main looping sound |
|
if ( m_pMoanSound ) |
|
{ |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_IDLE_PITCH, 1.0 ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0.75, 1.0 ); |
|
} |
|
|
|
// Second Layer |
|
if ( m_pLayer2 ) |
|
{ |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, 100, 1.0 ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pLayer2, 0.0, 1.0 ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Make the zombie sound pizzled |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::SetAngrySoundState( void ) |
|
{ |
|
if (( !m_pMoanSound ) || ( !m_pLayer2 )) |
|
{ |
|
return; |
|
} |
|
|
|
EmitSound( "NPC_FastZombie.LeapAttack" ); |
|
|
|
// Main looping sound |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_MIN_PITCH, 0.5 ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1.0, 0.5 ); |
|
|
|
// Second Layer |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, 100, 1.0 ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pLayer2, 0.0, 1.0 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
m_fJustJumped = false; |
|
|
|
m_fIsTorso = m_fIsHeadless = false; |
|
|
|
if( FClassnameIs( this, "npc_fastzombie" ) ) |
|
{ |
|
m_fIsTorso = false; |
|
} |
|
else |
|
{ |
|
// This was placed as an npc_fastzombie_torso |
|
m_fIsTorso = true; |
|
} |
|
|
|
#ifdef HL2_EPISODIC |
|
SetBloodColor( BLOOD_COLOR_ZOMBIE ); |
|
#else |
|
SetBloodColor( BLOOD_COLOR_YELLOW ); |
|
#endif // HL2_EPISODIC |
|
|
|
m_iHealth = 50; |
|
m_flFieldOfView = 0.2; |
|
|
|
CapabilitiesClear(); |
|
CapabilitiesAdd( bits_CAP_MOVE_CLIMB | bits_CAP_MOVE_JUMP | bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 /* | bits_CAP_INNATE_MELEE_ATTACK1 */); |
|
|
|
if ( m_fIsTorso == true ) |
|
{ |
|
CapabilitiesRemove( bits_CAP_MOVE_JUMP | bits_CAP_INNATE_RANGE_ATTACK1 ); |
|
} |
|
|
|
m_flNextAttack = gpGlobals->curtime; |
|
|
|
m_pLayer2 = NULL; |
|
m_iClimbCount = 0; |
|
|
|
EndNavJump(); |
|
|
|
m_flDistFactor = 1.0; |
|
|
|
BaseClass::Spawn(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::PostNPCInit( void ) |
|
{ |
|
SoundInit(); |
|
|
|
m_flTimeUpdateSound = gpGlobals->curtime; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the classname (ie "npc_headcrab") to spawn when our headcrab bails. |
|
//----------------------------------------------------------------------------- |
|
const char *CFastZombie::GetHeadcrabClassname( void ) |
|
{ |
|
return "npc_headcrab_fast"; |
|
} |
|
|
|
const char *CFastZombie::GetHeadcrabModel( void ) |
|
{ |
|
return "models/headcrab.mdl"; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CFastZombie::MaxYawSpeed( void ) |
|
{ |
|
switch( GetActivity() ) |
|
{ |
|
case ACT_TURN_LEFT: |
|
case ACT_TURN_RIGHT: |
|
return 120; |
|
break; |
|
|
|
case ACT_RUN: |
|
return 160; |
|
break; |
|
|
|
case ACT_WALK: |
|
case ACT_IDLE: |
|
return 25; |
|
break; |
|
|
|
default: |
|
return 20; |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::SetZombieModel( void ) |
|
{ |
|
Hull_t lastHull = GetHullType(); |
|
|
|
if ( m_fIsTorso ) |
|
{ |
|
SetModel( "models/zombie/fast_torso.mdl" ); |
|
SetHullType(HULL_TINY); |
|
} |
|
else |
|
{ |
|
SetModel( "models/zombie/fast.mdl" ); |
|
SetHullType(HULL_HUMAN); |
|
} |
|
|
|
SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless ); |
|
|
|
SetHullSizeNormal( true ); |
|
SetDefaultEyeOffset(); |
|
SetActivity( ACT_IDLE ); |
|
|
|
// hull changed size, notify vphysics |
|
// UNDONE: Solve this generally, systematically so other |
|
// NPCs can change size |
|
if ( lastHull != GetHullType() ) |
|
{ |
|
if ( VPhysicsGetObject() ) |
|
{ |
|
SetupVPhysicsHull(); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the model to use for our legs ragdoll when we are blown in twain. |
|
//----------------------------------------------------------------------------- |
|
const char *CFastZombie::GetLegsModel( void ) |
|
{ |
|
return s_pLegsModel; |
|
} |
|
|
|
const char *CFastZombie::GetTorsoModel( void ) |
|
{ |
|
return "models/gibs/fast_zombie_torso.mdl"; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: See if I can swat the player |
|
// |
|
// |
|
//----------------------------------------------------------------------------- |
|
int CFastZombie::MeleeAttack1Conditions( float flDot, float flDist ) |
|
{ |
|
if ( !GetEnemy() ) |
|
{ |
|
return COND_NONE; |
|
} |
|
|
|
if( !(GetFlags() & FL_ONGROUND) ) |
|
{ |
|
// Have to be on the ground! |
|
return COND_NONE; |
|
} |
|
|
|
if( gpGlobals->curtime < m_flNextMeleeAttack ) |
|
{ |
|
return COND_NONE; |
|
} |
|
|
|
int baseResult = BaseClass::MeleeAttack1Conditions( flDot, flDist ); |
|
|
|
// @TODO (toml 07-21-04): follow up with Steve to find out why fz was explicitly not using these conditions |
|
if ( baseResult == COND_TOO_FAR_TO_ATTACK || baseResult == COND_NOT_FACING_ATTACK ) |
|
{ |
|
return COND_NONE; |
|
} |
|
|
|
return baseResult; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a moan sound for this class of zombie. |
|
//----------------------------------------------------------------------------- |
|
const char *CFastZombie::GetMoanSound( int nSound ) |
|
{ |
|
return pMoanSounds[ nSound % ARRAYSIZE( pMoanSounds ) ]; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sound of a footstep |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::FootstepSound( bool fRightFoot ) |
|
{ |
|
if( fRightFoot ) |
|
{ |
|
EmitSound( "NPC_FastZombie.FootstepRight" ); |
|
} |
|
else |
|
{ |
|
EmitSound( "NPC_FastZombie.FootstepLeft" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a random attack hit sound |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::AttackHitSound( void ) |
|
{ |
|
EmitSound( "NPC_FastZombie.AttackHit" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a random attack miss sound |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::AttackMissSound( void ) |
|
{ |
|
// Play a random attack miss sound |
|
EmitSound( "NPC_FastZombie.AttackMiss" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a random attack sound. |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::LeapAttackSound( void ) |
|
{ |
|
EmitSound( "NPC_FastZombie.LeapAttack" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a random attack sound. |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::AttackSound( void ) |
|
{ |
|
EmitSound( "NPC_FastZombie.Attack" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a random idle sound. |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::IdleSound( void ) |
|
{ |
|
EmitSound( "NPC_FastZombie.Idle" ); |
|
MakeAISpookySound( 360.0f ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a random pain sound. |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::PainSound( const CTakeDamageInfo &info ) |
|
{ |
|
if ( m_pLayer2 ) |
|
ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumePain, ARRAYSIZE(envFastZombieVolumePain) ); |
|
if ( m_pMoanSound ) |
|
ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pMoanSound, SOUNDCTRL_CHANGE_VOLUME, envFastZombieInverseVolumePain, ARRAYSIZE(envFastZombieInverseVolumePain) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::DeathSound( const CTakeDamageInfo &info ) |
|
{ |
|
EmitSound( "NPC_FastZombie.Die" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Play a random alert sound. |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::AlertSound( void ) |
|
{ |
|
CBaseEntity *pPlayer = AI_GetSinglePlayer(); |
|
|
|
if( pPlayer ) |
|
{ |
|
// Measure how far the player is, and play the appropriate type of alert sound. |
|
// Doesn't matter if I'm getting mad at a different character, the player is the |
|
// one that hears the sound. |
|
float flDist; |
|
|
|
flDist = ( GetAbsOrigin() - pPlayer->GetAbsOrigin() ).Length(); |
|
|
|
if( flDist > 512 ) |
|
{ |
|
EmitSound( "NPC_FastZombie.AlertFar" ); |
|
} |
|
else |
|
{ |
|
EmitSound( "NPC_FastZombie.AlertNear" ); |
|
} |
|
} |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
#define FASTZOMBIE_MINLEAP 200 |
|
#define FASTZOMBIE_MAXLEAP 300 |
|
float CFastZombie::InnateRange1MaxRange( void ) |
|
{ |
|
return FASTZOMBIE_MAXLEAP; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: See if I can make my leaping attack!! |
|
// |
|
// |
|
//----------------------------------------------------------------------------- |
|
int CFastZombie::RangeAttack1Conditions( float flDot, float flDist ) |
|
{ |
|
|
|
if (GetEnemy() == NULL) |
|
{ |
|
return( COND_NONE ); |
|
} |
|
|
|
if( !(GetFlags() & FL_ONGROUND) ) |
|
{ |
|
return COND_NONE; |
|
} |
|
|
|
if( gpGlobals->curtime < m_flNextAttack ) |
|
{ |
|
return( COND_NONE ); |
|
} |
|
|
|
// make sure the enemy isn't on a roof and I'm in the streets (Ravenholm) |
|
float flZDist; |
|
flZDist = fabs( GetEnemy()->GetLocalOrigin().z - GetLocalOrigin().z ); |
|
if( flZDist > FASTZOMBIE_MAXLEAP_Z ) |
|
{ |
|
return COND_TOO_FAR_TO_ATTACK; |
|
} |
|
|
|
if( flDist > InnateRange1MaxRange() ) |
|
{ |
|
return COND_TOO_FAR_TO_ATTACK; |
|
} |
|
|
|
if( flDist < FASTZOMBIE_MINLEAP ) |
|
{ |
|
return COND_NONE; |
|
} |
|
|
|
if (flDot < 0.8) |
|
{ |
|
return COND_NONE; |
|
} |
|
|
|
if ( !IsMoving() ) |
|
{ |
|
// I Have to be running!!! |
|
return COND_NONE; |
|
} |
|
|
|
// Don't jump at the player unless he's facing me. |
|
// This allows the player to get away if he turns and sprints |
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( GetEnemy() ); |
|
|
|
if( pPlayer ) |
|
{ |
|
// If the enemy is a player, don't attack from behind! |
|
if( !pPlayer->FInViewCone( this ) ) |
|
{ |
|
return COND_NONE; |
|
} |
|
} |
|
|
|
// Drumroll please! |
|
// The final check! Is the path from my position to halfway between me |
|
// and the player clear? |
|
trace_t tr; |
|
Vector vecDirToEnemy; |
|
|
|
vecDirToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter(); |
|
Vector vecHullMin( -16, -16, -16 ); |
|
Vector vecHullMax( 16, 16, 16 ); |
|
|
|
// only check half the distance. (the first part of the jump) |
|
vecDirToEnemy = vecDirToEnemy * 0.5; |
|
|
|
AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + vecDirToEnemy, vecHullMin, vecHullMax, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if( tr.fraction != 1.0 ) |
|
{ |
|
// There's some sort of obstacle pretty much right in front of me. |
|
return COND_NONE; |
|
} |
|
|
|
return COND_CAN_RANGE_ATTACK1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
if ( pEvent->event == AE_FASTZOMBIE_CLIMB_LEFT || pEvent->event == AE_FASTZOMBIE_CLIMB_RIGHT ) |
|
{ |
|
if( ++m_iClimbCount % 3 == 0 ) |
|
{ |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, random->RandomFloat( 100, 150 ), 0.0 ); |
|
ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumeClimb, ARRAYSIZE(envFastZombieVolumeClimb) ); |
|
} |
|
|
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_FASTZOMBIE_LEAP ) |
|
{ |
|
LeapAttack(); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_FASTZOMBIE_GALLOP_LEFT ) |
|
{ |
|
EmitSound( "NPC_FastZombie.GallopLeft" ); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_FASTZOMBIE_GALLOP_RIGHT ) |
|
{ |
|
EmitSound( "NPC_FastZombie.GallopRight" ); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ZOMBIE_ATTACK_RIGHT ) |
|
{ |
|
Vector right; |
|
AngleVectors( GetLocalAngles(), NULL, &right, NULL ); |
|
right = right * -50; |
|
|
|
QAngle angle( -3, -5, -3 ); |
|
ClawAttack( GetClawAttackRange(), 3, angle, right, ZOMBIE_BLOOD_RIGHT_HAND ); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ZOMBIE_ATTACK_LEFT ) |
|
{ |
|
Vector right; |
|
AngleVectors( GetLocalAngles(), NULL, &right, NULL ); |
|
right = right * 50; |
|
QAngle angle( -3, 5, -3 ); |
|
ClawAttack( GetClawAttackRange(), 3, angle, right, ZOMBIE_BLOOD_LEFT_HAND ); |
|
return; |
|
} |
|
|
|
//============================================================================= |
|
#ifdef HL2_EPISODIC |
|
|
|
// Do the leap attack |
|
if ( pEvent->event == AE_FASTZOMBIE_VEHICLE_LEAP ) |
|
{ |
|
VehicleLeapAttack(); |
|
return; |
|
} |
|
|
|
// Die while doing an SS in a vehicle |
|
if ( pEvent->event == AE_FASTZOMBIE_VEHICLE_SS_DIE ) |
|
{ |
|
if ( IsInAVehicle() ) |
|
{ |
|
// Get the vehicle's present speed as a baseline |
|
Vector vecVelocity = vec3_origin; |
|
CBaseEntity *pVehicle = m_PassengerBehavior.GetTargetVehicle(); |
|
if ( pVehicle ) |
|
{ |
|
pVehicle->GetVelocity( &vecVelocity, NULL ); |
|
} |
|
|
|
// TODO: We need to make this content driven -- jdw |
|
Vector vecForward, vecRight, vecUp; |
|
GetVectors( &vecForward, &vecRight, &vecUp ); |
|
|
|
vecVelocity += ( vecForward * -2500.0f ) + ( vecRight * 200.0f ) + ( vecUp * 300 ); |
|
|
|
// Always kill |
|
float flDamage = GetMaxHealth() + 10; |
|
|
|
// Take the damage and die |
|
CTakeDamageInfo info( this, this, vecVelocity * 25.0f, WorldSpaceCenter(), flDamage, (DMG_CRUSH|DMG_VEHICLE) ); |
|
TakeDamage( info ); |
|
} |
|
return; |
|
} |
|
|
|
#endif // HL2_EPISODIC |
|
//============================================================================= |
|
|
|
BaseClass::HandleAnimEvent( pEvent ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Jump at the enemy!! (stole this from the headcrab) |
|
// |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::LeapAttack( void ) |
|
{ |
|
SetGroundEntity( NULL ); |
|
|
|
BeginAttackJump(); |
|
|
|
LeapAttackSound(); |
|
|
|
// |
|
// Take him off ground so engine doesn't instantly reset FL_ONGROUND. |
|
// |
|
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 )); |
|
|
|
Vector vecJumpDir; |
|
CBaseEntity *pEnemy = GetEnemy(); |
|
|
|
if ( pEnemy ) |
|
{ |
|
Vector vecEnemyPos = pEnemy->WorldSpaceCenter(); |
|
|
|
float gravity = GetCurrentGravity(); |
|
if ( gravity <= 1 ) |
|
{ |
|
gravity = 1; |
|
} |
|
|
|
// |
|
// How fast does the zombie need to travel to reach my enemy's eyes given gravity? |
|
// |
|
float height = ( vecEnemyPos.z - GetAbsOrigin().z ); |
|
|
|
if ( height < 16 ) |
|
{ |
|
height = 16; |
|
} |
|
else if ( height > 120 ) |
|
{ |
|
height = 120; |
|
} |
|
float speed = sqrt( 2 * gravity * height ); |
|
float time = speed / gravity; |
|
|
|
// |
|
// Scale the sideways velocity to get there at the right time |
|
// |
|
vecJumpDir = vecEnemyPos - GetAbsOrigin(); |
|
vecJumpDir = vecJumpDir / time; |
|
|
|
// |
|
// Speed to offset gravity at the desired height. |
|
// |
|
vecJumpDir.z = speed; |
|
|
|
// |
|
// Don't jump too far/fast. |
|
// |
|
#define CLAMP 1000.0 |
|
float distance = vecJumpDir.Length(); |
|
if ( distance > CLAMP ) |
|
{ |
|
vecJumpDir = vecJumpDir * ( CLAMP / distance ); |
|
} |
|
|
|
// try speeding up a bit. |
|
SetAbsVelocity( vecJumpDir ); |
|
m_flNextAttack = gpGlobals->curtime + 2; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::StartTask( const Task_t *pTask ) |
|
{ |
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_FASTZOMBIE_VERIFY_ATTACK: |
|
// Simply ensure that the zombie still has a valid melee attack |
|
if( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) |
|
{ |
|
TaskComplete(); |
|
} |
|
else |
|
{ |
|
TaskFail(""); |
|
} |
|
break; |
|
|
|
case TASK_FASTZOMBIE_JUMP_BACK: |
|
{ |
|
SetActivity( ACT_IDLE ); |
|
|
|
SetGroundEntity( NULL ); |
|
|
|
BeginAttackJump(); |
|
|
|
Vector forward; |
|
AngleVectors( GetLocalAngles(), &forward ); |
|
|
|
// |
|
// Take him off ground so engine doesn't instantly reset FL_ONGROUND. |
|
// |
|
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 )); |
|
|
|
ApplyAbsVelocityImpulse( forward * -200 + Vector( 0, 0, 200 ) ); |
|
} |
|
break; |
|
|
|
case TASK_FASTZOMBIE_UNSTICK_JUMP: |
|
{ |
|
SetGroundEntity( NULL ); |
|
|
|
// Call begin attack jump. A little bit later if we fail to pathfind, we check |
|
// this value to see if we just jumped. If so, we assume we've jumped |
|
// to someplace that's not pathing friendly, and so must jump again to get out. |
|
BeginAttackJump(); |
|
|
|
// |
|
// Take him off ground so engine doesn't instantly reset FL_ONGROUND. |
|
// |
|
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 )); |
|
|
|
CBaseEntity *pEnemy = GetEnemy(); |
|
Vector vecJumpDir; |
|
|
|
if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN ) |
|
{ |
|
// Jump off the pipe backwards! |
|
Vector forward; |
|
|
|
GetVectors( &forward, NULL, NULL ); |
|
|
|
ApplyAbsVelocityImpulse( forward * -200 ); |
|
} |
|
else if( pEnemy ) |
|
{ |
|
vecJumpDir = pEnemy->GetLocalOrigin() - GetLocalOrigin(); |
|
VectorNormalize( vecJumpDir ); |
|
vecJumpDir.z = 0; |
|
|
|
ApplyAbsVelocityImpulse( vecJumpDir * 300 + Vector( 0, 0, 200 ) ); |
|
} |
|
else |
|
{ |
|
DevMsg("UNHANDLED CASE! Stuck Fast Zombie with no enemy!\n"); |
|
} |
|
} |
|
break; |
|
|
|
case TASK_WAIT_FOR_MOVEMENT: |
|
// If we're waiting for movement, that means that pathfinding succeeded, and |
|
// we're about to be moving. So we aren't stuck. So clear this flag. |
|
m_fJustJumped = false; |
|
|
|
BaseClass::StartTask( pTask ); |
|
break; |
|
|
|
case TASK_FACE_ENEMY: |
|
{ |
|
// We don't use the base class implementation of this, because GetTurnActivity |
|
// stomps our landing scrabble animations (sjb) |
|
Vector flEnemyLKP = GetEnemyLKP(); |
|
GetMotor()->SetIdealYawToTarget( flEnemyLKP ); |
|
} |
|
break; |
|
|
|
case TASK_FASTZOMBIE_LAND_RECOVER: |
|
{ |
|
// Set the ideal yaw |
|
Vector flEnemyLKP = GetEnemyLKP(); |
|
GetMotor()->SetIdealYawToTarget( flEnemyLKP ); |
|
|
|
// figure out which way to turn. |
|
float flDeltaYaw = GetMotor()->DeltaIdealYaw(); |
|
|
|
if( flDeltaYaw < 0 ) |
|
{ |
|
SetIdealActivity( (Activity)ACT_FASTZOMBIE_LAND_RIGHT ); |
|
} |
|
else |
|
{ |
|
SetIdealActivity( (Activity)ACT_FASTZOMBIE_LAND_LEFT ); |
|
} |
|
|
|
|
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_RANGE_ATTACK1: |
|
|
|
// Make melee attacks impossible until we land! |
|
m_flNextMeleeAttack = gpGlobals->curtime + 60; |
|
|
|
SetTouch( &CFastZombie::LeapAttackTouch ); |
|
break; |
|
|
|
case TASK_FASTZOMBIE_DO_ATTACK: |
|
SetActivity( (Activity)ACT_FASTZOMBIE_LEAP_SOAR ); |
|
break; |
|
|
|
default: |
|
BaseClass::StartTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::RunTask( const Task_t *pTask ) |
|
{ |
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_FASTZOMBIE_JUMP_BACK: |
|
case TASK_FASTZOMBIE_UNSTICK_JUMP: |
|
if( GetFlags() & FL_ONGROUND ) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_RANGE_ATTACK1: |
|
if( ( GetFlags() & FL_ONGROUND ) || ( m_pfnTouch == NULL ) ) |
|
{ |
|
// All done when you touch the ground, or if our touch function has somehow cleared. |
|
TaskComplete(); |
|
|
|
// Allow melee attacks again. |
|
m_flNextMeleeAttack = gpGlobals->curtime + 0.5; |
|
return; |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::RunTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
int CFastZombie::TranslateSchedule( int scheduleType ) |
|
{ |
|
switch( scheduleType ) |
|
{ |
|
case SCHED_RANGE_ATTACK1: |
|
{ |
|
// Scream right now, cause in half a second, we're gonna jump!! |
|
|
|
if( !m_fHasScreamed ) |
|
{ |
|
// Only play that over-the-top attack scream once per combat state. |
|
EmitSound( "NPC_FastZombie.Scream" ); |
|
m_fHasScreamed = true; |
|
} |
|
else |
|
{ |
|
EmitSound( "NPC_FastZombie.RangeAttack" ); |
|
} |
|
|
|
return SCHED_FASTZOMBIE_RANGE_ATTACK1; |
|
} |
|
break; |
|
|
|
case SCHED_MELEE_ATTACK1: |
|
if ( m_fIsTorso == true ) |
|
{ |
|
return SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1; |
|
} |
|
else |
|
{ |
|
return SCHED_FASTZOMBIE_MELEE_ATTACK1; |
|
} |
|
break; |
|
|
|
case SCHED_FASTZOMBIE_UNSTICK_JUMP: |
|
if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT ) |
|
{ |
|
return SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP; |
|
} |
|
else |
|
{ |
|
return SCHED_FASTZOMBIE_UNSTICK_JUMP; |
|
} |
|
break; |
|
case SCHED_MOVE_TO_WEAPON_RANGE: |
|
{ |
|
float flZDist = fabs( GetEnemy()->GetLocalOrigin().z - GetLocalOrigin().z ); |
|
if ( flZDist > FASTZOMBIE_MAXLEAP_Z ) |
|
return SCHED_CHASE_ENEMY; |
|
else // fall through to default |
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
break; |
|
} |
|
|
|
default: |
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
Activity CFastZombie::NPC_TranslateActivity( Activity baseAct ) |
|
{ |
|
if ( baseAct == ACT_CLIMB_DOWN ) |
|
return ACT_CLIMB_UP; |
|
|
|
return BaseClass::NPC_TranslateActivity( baseAct ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFastZombie::LeapAttackTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( !pOther->IsSolid() ) |
|
{ |
|
// Touching a trigger or something. |
|
return; |
|
} |
|
|
|
// Stop the zombie and knock the player back |
|
Vector vecNewVelocity( 0, 0, GetAbsVelocity().z ); |
|
SetAbsVelocity( vecNewVelocity ); |
|
|
|
Vector forward; |
|
AngleVectors( GetLocalAngles(), &forward ); |
|
forward *= 500; |
|
QAngle qaPunch( 15, random->RandomInt(-5,5), random->RandomInt(-5,5) ); |
|
|
|
ClawAttack( GetClawAttackRange(), 5, qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS ); |
|
|
|
SetTouch( NULL ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Lets us know if we touch the player while we're climbing. |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::ClimbTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( pOther->IsPlayer() ) |
|
{ |
|
// If I hit the player, shove him aside. |
|
Vector vecDir = pOther->WorldSpaceCenter() - WorldSpaceCenter(); |
|
vecDir.z = 0.0; // planar |
|
VectorNormalize( vecDir ); |
|
|
|
if( IsXbox() ) |
|
{ |
|
vecDir *= 400.0f; |
|
} |
|
else |
|
{ |
|
vecDir *= 200.0f; |
|
} |
|
|
|
pOther->VelocityPunch( vecDir ); |
|
|
|
if ( GetActivity() != ACT_CLIMB_DISMOUNT || |
|
( pOther->GetGroundEntity() == NULL && |
|
GetNavigator()->IsGoalActive() && |
|
pOther->GetAbsOrigin().z - GetNavigator()->GetCurWaypointPos().z < -1.0 ) ) |
|
{ |
|
SetCondition( COND_FASTZOMBIE_CLIMB_TOUCH ); |
|
} |
|
|
|
SetTouch( NULL ); |
|
} |
|
else if ( dynamic_cast<CPhysicsProp *>(pOther) ) |
|
{ |
|
NPCPhysics_CreateSolver( this, pOther, true, 5.0 ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Shuts down our looping sounds. |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::StopLoopingSounds( void ) |
|
{ |
|
if ( m_pMoanSound ) |
|
{ |
|
ENVELOPE_CONTROLLER.SoundDestroy( m_pMoanSound ); |
|
m_pMoanSound = NULL; |
|
} |
|
|
|
if ( m_pLayer2 ) |
|
{ |
|
ENVELOPE_CONTROLLER.SoundDestroy( m_pLayer2 ); |
|
m_pLayer2 = NULL; |
|
} |
|
|
|
BaseClass::StopLoopingSounds(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fast zombie cannot range attack when he's a torso! |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce ) |
|
{ |
|
CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK1 ); |
|
CapabilitiesRemove( bits_CAP_MOVE_JUMP ); |
|
CapabilitiesRemove( bits_CAP_MOVE_CLIMB ); |
|
|
|
ReleaseHeadcrab( EyePosition(), vecLegsForce * 0.5, true, true, true ); |
|
|
|
BaseClass::BecomeTorso( vecTorsoForce, vecLegsForce ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns true if a reasonable jumping distance |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
bool CFastZombie::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const |
|
{ |
|
const float MAX_JUMP_RISE = 220.0f; |
|
const float MAX_JUMP_DISTANCE = 512.0f; |
|
const float MAX_JUMP_DROP = 384.0f; |
|
|
|
if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) ) |
|
{ |
|
// Hang onto the jump distance. The AI is going to want it. |
|
m_flJumpDist = (startPos - endPos).Length(); |
|
|
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
|
|
bool CFastZombie::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost ) |
|
{ |
|
float delta = vecEnd.z - vecStart.z; |
|
|
|
float multiplier = 1; |
|
if ( moveType == bits_CAP_MOVE_JUMP ) |
|
{ |
|
multiplier = ( delta < 0 ) ? 0.5 : 1.5; |
|
} |
|
else if ( moveType == bits_CAP_MOVE_CLIMB ) |
|
{ |
|
multiplier = ( delta > 0 ) ? 0.5 : 4.0; |
|
} |
|
|
|
*pCost *= multiplier; |
|
|
|
return ( multiplier != 1 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
|
|
bool CFastZombie::ShouldFailNav( bool bMovementFailed ) |
|
{ |
|
if ( !BaseClass::ShouldFailNav( bMovementFailed ) ) |
|
{ |
|
DevMsg( 2, "Fast zombie in scripted sequence probably hit bad node configuration at %s\n", VecToString( GetAbsOrigin() ) ); |
|
|
|
if ( GetNavigator()->GetPath()->CurWaypointNavType() == NAV_JUMP && GetNavigator()->RefindPathToGoal( false ) ) |
|
{ |
|
return false; |
|
} |
|
DevMsg( 2, "Fast zombie failed to get to scripted sequence\n" ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//--------------------------------------------------------- |
|
// Purpose: Notifier that lets us know when the fast |
|
// zombie has hit the apex of a navigational jump. |
|
//--------------------------------------------------------- |
|
void CFastZombie::OnNavJumpHitApex( void ) |
|
{ |
|
m_fHitApex = true; // stop subsequent notifications |
|
} |
|
|
|
//--------------------------------------------------------- |
|
// Purpose: Overridden to detect when the zombie goes into |
|
// and out of his climb state and his navigation |
|
// jump state. |
|
//--------------------------------------------------------- |
|
void CFastZombie::OnChangeActivity( Activity NewActivity ) |
|
{ |
|
if ( NewActivity == ACT_FASTZOMBIE_FRENZY ) |
|
{ |
|
// Scream!!!! |
|
EmitSound( "NPC_FastZombie.Frenzy" ); |
|
SetPlaybackRate( random->RandomFloat( .9, 1.1 ) ); |
|
} |
|
|
|
if( NewActivity == ACT_JUMP ) |
|
{ |
|
BeginNavJump(); |
|
} |
|
else if( GetActivity() == ACT_JUMP ) |
|
{ |
|
EndNavJump(); |
|
} |
|
|
|
if ( NewActivity == ACT_LAND ) |
|
{ |
|
m_flNextAttack = gpGlobals->curtime + 1.0; |
|
} |
|
|
|
if ( NewActivity == ACT_GLIDE ) |
|
{ |
|
// Started a jump. |
|
BeginNavJump(); |
|
} |
|
else if ( GetActivity() == ACT_GLIDE ) |
|
{ |
|
// Landed a jump |
|
EndNavJump(); |
|
|
|
if ( m_pMoanSound ) |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_MIN_PITCH, 0.3 ); |
|
} |
|
|
|
if ( NewActivity == ACT_CLIMB_UP ) |
|
{ |
|
// Started a climb! |
|
if ( m_pMoanSound ) |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0.0, 0.2 ); |
|
|
|
SetTouch( &CFastZombie::ClimbTouch ); |
|
} |
|
else if ( GetActivity() == ACT_CLIMB_DISMOUNT || ( GetActivity() == ACT_CLIMB_UP && NewActivity != ACT_CLIMB_DISMOUNT ) ) |
|
{ |
|
// Ended a climb |
|
if ( m_pMoanSound ) |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1.0, 0.2 ); |
|
|
|
SetTouch( NULL ); |
|
} |
|
|
|
BaseClass::OnChangeActivity( NewActivity ); |
|
} |
|
|
|
|
|
//========================================================= |
|
// |
|
//========================================================= |
|
int CFastZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) |
|
{ |
|
if ( m_fJustJumped ) |
|
{ |
|
// Assume we failed cause we jumped to a bad place. |
|
m_fJustJumped = false; |
|
return SCHED_FASTZOMBIE_UNSTICK_JUMP; |
|
} |
|
|
|
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode ); |
|
} |
|
|
|
//========================================================= |
|
// Purpose: Do some record keeping for jumps made for |
|
// navigational purposes (i.e., not attack jumps) |
|
//========================================================= |
|
void CFastZombie::BeginNavJump( void ) |
|
{ |
|
m_fIsNavJumping = true; |
|
m_fHitApex = false; |
|
|
|
ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumeJump, ARRAYSIZE(envFastZombieVolumeJump) ); |
|
} |
|
|
|
//========================================================= |
|
// |
|
//========================================================= |
|
void CFastZombie::EndNavJump( void ) |
|
{ |
|
m_fIsNavJumping = false; |
|
m_fHitApex = false; |
|
} |
|
|
|
//========================================================= |
|
// |
|
//========================================================= |
|
void CFastZombie::BeginAttackJump( void ) |
|
{ |
|
// Set this to true. A little bit later if we fail to pathfind, we check |
|
// this value to see if we just jumped. If so, we assume we've jumped |
|
// to someplace that's not pathing friendly, and so must jump again to get out. |
|
m_fJustJumped = true; |
|
|
|
m_flJumpStartAltitude = GetLocalOrigin().z; |
|
} |
|
|
|
//========================================================= |
|
// |
|
//========================================================= |
|
void CFastZombie::EndAttackJump( void ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::BuildScheduleTestBits( void ) |
|
{ |
|
// FIXME: This is probably the desired call to make, but it opts into an untested base class path, we'll need to |
|
// revisit this and figure out if we want that. -- jdw |
|
// BaseClass::BuildScheduleTestBits(); |
|
// |
|
// For now, make sure our active behavior gets a chance to add its own bits |
|
if ( GetRunningBehavior() ) |
|
GetRunningBehavior()->BridgeBuildScheduleTestBits(); |
|
|
|
#ifdef HL2_EPISODIC |
|
SetCustomInterruptCondition( COND_PROVOKED ); |
|
#endif // HL2_EPISODIC |
|
|
|
// Any schedule that makes us climb should break if we touch player |
|
if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT) |
|
{ |
|
SetCustomInterruptCondition( COND_FASTZOMBIE_CLIMB_TOUCH ); |
|
} |
|
else |
|
{ |
|
ClearCustomInterruptCondition( COND_FASTZOMBIE_CLIMB_TOUCH ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// |
|
//========================================================= |
|
void CFastZombie::OnStateChange( NPC_STATE OldState, NPC_STATE NewState ) |
|
{ |
|
if( NewState == NPC_STATE_COMBAT ) |
|
{ |
|
SetAngrySoundState(); |
|
} |
|
else if( (m_pMoanSound) && ( NewState == NPC_STATE_IDLE || NewState == NPC_STATE_ALERT ) ) ///!!!HACKHACK - sjb |
|
{ |
|
// Don't make this sound while we're slumped |
|
if ( IsSlumped() == false ) |
|
{ |
|
// Set it up so that if the zombie goes into combat state sometime down the road |
|
// that he'll be able to scream. |
|
m_fHasScreamed = false; |
|
|
|
SetIdleSoundState(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
// Shut up my screaming sounds. |
|
CPASAttenuationFilter filter( this ); |
|
EmitSound( filter, entindex(), "NPC_FastZombie.NoSound" ); |
|
|
|
CTakeDamageInfo dInfo = info; |
|
|
|
#if 0 |
|
|
|
// Become a server-side ragdoll and create a constraint at the hand |
|
if ( m_PassengerBehavior.GetPassengerState() == PASSENGER_STATE_INSIDE ) |
|
{ |
|
IPhysicsObject *pVehiclePhys = m_PassengerBehavior.GetTargetVehicle()->GetServerVehicle()->GetVehicleEnt()->VPhysicsGetObject(); |
|
CBaseAnimating *pVehicleAnimating = m_PassengerBehavior.GetTargetVehicle()->GetServerVehicle()->GetVehicleEnt()->GetBaseAnimating(); |
|
int nRightHandBone = 31;//GetBaseAnimating()->LookupBone( "ValveBiped.Bip01_R_Finger2" ); |
|
Vector vecRightHandPos; |
|
QAngle vecRightHandAngle; |
|
GetAttachment( LookupAttachment( "Blood_Right" ), vecRightHandPos, vecRightHandAngle ); |
|
//CTakeDamageInfo dInfo( GetEnemy(), GetEnemy(), RandomVector( -200, 200 ), WorldSpaceCenter(), 50.0f, DMG_CRUSH ); |
|
dInfo.SetDamageType( info.GetDamageType() | DMG_REMOVENORAGDOLL ); |
|
dInfo.ScaleDamageForce( 10.0f ); |
|
CBaseEntity *pRagdoll = CreateServerRagdoll( GetBaseAnimating(), 0, info, COLLISION_GROUP_DEBRIS ); |
|
|
|
/* |
|
GetBaseAnimating()->GetBonePosition( nRightHandBone, vecRightHandPos, vecRightHandAngle ); |
|
|
|
CBaseEntity *pRagdoll = CreateServerRagdollAttached( GetBaseAnimating(), |
|
vec3_origin, |
|
-1, |
|
COLLISION_GROUP_DEBRIS, |
|
pVehiclePhys, |
|
pVehicleAnimating, |
|
0, |
|
vecRightHandPos, |
|
nRightHandBone, |
|
vec3_origin );*/ |
|
|
|
} |
|
#endif |
|
|
|
BaseClass::Event_Killed( dInfo ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
bool CFastZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold ) |
|
{ |
|
if( m_fIsTorso ) |
|
{ |
|
// Already split. |
|
return false; |
|
} |
|
|
|
// Break in half IF: |
|
// |
|
// Take half or more of max health in DMG_BLAST |
|
if( (info.GetDamageType() & DMG_BLAST) && m_iHealth <= 0 ) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//============================================================================= |
|
#ifdef HL2_EPISODIC |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add the passenger behavior to our repertoire |
|
//----------------------------------------------------------------------------- |
|
bool CFastZombie::CreateBehaviors( void ) |
|
{ |
|
AddBehavior( &m_PassengerBehavior ); |
|
|
|
return BaseClass::CreateBehaviors(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get on the vehicle! |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::InputAttachToVehicle( inputdata_t &inputdata ) |
|
{ |
|
// Interrupt us |
|
SetCondition( COND_PROVOKED ); |
|
|
|
// Find the target vehicle |
|
CBaseEntity *pEntity = FindNamedEntity( inputdata.value.String() ); |
|
CPropJeepEpisodic *pVehicle = dynamic_cast<CPropJeepEpisodic *>(pEntity); |
|
|
|
// Get in the car if it's valid |
|
if ( pVehicle && CanEnterVehicle( pVehicle ) ) |
|
{ |
|
// Set her into a "passenger" behavior |
|
m_PassengerBehavior.Enable( pVehicle ); |
|
m_PassengerBehavior.AttachToVehicle(); |
|
} |
|
|
|
RemoveSpawnFlags( SF_NPC_GAG ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Passed along from the vehicle's callback list |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) |
|
{ |
|
// Only do the override while riding on a vehicle |
|
if ( m_PassengerBehavior.CanSelectSchedule() && m_PassengerBehavior.GetPassengerState() != PASSENGER_STATE_OUTSIDE ) |
|
{ |
|
int damageType = 0; |
|
float flDamage = CalculatePhysicsImpactDamage( index, pEvent, gZombiePassengerImpactDamageTable, 1.0, true, damageType ); |
|
|
|
if ( flDamage > 0 ) |
|
{ |
|
Vector damagePos; |
|
pEvent->pInternalData->GetContactPoint( damagePos ); |
|
Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); |
|
CTakeDamageInfo info( this, this, damageForce, damagePos, flDamage, (damageType|DMG_VEHICLE) ); |
|
TakeDamage( info ); |
|
} |
|
return; |
|
} |
|
|
|
BaseClass::VPhysicsCollision( index, pEvent ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: FIXME: Fold this into LeapAttack using different jump targets! |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::VehicleLeapAttack( void ) |
|
{ |
|
CBaseEntity *pEnemy = GetEnemy(); |
|
if ( pEnemy == NULL ) |
|
return; |
|
|
|
Vector vecEnemyPos; |
|
UTIL_PredictedPosition( pEnemy, 1.0f, &vecEnemyPos ); |
|
|
|
// Move |
|
SetGroundEntity( NULL ); |
|
BeginAttackJump(); |
|
LeapAttackSound(); |
|
|
|
// Take him off ground so engine doesn't instantly reset FL_ONGROUND. |
|
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 )); |
|
|
|
// FIXME: This should be the exact position we'll enter at, but this approximates it generally |
|
//vecEnemyPos[2] += 16; |
|
|
|
Vector vecMins = GetHullMins(); |
|
Vector vecMaxs = GetHullMaxs(); |
|
Vector vecJumpDir = VecCheckToss( this, GetAbsOrigin(), vecEnemyPos, 0.1f, 1.0f, false, &vecMins, &vecMaxs ); |
|
|
|
SetAbsVelocity( vecJumpDir ); |
|
m_flNextAttack = gpGlobals->curtime + 2.0f; |
|
SetTouch( &CFastZombie::VehicleLeapAttackTouch ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CFastZombie::CanEnterVehicle( CPropJeepEpisodic *pVehicle ) |
|
{ |
|
if ( pVehicle == NULL ) |
|
return false; |
|
|
|
return pVehicle->NPC_CanEnterVehicle( this, false ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: FIXME: Move into behavior? |
|
// Input : *pOther - |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::VehicleLeapAttackTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( pOther->GetServerVehicle() ) |
|
{ |
|
m_PassengerBehavior.AttachToVehicle(); |
|
|
|
// HACK: Stop us cold |
|
SetLocalVelocity( vec3_origin ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determine whether we're in a vehicle or not |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CFastZombie::IsInAVehicle( void ) |
|
{ |
|
// Must be active and getting in/out of vehicle |
|
if ( m_PassengerBehavior.IsEnabled() && m_PassengerBehavior.GetPassengerState() != PASSENGER_STATE_OUTSIDE ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Override our efficiency so that we don't jitter when we're in the middle |
|
// of our enter/exit animations. |
|
// Input : bInPVS - Whether we're in the PVS or not |
|
//----------------------------------------------------------------------------- |
|
void CFastZombie::UpdateEfficiency( bool bInPVS ) |
|
{ |
|
// If we're transitioning and in the PVS, we override our efficiency |
|
if ( IsInAVehicle() && bInPVS ) |
|
{ |
|
PassengerState_e nState = m_PassengerBehavior.GetPassengerState(); |
|
if ( nState == PASSENGER_STATE_ENTERING || nState == PASSENGER_STATE_EXITING ) |
|
{ |
|
SetEfficiency( AIE_NORMAL ); |
|
return; |
|
} |
|
} |
|
|
|
// Do the default behavior |
|
BaseClass::UpdateEfficiency( bInPVS ); |
|
} |
|
|
|
#endif // HL2_EPISODIC |
|
//============================================================================= |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
AI_BEGIN_CUSTOM_NPC( npc_fastzombie, CFastZombie ) |
|
|
|
DECLARE_ACTIVITY( ACT_FASTZOMBIE_LEAP_SOAR ) |
|
DECLARE_ACTIVITY( ACT_FASTZOMBIE_LEAP_STRIKE ) |
|
DECLARE_ACTIVITY( ACT_FASTZOMBIE_LAND_RIGHT ) |
|
DECLARE_ACTIVITY( ACT_FASTZOMBIE_LAND_LEFT ) |
|
DECLARE_ACTIVITY( ACT_FASTZOMBIE_FRENZY ) |
|
DECLARE_ACTIVITY( ACT_FASTZOMBIE_BIG_SLASH ) |
|
|
|
DECLARE_TASK( TASK_FASTZOMBIE_DO_ATTACK ) |
|
DECLARE_TASK( TASK_FASTZOMBIE_LAND_RECOVER ) |
|
DECLARE_TASK( TASK_FASTZOMBIE_UNSTICK_JUMP ) |
|
DECLARE_TASK( TASK_FASTZOMBIE_JUMP_BACK ) |
|
DECLARE_TASK( TASK_FASTZOMBIE_VERIFY_ATTACK ) |
|
|
|
DECLARE_CONDITION( COND_FASTZOMBIE_CLIMB_TOUCH ) |
|
|
|
//Adrian: events go here |
|
DECLARE_ANIMEVENT( AE_FASTZOMBIE_LEAP ) |
|
DECLARE_ANIMEVENT( AE_FASTZOMBIE_GALLOP_LEFT ) |
|
DECLARE_ANIMEVENT( AE_FASTZOMBIE_GALLOP_RIGHT ) |
|
DECLARE_ANIMEVENT( AE_FASTZOMBIE_CLIMB_LEFT ) |
|
DECLARE_ANIMEVENT( AE_FASTZOMBIE_CLIMB_RIGHT ) |
|
|
|
#ifdef HL2_EPISODIC |
|
// FIXME: Move! |
|
DECLARE_ANIMEVENT( AE_PASSENGER_PHYSICS_PUSH ) |
|
DECLARE_ANIMEVENT( AE_FASTZOMBIE_VEHICLE_LEAP ) |
|
DECLARE_ANIMEVENT( AE_FASTZOMBIE_VEHICLE_SS_DIE ) |
|
#endif // HL2_EPISODIC |
|
|
|
//========================================================= |
|
// |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FASTZOMBIE_RANGE_ATTACK1, |
|
|
|
" Tasks" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK1" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_FASTZOMBIE_LEAP_STRIKE" |
|
" TASK_RANGE_ATTACK1 0" |
|
" TASK_WAIT 0.1" |
|
" TASK_FASTZOMBIE_LAND_RECOVER 0" // essentially just figure out which way to turn. |
|
" TASK_FACE_ENEMY 0" |
|
" " |
|
" Interrupts" |
|
) |
|
|
|
//========================================================= |
|
// I have landed somewhere that's pathfinding-unfriendly |
|
// just try to jump out. |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FASTZOMBIE_UNSTICK_JUMP, |
|
|
|
" Tasks" |
|
" TASK_FASTZOMBIE_UNSTICK_JUMP 0" |
|
" " |
|
" Interrupts" |
|
) |
|
|
|
//========================================================= |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP, |
|
|
|
" Tasks" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_FASTZOMBIE_UNSTICK_JUMP 0" |
|
" " |
|
" Interrupts" |
|
) |
|
|
|
//========================================================= |
|
// > Melee_Attack1 |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FASTZOMBIE_MELEE_ATTACK1, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_MELEE_ATTACK1 0" |
|
" TASK_MELEE_ATTACK1 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_FASTZOMBIE_FRENZY" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" |
|
" TASK_FASTZOMBIE_VERIFY_ATTACK 0" |
|
" TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_FASTZOMBIE_BIG_SLASH" |
|
|
|
"" |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_ENEMY_OCCLUDED" |
|
); |
|
|
|
//========================================================= |
|
// > Melee_Attack1 |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_MELEE_ATTACK1 0" |
|
" TASK_MELEE_ATTACK1 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" |
|
" TASK_FASTZOMBIE_VERIFY_ATTACK 0" |
|
|
|
"" |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_ENEMY_OCCLUDED" |
|
); |
|
|
|
AI_END_CUSTOM_NPC() |
|
|
|
|
|
|