mirror of
https://github.com/Gigaslav/HL2Overcharged.git
synced 2026-01-05 06:10:21 +03:00
2342 lines
63 KiB
C++
2342 lines
63 KiB
C++
//========= 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"
|
||
|
||
#include "BloodDrips.h"
|
||
#include "BloodDripsGreen.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
|
||
ConVar sk_fastzombie_health( "sk_fastzombie_health","0");
|
||
#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 ); }
|
||
virtual int OnTakeDamage_Alive(const CTakeDamageInfo &inputInfo);
|
||
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();
|
||
|
||
//UTIL_SetSize(this, Vector(RandomFloat(-3, -2), RandomFloat(-3, -2), RandomFloat(-3, -2)), Vector(RandomFloat(3, 2), RandomFloat(3, 2), RandomFloat(3, 2)));
|
||
|
||
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;
|
||
float j;
|
||
j = random->RandomFloat( 0.4, 1.4 );
|
||
m_iHealth = sk_fastzombie_health.GetFloat()*j;
|
||
|
||
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/zombie/fast_torso.mdl";//"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:
|
||
// Input : &inputInfo -
|
||
// Output : int
|
||
//-----------------------------------------------------------------------------
|
||
int CFastZombie::OnTakeDamage_Alive(const CTakeDamageInfo &inputInfo)
|
||
{
|
||
/*if (cvar->FindVar("oc_ragdoll_enable_blooddrips")->GetInt())
|
||
{
|
||
int MaxDrips = RandomInt(1, 5);
|
||
int MaxDripsGreen = RandomInt(1, 5);
|
||
Vector vecTraceDir;
|
||
if (inputInfo.GetDamageType() & (DMG_BULLET | DMG_SHOCK))
|
||
{
|
||
for (int i = 0; i < MaxDrips; i++)
|
||
{
|
||
vecTraceDir = WorldSpaceCenter();
|
||
if (LastHitGroup() == HITGROUP_CHEST)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(-0.1, 0.1);
|
||
vecTraceDir.y += random->RandomFloat(-0.1, 0.1);
|
||
vecTraceDir.z += random->RandomFloat(-0.1, 0.1);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_HEAD)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.4);
|
||
vecTraceDir.y += random->RandomFloat(0.1, 0.4);
|
||
vecTraceDir.z += random->RandomFloat(12.1, 12.4);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_LEFTARM)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
|
||
vecTraceDir.y += random->RandomFloat(-12.1, -12.4);
|
||
vecTraceDir.z += random->RandomFloat(0.1, 0.2);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_RIGHTARM)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
|
||
vecTraceDir.y += random->RandomFloat(12.1, 12.4);
|
||
vecTraceDir.z += random->RandomFloat(0.1, 0.2);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_LEFTLEG)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
|
||
vecTraceDir.y += random->RandomFloat(-12.1, -12.4);
|
||
vecTraceDir.z += random->RandomFloat(-20.1, -20.2);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_RIGHTLEG)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
|
||
vecTraceDir.y += random->RandomFloat(12.1, 12.4);
|
||
vecTraceDir.z += random->RandomFloat(-20.1, -20.2);
|
||
}
|
||
|
||
// Set the velocity
|
||
Vector vecVelocity;
|
||
AngularImpulse angImpulse;
|
||
|
||
QAngle angles;
|
||
angles.x = random->RandomFloat(-70, 20);
|
||
angles.y = random->RandomFloat(0, 360);
|
||
angles.z = 0.0f;
|
||
AngleVectors(angles, &vecVelocity);
|
||
|
||
vecVelocity *= random->RandomFloat(150, 300);
|
||
vecVelocity += GetAbsVelocity();
|
||
|
||
angImpulse = RandomAngularImpulse(-280, 280);
|
||
|
||
|
||
|
||
CBloodDrips *pGrenade = (CBloodDrips*)CreateEntityByName("BloodDrips");
|
||
//pGrenade->SetAbsOrigin(vecTraceDir);
|
||
pGrenade->SetLocalOrigin(vecTraceDir);
|
||
pGrenade->SetAbsAngles(RandomAngle(0, 360));
|
||
DispatchSpawn(pGrenade);
|
||
pGrenade->SetThrower(this);
|
||
pGrenade->SetOwnerEntity(this);
|
||
pGrenade->SetAbsVelocity(vecVelocity);
|
||
|
||
|
||
|
||
/*CBloodEmitter *pBlood = (CBloodEmitter*)CreateEntityByName("blood_emitter");// CEndPointB::DispatchImpactSound(vecTraceDir); // Light Kill : Test
|
||
pBlood->direction = inputInfo.
|
||
pBlood->timeThink = Approach(0.5f, 0.05, 1);//0.05f;
|
||
pBlood->composeColor = 2;
|
||
pBlood->Spawn();
|
||
pBlood->SetLocalOrigin(vecTraceDir);
|
||
pBlood->SetAbsOrigin(vecTraceDir);
|
||
pBlood->SetParent(this);*/
|
||
|
||
/*CBloodEmitter *pBlood = (CBloodEmitter*)CreateEntityByName("blood_emitter");
|
||
//pGrenade->SetAbsOrigin(vecTraceDir);
|
||
pBlood->SetMoveType(MOVETYPE_NONE);
|
||
pBlood->SetLocalOrigin(vecTraceDir);
|
||
pBlood->SetAbsAngles(RandomAngle(0, 360));
|
||
DispatchSpawn(pBlood);
|
||
//pBlood->SetThrower(this);
|
||
pBlood->SetParent(this);*/
|
||
//pBlood->SetOwnerEntity(this);
|
||
|
||
/*}
|
||
for (int i = 0; i < MaxDripsGreen; i++)
|
||
{
|
||
vecTraceDir = WorldSpaceCenter();
|
||
if (LastHitGroup() == HITGROUP_CHEST)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(-0.1, 0.1);
|
||
vecTraceDir.y += random->RandomFloat(-0.1, 0.1);
|
||
vecTraceDir.z += random->RandomFloat(-0.1, 0.1);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_HEAD)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.4);
|
||
vecTraceDir.y += random->RandomFloat(0.1, 0.4);
|
||
vecTraceDir.z += random->RandomFloat(12.1, 12.4);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_LEFTARM)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
|
||
vecTraceDir.y += random->RandomFloat(-12.1, -12.4);
|
||
vecTraceDir.z += random->RandomFloat(0.1, 0.2);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_RIGHTARM)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
|
||
vecTraceDir.y += random->RandomFloat(12.1, 12.4);
|
||
vecTraceDir.z += random->RandomFloat(0.1, 0.2);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_LEFTLEG)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
|
||
vecTraceDir.y += random->RandomFloat(-12.1, -12.4);
|
||
vecTraceDir.z += random->RandomFloat(-20.1, -20.2);
|
||
}
|
||
if (LastHitGroup() == HITGROUP_RIGHTLEG)
|
||
{
|
||
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
|
||
vecTraceDir.y += random->RandomFloat(12.1, 12.4);
|
||
vecTraceDir.z += random->RandomFloat(-20.1, -20.2);
|
||
}
|
||
|
||
// Set the velocity
|
||
Vector vecVelocity;
|
||
AngularImpulse angImpulse;
|
||
|
||
QAngle angles;
|
||
angles.x = random->RandomFloat(-70, 20);
|
||
angles.y = random->RandomFloat(0, 360);
|
||
angles.z = 0.0f;
|
||
AngleVectors(angles, &vecVelocity);
|
||
|
||
vecVelocity *= random->RandomFloat(150, 300);
|
||
vecVelocity += GetAbsVelocity();
|
||
|
||
angImpulse = RandomAngularImpulse(-180, 180);
|
||
|
||
CBloodDripsGreen *pGrenade = (CBloodDripsGreen*)CreateEntityByName("BloodDripsGreen");
|
||
//pGrenade->SetAbsOrigin(vecTraceDir);
|
||
pGrenade->SetLocalOrigin(vecTraceDir);
|
||
pGrenade->SetAbsAngles(RandomAngle(0, 360));
|
||
DispatchSpawn(pGrenade);
|
||
pGrenade->SetThrower(this);
|
||
pGrenade->SetOwnerEntity(this);
|
||
pGrenade->SetAbsVelocity(vecVelocity);
|
||
}
|
||
}
|
||
|
||
}*/
|
||
|
||
//UTIL_BloodStream(vecTraceDir, vecVelocity, 1, 25);
|
||
return BaseClass::OnTakeDamage_Alive(inputInfo);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// 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"//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
" 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()
|
||
|
||
|